查看寻呼机内存分析器

时间:2013-12-06 05:52:42

标签: android memory-leaks garbage-collection

我写了一个图书阅读器应用程序,它使用带有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

}

布局文件在LinearLayout中只包含一个textview和一个编辑文本。 (除非有要求,否则不在此处复制)。

带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

(2013年12月17日新增以下内容) 在马丁的帮助下进行了一些调查后(谢谢!),我发现有两个罪魁祸首。第一个是尚未发布的TextWatcher(TextChangedListener)。这是一个简单的解决方案。第二个罪魁祸首非常讨厌。

第二个罪魁祸首来自EditText,它仍然保留对已被销毁的活动的引用(mContext)。只有在运行Android 4.3的Nexus 7上测试应用程序时,才会出现此问题。三星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                                

实时活动之间的连接是通过每个TextView的SavedState(EditText是TextView),当视图发生变化时系统会自动保存。 (参考:http://developer.android.com/guide/components/activities.html#SavingActivityState)这个不幸的SavedState被FragmentStatePagerAdapter引用作为其工作的一部分。与侦听器不同,我不能在销毁活动时从ViewPager释放FragmentStatePagerAdapter。

我观察到这个问题不仅发生在EditText上,而且还发生在支持更改其文本格式/颜色等的TextView上。不知何故,由于TextView无法释放其mContext,我当前的复仇是明确的通过调用mNoteView.setSaveEnabled(false)来阻止它保存其状态;创建视图时。它不会伤害我的实际应用程序,因为我已经保存,每当片段恢复活动时加载/重新加载EditText的数据。但是,Android开发人员的文档似乎并没有鼓励这种转变。也许有更好的方法来做到这一点。任何新的见解或专家建议都将受到欢迎!

1 个答案:

答案 0 :(得分:0)

我不确定发生了什么,但是我会开始在一个答案中投入想法,希望你能找到问题(比评论容易得多)。

1)尝试切换到FragmentPagerAdapter(其工作方式不同)。我认为这不会有太大变化。

2)通过制作更多最终内容来改善你的片段......例如:

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

mDataView和mNoteView都是字段,它们可以是本地人吗?

尝试制作:

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处理它(也可以是一个逻辑位置的一行代码,Activity,它可以控制Window。

如果您只在这里更改DataView和EditView,并且您可以将它们设为最终,那么也要修复这些行:

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

最后,我个人改变的是你的活动......

我完全为Fragment Adapter创建了一个Inner类,而不是使用Anonymous。

创建一个字段来保存它:

ViewPagerAdapter mViewPagerAdapter;

(这将是你的内心阶层)

private class ViewPagerAdapter extends FragmentStatePagerAdapter {

然后在你的onCreate中实例化它:

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

此外,删除FragmentManager的实例,因为您似乎不再使用它,保存内存;)

如果您需要,还可以执行以下操作:mPagerAdapter.notifyDataSetChanged();

所以这些都是我用这种方式做的事情,如果你看到任何变化,请尝试告诉我。