我写了一个图书阅读器应用程序,它使用带有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);
}
}
}
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开发人员的文档似乎并没有鼓励这种转变。也许有更好的方法来做到这一点。任何新的见解或专家建议都将受到欢迎!
答案 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();
。
所以这些都是我用这种方式做的事情,如果你看到任何变化,请尝试告诉我。