Android的support v4中的Fragment的一個Bug
阿新 • • 發佈:2019-02-10
問題描述
public class MatchFragment extends BaseFragment {
public static final String TAG = MatchFragment.class.getSimpleName();
private FragmentManager mFragmentManager;
public MatchFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_match, container, false);
mFragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.root_frame_layout, new MatchingFashionFragment());
fragmentTransaction.commit();
return view;
}
}
當這個Fragment物件被嵌入到一個Activity中然後又被其他Fragment取代後,然後這個Fragment物件又被重新放回到Activity中時,在fragmentTransaction.commit();處會報如下的錯誤
java.lang.IllegalStateException: Activity has been destroyed
at android.support .v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1365)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at com.jd.wxsq.app.Fragment.MatchFragment.onCreateView(MatchFragment.java:70)
at android.support.v4.app.Fragment.performCreateView(Fragment.java:1500)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:927)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1467)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:440)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
at dalvik.system.NativeStart.main(Native Method)
問題原因分析
檢視android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1365)處的原始碼:
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mActivity == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mActivity.mHandler.removeCallbacks(mExecCommit);
mActivity.mHandler.post(mExecCommit);
}
}
}
在第7行,可以看到mActivity為null了,所以可以猜測Fragment被detach後,Fragment的mChildFragmentManager的mActivity變為空了,而Fragment被attach後,mChildFragmentManager的mActivity又沒有被正確的賦予Activity的物件,才造成了這個bug
在哪裡被賦值成null呢?請看下面的程式碼
C:\Users\lihuaping\AppData\Local\Android\sdk\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.0.0\support-v4-19.0.0-sources.jar!\android\support\v4\app\FragmentManager.java
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
moveToState(Fragment.INITIALIZING, false);
mActivity = null;
mContainer = null;
mParent = null;
}
所以我們要做的就是,在Fragment被detach時,把mChildFragmentManager置空就可以了,mChildFragmentManager是Fragment的私有成員,如何做到?使用反射就可以做到
在你的Fragment的程式碼中加入如下的程式碼:
@Override
public void onDetach() {
super.onDetach();
try {
Field childFragmentManager =
Fragment.class.getDeclaredField("mChildFragmentManager");
childFragmentManager.setAccessible(true);
childFragmentManager.set(this, null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}