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

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

问题是这样的,页面中有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();

结果还是有:

Android-Fragment中TextView.setFocusable(true)导致的内存泄露_第1张图片


同样的道理,继续反射之,加入:fixInputEventReceiver();终于不再泄露了。

看上去,TextView设置为focusable=true以后,InputMethodManager会把它记录为当前获取焦点的view,mNextServedView应该是点击next的时候获取焦点的view,但是,在Fragment销毁的时候,并没有通知InputMethodManager去删掉view的引用。WindowInputEventReceiver就比较坑爹了,它是ViewRootImpl的内部类,直接持有对外部类的引用,导致ViewRootImpl没有释放,而ViewRootImpl也会记录当前获取焦点的view,同样在fragment销毁的时候,引用没有被销毁!



你可能感兴趣的:(Android-Fragment中TextView.setFocusable(true)导致的内存泄露)