Android-Fragment中TextView.setFocusable(true)导致的内存泄露

来源:互联网 发布:广州网络远程教育 编辑:程序博客网 时间:2024/05/22 10:39

转载自:http://blog.csdn.net/goldenfish1919/article/details/38272305

转载请标明出处:http://blog.csdn.net/goldenfish1919/article/details/38272305

问题是这样的,页面中有EditText,为了让EditText失去焦点,只能让页面上的一个TextView获取焦点,因此设置了某个TextView的focusable和focusable都是true。但是很悲剧的是竟然出了内存泄露!复现代码:


MainActivity.java

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class MainActivity extends FragmentActivity {  
  2.       
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.main);  
  7.         openFragmentFoo();  
  8.     }  
  9.   
  10.     public void openFragmentFoo(){  
  11.         FragmentManager m = getSupportFragmentManager();  
  12.         FragmentTransaction ft = m.beginTransaction();  
  13.         ft.replace(R.id.fragment_container, new FooFragment());  
  14.         ft.commit();  
  15.     }  
  16.       
  17.     public void openFragmentBar(){  
  18.         FragmentManager m = getSupportFragmentManager();  
  19.         FragmentTransaction ft = m.beginTransaction();  
  20.         ft.replace(R.id.fragment_container, new BarFragment());  
  21.         ft.addToBackStack(null);  
  22.         ft.commit();  
  23.     }  
  24.       
  25.     public void openFragmentThird(){  
  26.         FragmentManager m = getSupportFragmentManager();  
  27.         FragmentTransaction ft = m.beginTransaction();  
  28.         ft.replace(R.id.fragment_container, new ThirdFragment());  
  29.         ft.addToBackStack(null);  
  30.         ft.commit();  
  31.     }  
  32. }  

main.xml:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.        android:id="@+id/fragment_container"  
  3.        android:layout_width="match_parent"  
  4.        android:layout_height="match_parent">  
  5.         />  
  6. </FrameLayout>  

FooFragment.java

  1. public class FooFragment extends Fragment {  
  2.     @Override  
  3.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  4.       Bundle savedInstanceState) {  
  5.       View view = inflater.inflate(R.layout.fragment_foo, container, false);  
  6.   
  7.       Button btn = (Button)view.findViewById(R.id.button1);  
  8.       btn.setOnClickListener(new OnClickListener(){  
  9.             @Override  
  10.             public void onClick(View v) {  
  11.                 MainActivity main = (MainActivity)getActivity();  
  12.                 main.openFragmentBar();  
  13.             }  
  14.       });  
  15.       return view;  
  16.     }  
  17. }  

fragment_foo.xml

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"   
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.     <TextView  
  7.         android:id="@+id/textView1"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="foo" />  
  11.      <Button  
  12.         android:id="@+id/button1"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="button" />  
  16. </LinearLayout>  

