Android-Fragment中TextView.setFocusable(true)导致的内存泄露
2014-07-29 16:43
375 查看
转载请标明出处:/article/1907999.html
问题是这样的,页面中有EditText,为了让EditText失去焦点,只能让页面上的一个TextView获取焦点,因此设置了某个TextView的focusable和focusable都是true。但是很悲剧的是竟然出了内存泄露!复现代码:
MainActivity.java
main.xml:
FooFragment.java
ThirdFragment.java:
fragment_third.xml:
MainActivity首先是显示FooFragment,然后点击跳转到BarFragment,然后点击跳转到ThirdFragment,然后点击back->back->回到FooFragment,这个时候BarFragment和ThirdFragment肯定已经是被destroy了,控制台有输出。
假如在BarFragment的onDestroyView()中不调用fixInputMethodManager();和fixInputEventReceiver();,dump内存文件:
然后参考:http://stackoverflow.com/questions/5038158/main-activity-is-not-garbage-collected-after-destruction-because-it-is-reference/23889598#23889598
加入:fixInputMethodManager();
结果还是有:
同样的道理,继续反射之,加入:fixInputEventReceiver();终于不再泄露了。
看上去,TextView设置为focusable=true以后,InputMethodManager会把它记录为当前获取焦点的view,mNextServedView应该是点击next的时候获取焦点的view,但是,在Fragment销毁的时候,并没有通知InputMethodManager去删掉view的引用。WindowInputEventReceiver就比较坑爹了,它是ViewRootImpl的内部类,直接持有对外部类的引用,导致ViewRootImpl没有释放,而ViewRootImpl也会记录当前获取焦点的view,同样在fragment销毁的时候,引用没有被销毁!
问题是这样的,页面中有EditText,为了让EditText失去焦点,只能让页面上的一个TextView获取焦点,因此设置了某个TextView的focusable和focusable都是true。但是很悲剧的是竟然出了内存泄露!复现代码:
MainActivity.java
public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); openFragmentFoo(); } public void openFragmentFoo(){ FragmentManager m = getSupportFragmentManager(); FragmentTransaction ft = m.beginTransaction(); ft.replace(R.id.fragment_container, new FooFragment()); ft.commit(); } public void openFragmentBar(){ FragmentManager m = getSupportFragmentManager(); FragmentTransaction ft = m.beginTransaction(); ft.replace(R.id.fragment_container, new BarFragment()); ft.addToBackStack(null); ft.commit(); } public void openFragmentThird(){ FragmentManager m = getSupportFragmentManager(); FragmentTransaction ft = m.beginTransaction(); ft.replace(R.id.fragment_container, new ThirdFragment()); ft.addToBackStack(null); ft.commit(); } }
main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent"> /> </FrameLayout>
FooFragment.java
public class FooFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_foo, container, false); Button btn = (Button)view.findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { MainActivity main = (MainActivity)getActivity(); main.openFragmentBar(); } }); return view; } }fragment_foo.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="foo" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" /> </LinearLayout>BarFragment.java:
public class BarFragment extends Fragment { private static final String tag = "BarFragment"; private TextView textview2; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_bar, container, false); textview2 = (TextView) view.findViewById(R.id.textView2); <span style="color:#ff0000;">textview2.requestFocus();</span> Button btn = (Button) view.findViewById(R.id.button2); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { MainActivity main = (MainActivity) getActivity(); main.openFragmentThird(); } }); return view; } @Override public void onDestroyView() { // http://stackoverflow.com/questions/5038158/main-activity-is-not-garbage-collected-after-destruction-because-it-is-reference/23889598#23889598 <span style="color:#ff0000;">fixInputMethodManager();</span> // ViewrootImpl:http://blog.csdn.net/gemmem/article/details/9967295 <span style="color:#ff6666;">fixInputEventReceiver();</span> super.onDestroyView(); } @Override public void onDestroy() { super.onDestroy(); Log.e(tag, "BarFragment onDestroy"); } @Override public void onDetach() { super.onDetach(); Log.e(tag, "BarFragment onDetach"); } private void fixInputEventReceiver() { View rootView = textview2.getRootView();//这个是PhoneWindow$DecorView ViewParent viewRootImpl = rootView.getParent(); TypedObject param = new TypedObject(rootView, View.class); invokeMethodExceptionSafe(viewRootImpl, "clearChildFocus", param); } private void fixInputMethodManager() { final InputMethodManager imm = (InputMethodManager)this.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); final TypedObject windowToken = new TypedObject(this.getActivity().getWindow().getDecorView().getWindowToken(), IBinder.class); invokeMethodExceptionSafe(imm, "windowDismissed", windowToken); final TypedObject view = new TypedObject(null,View.class); invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view); } public static final class TypedObject { private final Object object; private final Class<?> type; public TypedObject(final Object object, final Class<?> type) { this.object = object; this.type = type; } Object getObject() { return object; } Class<?> getType() { return type; } } public static void invokeMethodExceptionSafe(final Object methodOwner,final String method, final TypedObject... arguments) { if (null == methodOwner) { return; } try { final Class<?>[] types = null == arguments ? new Class[0]: new Class[arguments.length]; final Object[] objects = null == arguments ? new Object[0]: new Object[arguments.length]; if (null != arguments) { for (int i = 0, limit = types.length; i < limit; i++) { types[i] = arguments[i].getType(); objects[i] = arguments[i].getObject(); } } final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types); declaredMethod.setAccessible(true); declaredMethod.invoke(methodOwner, objects); } catch (final Throwable ignored) { } } }fragment_bar.xml:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="100dp" android:text="bar" <span style="color:#ff0000;">android:focusable="true" android:focusableInTouchMode="true"</span>/> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="100dp" android:text="button2" /> </LinearLayout>
ThirdFragment.java:
public class ThirdFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_third, container, false); return view; } }
fragment_third.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="third" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button3" /> </LinearLayout>
MainActivity首先是显示FooFragment,然后点击跳转到BarFragment,然后点击跳转到ThirdFragment,然后点击back->back->回到FooFragment,这个时候BarFragment和ThirdFragment肯定已经是被destroy了,控制台有输出。
假如在BarFragment的onDestroyView()中不调用fixInputMethodManager();和fixInputEventReceiver();,dump内存文件:
然后参考:http://stackoverflow.com/questions/5038158/main-activity-is-not-garbage-collected-after-destruction-because-it-is-reference/23889598#23889598
加入:fixInputMethodManager();
结果还是有:
同样的道理,继续反射之,加入:fixInputEventReceiver();终于不再泄露了。
看上去,TextView设置为focusable=true以后,InputMethodManager会把它记录为当前获取焦点的view,mNextServedView应该是点击next的时候获取焦点的view,但是,在Fragment销毁的时候,并没有通知InputMethodManager去删掉view的引用。WindowInputEventReceiver就比较坑爹了,它是ViewRootImpl的内部类,直接持有对外部类的引用,导致ViewRootImpl没有释放,而ViewRootImpl也会记录当前获取焦点的view,同样在fragment销毁的时候,引用没有被销毁!
相关文章推荐
- Android-Fragment中TextView.setFocusable(true)导致的内存泄露
- android 退出方案 导致内存泄露
- Android中Handler导致的内存泄露
- Android 非静态内部类导致的内存泄露(非static内部类)
- [置顶] android内存泄漏总结(总结所有导致内存泄露的可能性及解决方案)
- android 退出方案 导致内存泄露
- Android InputMethodManager 导致的内存泄露
- Android开发,中可能会导致内存泄露的问题
- Android InputMethodManager 导致的内存泄露及解决方案
- Android中Handler使用不当导致内存泄露的问题
- Android 中 Handler 引起的内存泄露 在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何
- 【转】android开发中,可能会导致内存泄露的问题
- Android内存溢出与优化(四)——防止Handler导致的内存泄露
- Android开发 单例模式导致内存泄露
- Android开发编码规范导致的内存泄露问题
- android开发中,可能会导致内存泄露的问题
- Android融云使用不当产生的内存泄露和BUG(匿名类回调导致Activity内存泄露)
- Android Handler当做内部类,导致内存泄露的问题解决方案
- android开发中,可能会导致内存泄露的问题
- Android InputMethodManager 导致的内存泄露及解决方案