1. 程式人生 > >Android的support v4中的Fragment的一個Bug

Android的support v4中的Fragment的一個Bug

問題描述

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();
        }
    }