Двойная проверка правильности реализации фрагмента + шаблона держателя вида - PullRequest
6 голосов
/ 30 марта 2012

Я переработал какой-то код из-за утечки памяти.Код является частью раздела справки приложений, где мы используем FragmentActivity и FragmentPageAdapter, чтобы позволить пользователю пролистывать различные экраны справки.Каждый фрагмент, класс SectionFragment ниже, включает изображение, текст заголовка и основной текст.

Утечка памяти проявилась, потому что каждый раз при вызове onCreateView во фрагменте создавалось новое представление.

public class SectionFragment extends Fragment {

    private ImageView imgvw;
    private TextView headerTxvw;
    private TextView bodyTxvw;

    public int[][] content;        
    protected int pageIdx;

    public SectionFragment(int idx, int[][] content ){
        super();
        pageIdx = idx;
        this.content = content;
    }

    protected int getPageIdx() {
        return pageIdx;
    }

    protected Drawable getImageDrawable() {
        return getResources().getDrawable( content[pageIdx][0] );
    }

    protected String getHeaderText() {
        return getResources().getString( content[pageIdx][1] );
    }

    protected String getSubeaderText() {
        return getResources().getString( content[pageIdx][2] );
    }

    protected void loadView( View vw ) {
        imgvw = (ImageView)vw.findViewById( R.id.top_img );
        headerTxvw = (TextView)vw.findViewById( R.id.header_txt );
        bodyTxvw = (TextView)vw.findViewById( R.id.body_txt );

        imgvw.setImageDrawable( getImageDrawable() );
        headerTxvw.setText( getHeaderText() );
        bodyTxvw.setText( getSubeaderText() );
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
        View vw = inflater.inflate( R.layout.help_fragment, null );
        loadView( vw );
        return vw;
    }
}

Я все еще новичок во фрагментах и ​​не был частьюпервоначальное усилие по написанию кода, поэтому я не уверен, что мое решение верное, но оно напоминает шаблон держателя представления, который я много раз реализовывал в List Activity.Я был бы очень признателен за любые отзывы о следующей реализации, поэтому мне будет удобнее, если это правильно.

public class SectionFragment extends Fragment {

static private class ViewHolder {
    ImageView imgvw;
    TextView headerTxvw;
    TextView bodyTxvw;
}

public int[][] _content;
protected int _pageIdx;

public SectionFragment( int idx, int[][] content ){
    super();
    _pageIdx = idx;
    _content = content;
}

protected int getPageIdx() {
    return _pageIdx;
}

protected Drawable getImageDrawable() {
    return getResources().getDrawable( _content[_pageIdx][0] );
}

protected String getHeaderText() {
    return getResources().getString( _content[_pageIdx][1] );
}

protected String getSubeaderText() {
    return getResources().getString( _content[_pageIdx][2] );
}

protected void loadView( View vw ) {

    _holder.imgvw.setImageDrawable( getImageDrawable() );
    _holder.headerTxvw.setText( getHeaderText() );
    _holder.bodyTxvw.setText( getSubeaderText() );
}

private View _vw;
private ViewHolder _holder;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    if ( _vw == null ) {
        _vw = inflater.inflate( R.layout.help_fragment, null );

        _holder = new ViewHolder();
        _holder.imgvw = (ImageView)_vw.findViewById( R.id.top_img );
        _holder.headerTxvw = (TextView)_vw.findViewById( R.id.header_txt );
        _holder.bodyTxvw = (TextView)_vw.findViewById( R.id.body_txt );
        _vw.setTag( _holder );
    } else {
        ViewParent oldparent = (ViewParent)_vw.getParent();
        if ( oldparent != container ) {
            ((ViewGroup)oldparent).removeView( _vw );
        }
        _holder = (ViewHolder)_vw.getTag();
    }
    loadView( _vw );
    return _vw;
}
}

Я не включил другие классы, связанные с этим кодом, в частности FragmentActivity и FragmentPagerAdapter.потому что они, кажется, реализованы должным образом, но по запросу я также могу включить их.

1 Ответ

11 голосов
/ 30 марта 2012

Об утечках памяти в приложениях Android в целом

Вы уверены, что это утечка памяти, а не просто задержка сборки мусора? Вы запустили, например, плагин анализатора памяти Eclipse в приложении? Часто он может точно показать, где происходят утечки.

О просмотре фрагмента и его переработке (или его отсутствии)

Мое первое предположение об утечках / увеличении памяти в фрагментном пейджере - наличие динамического адаптера. Фрагмент pagepageapapter довольно тупой и не будет хорошо обрабатывать изменения .

Насколько я вижу из источника, он никогда не уничтожит фрагменты, даже если вы, например, измените счет с 5 на 4 (пятый фрагмент останется в диспетчере фрагментов). Это происходит из-за того, что он отделен, а не уничтожен. Однако fragmentstatepageradapter уничтожит представления, если они находятся за пределами поля страницы.

И все это на самом деле не приводит к утечкам памяти в долгосрочной перспективе, поскольку эти фрагменты будут очищены, когда менеджер фрагментов будет очищен от фрагментов позже. Однако это может немного увеличить использование памяти (что потенциально может привести к ошибкам памяти).

Новое представление действительно создается каждый раз, когда создается фрагмент (и чаще, чем это, если для параметра retaininstance задано значение true), но если вы не используете FragmentStatePagerAdapter, который должен появляться только один раз для фрагмента, поскольку обычный адаптер пейджера отсоединяется и присоединяется те же фрагменты из менеджера фрагментов. И в любом случае представление будет удалено после уничтожения действия, если вы не сохраните ссылки на него.

Кажется, вы сохраняете ссылки на представления в своем классе, чего я обычно стараюсь избегать. Я предпочитаю использовать getView (). FindViewById (), когда они мне нужны, но в этом случае я думаю, что ссылки на элементы должны быть найдены и удалены gc. Это все зависит от того, если вы пропустите эти ссылки в другом месте, конечно.

О держателе в фрагментном пейджере

Вы не должны пытаться делать что-то вроде виджета во фрагменте. Это, вероятно, не повредит в этом примере, но я не вижу, чтобы вы что-то извлекали из этого. Просто надуйте представление в oncreateview, установите значения и покончите с этим.

Нет повторного использования представлений, например, в виде списка, поэтому шаблон ViewHolder не имеет смысла, а только рискует привести к утечкам памяти или задержанным сборкам мусора из-за висячих ссылок.

Представления не предназначены для повторного использования, даже если они могут использоваться во время некоторых родительских хитростей, которые вы использовали. Это должно быть сделано только как окончательный выход. Если в потоке что-то не так, это может привести к ужасным утечкам.

Пожалуйста, исправьте меня, если кто-то может увидеть разумное использование для этого.

Примечание на ViewHolder

Я бы на самом деле воздерживался от использования держателя вида, если бы он мне действительно не понадобился даже в виде списка. Я видел, как эти теги приводили к действительно неприятным утечкам памяти, например, в некоторых адаптерах курсора, и я хотел бы упростить макеты строк перед попыткой сохранения ссылок. Findviewbyid не будет таким дорогим в хорошем макете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...