arFragment.java:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class BarFragment extends Fragment {  
  2.   
  3.     private static final String tag = "BarFragment";  
  4.     private TextView textview2;  
  5.   
  6.     @Override  
  7.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  8.             Bundle savedInstanceState) {  
  9.         View view = inflater.inflate(R.layout.fragment_bar, container, false);  
  10.   
  11.         textview2 = (TextView) view.findViewById(R.id.textView2);  
  12.         <span style="color:#ff0000;">textview2.requestFocus();</span>  
  13.   
  14.         Button btn = (Button) view.findViewById(R.id.button2);  
  15.         btn.setOnClickListener(new OnClickListener() {  
  16.             @Override  
  17.             public void onClick(View v) {  
  18.                 MainActivity main = (MainActivity) getActivity();  
  19.                 main.openFragmentThird();  
  20.             }  
  21.         });  
  22.         return view;  
  23.     }  
  24.       
  25.     @Override  
  26.     public void onDestroyView() {  
  27.         // http://stackoverflow.com/questions/5038158/main-activity-is-not-garbage-collected-after-destruction-because-it-is-reference/23889598#23889598  
  28.         <span style="color:#ff0000;">fixInputMethodManager();</span>  
  29.         // ViewrootImpl:http://blog.csdn.net/gemmem/article/details/9967295  
  30.         <span style="color:#ff6666;">fixInputEventReceiver();</span>  
  31.         super.onDestroyView();  
  32.     }  
  33.       
  34.     @Override  
  35.     public void onDestroy() {  
  36.         super.onDestroy();  
  37.         Log.e(tag, "BarFragment onDestroy");  
  38.     }  
  39.     @Override  
  40.     public void onDetach() {  
  41.         super.onDetach();  
  42.         Log.e(tag, "BarFragment onDetach");  
  43.     }  
  44.       
  45.     private void fixInputEventReceiver() {  
  46.         View rootView = textview2.getRootView();//这个是PhoneWindow$DecorView  
  47.         ViewParent viewRootImpl = rootView.getParent();  
  48.         TypedObject param = new TypedObject(rootView, View.class);  
  49.         invokeMethodExceptionSafe(viewRootImpl, "clearChildFocus", param);  
  50.     }  
  51.       
  52.     private void fixInputMethodManager() {  
  53.         final InputMethodManager imm = (InputMethodManager)this.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);  
  54.         final TypedObject windowToken = new TypedObject(this.getActivity().getWindow().getDecorView().getWindowToken(), IBinder.class);  
  55.         invokeMethodExceptionSafe(imm, "windowDismissed", windowToken);  
  56.         final TypedObject view = new TypedObject(null,View.class);  
  57.         invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view);  
  58.     }  
  59.   
  60.     public static final class TypedObject {  
  61.         private final Object object;  
  62.         private final Class<?> type;  
  63.         public TypedObject(final Object object, final Class<?> type) {  
  64.             this.object = object;  
  65.             this.type = type;  
  66.         }  
  67.         Object getObject() {  
  68.             return object;  
  69.         }  
  70.         Class<?> getType() {  
  71.             return type;  
  72.         }  
  73.     }  
  74.   
  75.     public static void invokeMethodExceptionSafe(final Object methodOwner,final String method, final TypedObject... arguments) {  
  76.         if (null == methodOwner) {  
  77.             return;  
  78.         }  
  79.         try {  
  80.             final Class<?>[] types = null == arguments ? new Class[0]: new Class[arguments.length];  
  81.             final Object[] objects = null == arguments ? new Object[0]: new Object[arguments.length];  
  82.             if (null != arguments) {  
  83.                 for (int i = 0, limit = types.length; i < limit; i++) {  
  84.                     types[i] = arguments[i].getType();  
  85.                     objects[i] = arguments[i].getObject();  
  86.                 }  
  87.             }  
  88.             final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types);  
  89.             declaredMethod.setAccessible(true);  
  90.             declaredMethod.invoke(methodOwner, objects);  
  91.         } catch (final Throwable ignored) {  
  92.         }  
  93.     }  
  94. }  
fragment_bar.xml:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:id="@+id/layout"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="wrap_content"  
  5.     android:orientation="vertical" >  
  6.     <TextView  
  7.         android:id="@+id/textView2"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="100dp"  
  10.         android:text="bar"   
  11.         <span style="color:#ff0000;">android:focusable="true"  
  12.         android:focusableInTouchMode="true"</span>/>  
  13.     <Button  
  14.         android:id="@+id/button2"  
  15.         android:layout_width="wrap_content"  
  16.         android:layout_height="100dp"  
  17.         android:text="button2" />  
  18. </LinearLayout>  

ThirdFragment.java:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class ThirdFragment extends Fragment {  
  2.     @Override  
  3.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  4.       Bundle savedInstanceState) {  
  5.       View view = inflater.inflate(R.layout.fragment_third, container, false);  
  6.       return view;  
  7.     }  
  8. }  

fragment_third.xml:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"   
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.     <TextView  
  7.         android:id="@+id/textView3"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="third" />  
  11.     <Button  
  12.         android:id="@+id/button3"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="button3" />  
  16. </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销毁的时候,引用没有被销毁!



0 0
原创粉丝点击