2013-12-06 4 views
2

Я написал приложение для чтения книг, которое использует пейджер представления с FragmentStatePagerAdapter. С ним нет очевидной ошибки времени выполнения. Тем не менее, использование памяти продолжает увеличиваться при прокрутке страниц. Поэтому я протестировал простую версию установки и протестировал ее на Nexus 7. Пользователь должен чередовать несколько проверок и несколько поворотов. После этого несколько раз, из журнала видно, что использование кучи продолжает расти. Я думаю, что это наводит на мысль о утечке памяти. Однако я не могу определить причину этого. Прикреплены код, журнал GC и два подозреваемых в утечке из MAT. Любое полезное понимание будет оценено по достоинству. Благодарю.просмотр анализатор памяти пейджера

MainActivity.java:

package com.example.leakypager; 
import android.os.Bundle; 
import android.support.v4.app.Fragment; 
import android.support.v4.app.FragmentActivity; 
import android.support.v4.app.FragmentManager; 
import android.support.v4.app.FragmentStatePagerAdapter; 
import android.support.v4.view.ViewPager; 
import android.util.Log; 

public class MainActivity extends FragmentActivity { 

    public static final String TAG = "MainActivity"; 
    public static final String EXTRA_argument = "pageNumber"; 
    private static final int viewPagerId = 1; 
    private static final int totalPages = 10; 

    private int mPageNumber; 
    private ViewPager mViewPager; 
    private int mTotalPages; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     // Set a default value to page 1 
     mPageNumber = getIntent().getIntExtra(EXTRA_argument, 1); 

     Log.i(TAG, "Called onCreate() on page number " + mPageNumber); 

     mViewPager = new ViewPager(this); 
     mViewPager.setId(viewPagerId); 
     setContentView(mViewPager); 

     mTotalPages = totalPages; 

     FragmentManager fragmentManager; 
     fragmentManager = getSupportFragmentManager();  

     mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) { 
      @Override 
      public Fragment getItem(int pos) { 
       // The page number is one greater than the pager index. 
       Fragment pageFragment; 
       pageFragment = PageFragment.newInstance(pos+1);     
       return pageFragment; 
      } 

      @Override 
      public int getCount() { 
       return mTotalPages; 
      } 

      @Override 
      public int getItemPosition(Object item) { 
       return POSITION_NONE; 
      } 
     }); 

     if (mPageNumber >= 1 && mPageNumber <= mTotalPages) { 
      // The pager item index is one less than the page number. 
      mViewPager.setCurrentItem(mPageNumber-1); 
     } 
    } 
} 

PageFragment.java:

package com.example.leakypager; 

import android.os.Bundle; 
import android.support.v4.app.Fragment; 
import android.text.Editable; 
import android.text.TextWatcher; 
import android.util.Log; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 
import android.view.WindowManager; 
import android.widget.EditText; 
import android.widget.TextView; 


public class PageFragment extends Fragment { 
    private static final String EXTRA_int = "argument"; 
    private static final String TAG = "PageFragment"; 

    private int mPageNumber; 
    private String mData; 
    private String mNote = ""; 

    private TextView mDataView; 
    private EditText mNoteView; 


    public static PageFragment newInstance(int pagenumber) { 
     Bundle args = new Bundle(); 
     args.putInt(EXTRA_int, pagenumber); 
     PageFragment fragment = new PageFragment(); 
     fragment.setArguments(args); 
     return fragment; 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState);  
     mPageNumber = (Integer) getArguments().getInt(EXTRA_int); 
     mData = "This is page " + mPageNumber; 
    } 

    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { 
     Log.i(TAG, "onCreateView() on page " + mPageNumber); 
     View v; 

     v = inflater.inflate(R.layout.fragment_page, parent, false); 

     mDataView = (TextView) v.findViewById(R.id.fragmentPage_data_id); 
     mNoteView = (EditText) v.findViewById(R.id.fragmentPage_note_id); 

     mNoteView.addTextChangedListener(new TextWatcher() { 
      @Override 
      public void afterTextChanged(Editable arg0) {} 
      @Override 
      public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {} 

      @Override 
      public void onTextChanged(CharSequence c, int start, int before, int count) { 
       if (c==null) c=""; // do not allow c to be null 
       mNote = c.toString().trim(); 
      } 
     }); 
    getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); 
     mDataView.setText(mData); 
     mNoteView.setText(mNote);  

     return v; 
    } // end to implementing onCreateView() for PageFragment 

} 

Файл макета содержит только один TextView и один редактировать текст внутри LinearLayout. (Не воспроизводится здесь, если не запрашивается).

Бревно с GC фильтром показывает следующий результат:

12-06 00:07:03.205: D/dalvikvm(23276): GC_FOR_ALLOC freed 232K, 4% free 7758K/8052K, paused 16ms, total 18ms 
12-06 00:07:10.095: D/dalvikvm(23276): GC_FOR_ALLOC freed 272K, 5% free 7989K/8324K, paused 19ms, total 19ms 
12-06 00:07:20.945: D/dalvikvm(23276): GC_FOR_ALLOC freed 460K, 7% free 8043K/8564K, paused 21ms, total 22ms 
12-06 00:07:23.805: D/dalvikvm(23276): GC_FOR_ALLOC freed 511K, 7% free 8043K/8616K, paused 17ms, total 17ms 
12-06 00:07:42.535: D/dalvikvm(23276): GC_FOR_ALLOC freed 421K, 6% free 8134K/8616K, paused 18ms, total 21ms 
12-06 00:08:03.925: D/dalvikvm(23276): GC_FOR_ALLOC freed 537K, 7% free 8109K/8708K, paused 20ms, total 20ms 
12-06 00:08:11.665: D/dalvikvm(23276): GC_FOR_ALLOC freed 537K, 8% free 8083K/8708K, paused 27ms, total 30ms 
12-06 00:08:15.265: D/dalvikvm(23276): GC_FOR_ALLOC freed 456K, 7% free 8138K/8708K, paused 18ms, total 18ms 
12-06 00:08:20.015: D/dalvikvm(23276): GC_FOR_ALLOC freed 313K, 5% free 8315K/8708K, paused 25ms, total 26ms 
12-06 00:08:24.685: D/dalvikvm(23276): GC_FOR_ALLOC freed 448K, 6% free 8378K/8888K, paused 18ms, total 19ms 
12-06 00:08:27.955: D/dalvikvm(23276): GC_FOR_ALLOC freed 330K, 5% free 8560K/8952K, paused 28ms, total 28ms 
12-06 00:08:36.055: D/dalvikvm(23276): GC_FOR_ALLOC freed 449K, 6% free 8625K/9136K, paused 37ms, total 37ms 
12-06 00:08:42.015: D/dalvikvm(23276): GC_FOR_ALLOC freed 438K, 6% free 8699K/9200K, paused 60ms, total 63ms 
12-06 00:08:45.775: D/dalvikvm(23276): GC_FOR_ALLOC freed 454K, 6% free 8760K/9276K, paused 27ms, total 30ms 
12-06 00:08:51.185: D/dalvikvm(23276): GC_FOR_ALLOC freed 491K, 6% free 8771K/9328K, paused 36ms, total 36ms 
12-06 00:08:57.845: D/dalvikvm(23276): GC_FOR_ALLOC freed 382K, 5% free 8866K/9328K, paused 28ms, total 30ms 
12-06 00:09:03.885: D/dalvikvm(23276): GC_FOR_ALLOC freed 491K, 6% free 8887K/9440K, paused 26ms, total 27ms 
12-06 00:09:06.905: D/dalvikvm(23276): GC_FOR_ALLOC freed 442K, 6% free 8955K/9460K, paused 26ms, total 27ms 
12-06 00:09:11.215: D/dalvikvm(23276): GC_FOR_ALLOC freed 408K, 5% free 9058K/9528K, paused 23ms, total 25ms 
12-06 00:09:19.355: D/dalvikvm(23276): GC_FOR_ALLOC freed 445K, 6% free 9130K/9640K, paused 47ms, total 47ms 
12-06 00:09:24.965: D/dalvikvm(23276): GC_FOR_ALLOC freed 479K, 6% free 9208K/9748K, paused 29ms, total 30ms 
12-06 00:09:30.735: D/dalvikvm(23276): GC_FOR_ALLOC freed 456K, 6% free 9330K/9848K, paused 35ms, total 36ms 
12-06 00:09:39.765: D/dalvikvm(23276): GC_FOR_ALLOC freed 473K, 6% free 9477K/10012K, paused 37ms, total 37ms 
12-06 00:09:54.795: D/dalvikvm(23276): GC_FOR_ALLOC freed 344K, 4% free 9803K/10208K, paused 36ms, total 36ms 
12-06 00:10:05.605: D/dalvikvm(23276): GC_FOR_ALLOC freed 478K, 6% free 10104K/10644K, paused 28ms, total 28ms 
12-06 00:10:17.165: D/dalvikvm(23276): GC_FOR_ALLOC freed 499K, 6% free 10483K/11044K, paused 40ms, total 41ms 
12-06 00:10:28.855: D/dalvikvm(23276): GC_FOR_ALLOC freed 679K, 7% free 10811K/11552K, paused 44ms, total 46ms 
12-06 00:10:43.815: D/dalvikvm(23276): GC_FOR_ALLOC freed 873K, 8% free 11054K/11988K, paused 47ms, total 47ms 
12-06 00:11:02.155: D/dalvikvm(23276): GC_FOR_ALLOC freed 891K, 8% free 11360K/12312K, paused 41ms, total 41ms 
12-06 00:11:22.725: D/dalvikvm(23276): GC_FOR_ALLOC freed 807K, 7% free 11852K/12720K, paused 58ms, total 58ms 
12-06 00:11:46.165: D/dalvikvm(23276): GC_FOR_ALLOC freed 932K, 8% free 12382K/13376K, paused 45ms, total 45ms 

Вот два интересных подозреваемых утечки сообщенные MAT:

Suspect 1. 
49 instances of "android.support.v4.app.Fragment$SavedState", 
loaded by "dalvik.system.PathClassLoader @ 0x420ebce8" occupy 
1,892,544 (16.76%) bytes. These instances are referenced from 
one instance of "java.lang.Object[]", loaded by "<system class loader>" 

Keywords 
java.lang.Object[] 
android.support.v4.app.Fragment$SavedState 
dalvik.system.PathClassLoader @ 0x420ebce8 


Suspect 2. 
19 instances of "com.example.leakypager.MainActivity", loaded by 
"dalvik.system.PathClassLoader @ 0x420ebce8" occupy 1,419,320 (12.57%) bytes. 
Biggest instances: 
com.example.leakypager.MainActivity @ 0x422adfa0 - 246,896 (2.19%) bytes. 
com.example.leakypager.MainActivity @ 0x4238a430 - 237,880 (2.11%) bytes. 
com.example.leakypager.MainActivity @ 0x420edc60 - 160,600 (1.42%) bytes. 

Keywords 
dalvik.system.PathClassLoader @ 0x420ebce8 
com.example.leakypager.MainActivity 

(Следующее обновление добавляется дек 17, 2013) После нескольких исследований с большой помощью Мартина (спасибо!), Я узнал, что есть два преступника. Первый - TextWatcher (TextChangedListener), который не был выпущен. Это легко исправить. Второй виновник очень противный.

Второй виновник исходит от EditText, который по-прежнему сохраняет ссылку (mContext) на уничтоженную деятельность. Эта проблема возникает только тогда, когда приложение протестировано на Nexus 7 под управлением Android 4.3. Это не происходит в Samsung TF, работающем под управлением 4.1.3. MAT показывает следующие входящие ссылки с существующей деятельности в мертвую деятельность:

com.example.leakySimplePager.MainActivity @ 0x4213b220 (dead) 
'- mContext android.widget.EditText @ 0x42114ec8     
'- mTextView android.widget.Editor @ 0x4214b4b0 
    '- this$0 android.widget.Editor$EasyEditSpanController @ 0x420fafa0 
    '- [2] java.lang.Object[13] @ 0x420e4a08 
    '- mSpans android.text.SpannableString @ 0x42129b90  
    '- text android.widget.TextView$SavedState @ 0x42129b68  
     '- [2] java.lang.Object[13] @ 0x42129b20   
     '- mValues android.util.SparseArray @ 0x42129ab8   
     '- value java.util.HashMap$HashMapEntry @ 0x4210e420    
     '- [2] java.util.HashMap$HashMapEntry[4] @ 0x4210e3f8    
      '- table java.util.HashMap @ 0x4210e3c0     
      '- mMap android.os.Bundle @ 0x4210e398     
      '- mState android.support.v4.app.Fragment$SavedState @ 0x42129bb0      
      '- [0] java.lang.Object[12] @ 0x42123300      
       '- array java.util.ArrayList @ 0x42106830       
       '- mSavedState com.example.leakySimplePager.MainActivity$ViewPagerAdapter @ 0x42114e90 
       '- mViewPagerAdapter com.example.leakySimplePager.MainActivity @ 0x42140910 (alive) 
       '- mContext com.android.internal.policy.impl.PhoneWindow$DecorView @ 0x420efc78        
        '- mCurRootView android.view.inputmethod.InputMethodManager @ 0x42102ee8         

Связь между живой активностью через SavedState каждый TextView (EditText является TextView), которая автоматически сохраняется системой при представление имеет изменения. (Ссылка: http://developer.android.com/guide/components/activities.html#SavingActivityState) Этот несчастный SavedState ссылается на FragmentStatePagerAdapter как часть его работы. В отличие от слушателя, я не могу освободить FragmentStatePagerAdapter из ViewPager, когда действие уничтожено.

Я заметил, что эта проблема возникает не только с помощью EditText, но и с TextView, которая поддерживает изменение его текстовых форматов/цветов и т. Д. Так или иначе, поскольку TextView не может освободить свой mContext, мой текущий поворот явно запретить его сохранение своего состояния, вызвав mNoteView.setSaveEnabled (false); когда создается представление. Это не повредит моему фактическому приложению, так как я уже сохраняю, загружаю/перезагружаю данные EditText всякий раз, когда фрагмент возвращается к активному. Однако документация разработчиков Android, похоже, не поощряет такой подход. Возможно, есть лучший способ сделать это. Любые новые идеи или советы экспертов будут приветствоваться!

+0

Зачем вам делать ... @Override public int getItemPosition (объект объекта) { return POSITION_NONE; }, что очень неэффективно. –

+0

В исходном приложении мне нужно обновлять позиции каждый раз, потому что на всех страницах, которые могут измениться, когда пользователь выбирает определенные параметры из меню, есть дополнительные функции, такие как фигура, макет экрана и т. Д. Эти функции не включены в этот простой код, потому что я хочу сосредоточиться на тестировании использования памяти в viewpager. – Ahui

+0

Понял. Я все еще не видел ничего странного здесь ... разве вы, ради тестирования, пытались временно переключиться на FragmentPagerAdapter вместо государственной версии? –

ответ

0

Я не уверен, что происходит, но я собираюсь начать бросать идеи в ответ в надежде, что вы сможете найти проблему (намного проще, чем комментарии).

1) Попробуйте переключиться на FragmentPagerAdapter (который работает по-другому). Я не думаю, что это сильно изменится.

2) Улучшение вашего фрагмента, делая более окончательный материал ... например .:

final View v = inflater.inflate(R.layout.fragment_page, parent, false); 

как mDataView и mNoteView являются поля, они могут быть местные жители?

Try решений:

final TextView dataView = (TextView) v.findViewById(R.id.fragmentPage_data_id); 
final EditText noteView = (EditText) v.findViewById(R.id.fragmentPage_note_id); 

final сделает их доступными в вашем анонимном классе.

Я немного подозрительными об этом (внутри каждого фрагмента и внутри onCreateView:

getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); 

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

А также, если вы только изменяете DataView и EditView здесь, и вы можете сделать их окончательными, затем исправить эти линии:

dataView.setText(mData); 
    noteView.setText(mNote);  

Наконец, что я бы лично изменить в вашей деятельности ...

Я полностью создать внутренний класс для этого фрагмента адаптера вместо использования Anonymous.

Создать поле, чтобы держать его:

ViewPagerAdapter mViewPagerAdapter;

(это будет ваш внутренний класс)

private class ViewPagerAdapter extends FragmentStatePagerAdapter {

, а затем создать его экземпляр позже в вашем OnCreate:

mPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); 
mViewPager.setAdapter(mPagerAdapter); 

Кроме того, dr op экземпляра FragmentManager, потому что вы, похоже, не используете его снова, сохраните память;)

Это также позволит вам делать такие вещи, как: mPagerAdapter.notifyDataSetChanged();, если вам нужно.

Так что это то, что я сделал бы так, попробую и дайте мне знать, если увидите какие-либо изменения.

+0

Спасибо. Я смог включить некоторые из предложений, но не все, потому что оригинальное приложение требует определенных действий. После изучения путей к GC root в MAT обнаруживается, что SavedState имеет указатель на набор, который (после многих многих ссылок) приводит к EditText, который указывает на Activity. После удаления EditText проблема с памятью исчезнет. Честно говоря, для меня это еще не имеет смысла. Но продолжайте исследовать, пока не получите более удовлетворительный ответ. – Ahui

+0

Возможно, я был введен в заблуждение, когда думал, что утечка вызвана EditText. Все еще не нашли объяснения. Но продолжит работать над этим. – Ahui

+0

Большое спасибо, Мартин! После дальнейших исследований, я думаю, что проблема действительно связана с EditText. Как только я его заменил, утечки больше нет. Следующее обсуждение, похоже, тесно связано с моей проблемой. Даже пути к корню GC, показанные в MAT, очень похожи: http://stackoverflow.com/questions/14069501/edittext-causing-memory-leak?lq=1 – Ahui

Смежные вопросы