Android 从源码的角度分析——为什么要用newInstance来实例化Fragment

最近在看Google技术文档的时候发现了一种新的方式来实例化Fragment,就是采用静态工厂的方式创建Fragment。我们在使用Android studio创建一个类的时候,选择New ->Fragment->Fragment(Blank)可以很直观的看到这种方式的写法:

public class BlankFragment extends Fragment {
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    private String mParam1;
    private String mParam2;

    public BlankFragment() {
        // Required empty public constructor
    }

    /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment BlankFragment. */
    // TODO: Rename and change types and number of parameters
    public static BlankFragment newInstance(String param1, String param2) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }
}

上述代码其实就是在一个Fragment的newInstance方法中传递两个参数,并且通过fragment.setArgument保存在它自己身上,而后通过onCreate()调用的时候将这些参数取出来。

可能有人乍一看,这样写没什么特殊的啊,不就是用静态工厂方法传个参数么,用构造器传参数不一样处理么?No,No,No,如果仅仅是个静态工厂而已,又怎么能成为谷歌推荐呢。

我们先来看一个小例子:

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >
    <FrameLayout  android:id="@+id/layout_top" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/>
    <FrameLayout  android:id="@+id/layout_bottom" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/>
</LinearLayout>

在xml中定义两个FrameLayout,平分整个屏幕高度。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(savedInstanceState == null){
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.add(R.id.layout_top,new TopFragment("顶部的Fragment"));
        transaction.add(R.id.layout_bottom,BottomFragment.newInstance("底部的Fragment"));
        transaction.commit();
        }
    }

在activity中采用两种不同的方式来实例化Fragment,顶部的Fragment通过构造方法将参数传递给它,而底部的Fragment通过newInstance的方式实例化并传参。两个Fragment的代码如下所示:

public class TopFragment extends Fragment {
    private String mTop = "啥也没有";
    public TopFragment(){
    }
    public TopFragment(String top) {
        this.mTop = top;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView tv = new TextView(getActivity());
        tv.setText(mTop);
        tv.setGravity(Gravity.CENTER);
        tv.setTextColor(Color.RED);
        tv.setTextSize(25);
        return tv;
    }
}
public class BottomFragment extends Fragment {
    private String mBottom = "啥也没有";
    public static BottomFragment newInstance(String bottom) {
        BottomFragment fragment = new BottomFragment();
        Bundle bundle = new Bundle();
        bundle.putString("bottom",bottom);
        fragment.setArguments(bundle);
        return fragment;
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(getArguments() != null){
            mBottom = getArguments().getString("bottom");
        }
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView tv = new TextView(getActivity());
        tv.setText(mBottom);
        tv.setGravity(Gravity.CENTER);
        tv.setTextColor(Color.RED);
        tv.setTextSize(25);
        return tv;
    }
}

在两个Fragment第一行都写一个相同的默认参数:“啥也没有”,ok,运行工程:

嗯,没毛病,两个Fragment都顺利的接收到来自activity的数据。然后我们把屏幕横过来,看看会出现怎样的状况:

咦。。。顶部的Fragment的数据呢?为什么只显示默认的数据?activity给它传过去的数据哪去了呢?

我们来分析一下产生上述情况的原因:当我们横竖屏切换的时候,activity会重建,相应的,依附于它上面的Fragment也会重新创建。好,顺着这个思路,进activity的onCreate方法中看看:

protected void onCreate(@Nullable Bundle savedInstanceState) {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
        if (mLastNonConfigurationInstances != null) {
            mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
        }
        if (mActivityInfo.parentActivityName != null) {
            if (mActionBar == null) {
                mEnableDefaultActionBarUp = true;
            } else {
                mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
            }
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                    ? mLastNonConfigurationInstances.fragments : null);//这里,会恢复所有Fragment的状态
        }
        mFragments.dispatchCreate();
        getApplication().dispatchActivityCreated(this, savedInstanceState);
        if (mVoiceInteractor != null) {
            mVoiceInteractor.attachActivity(this);
        }
        mCalled = true;
    }

显而易见,所有Fragment的状态恢复应该是在mFragments.restoreAllState()这个方法,跟进去看看:

public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
        mHost.mFragmentManager.restoreAllState(state, nonConfigList);
}

找到FragmentManager这个类,查看它的restoreAllState方法:

void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
    ...
    for (int i=0; i<fms.mActive.length; i++) {
            FragmentState fs = fms.mActive[i];
            if (fs != null) {
                FragmentManagerNonConfig childNonConfig = null;
                if (childNonConfigs != null && i < childNonConfigs.size()) {
                    childNonConfig = childNonConfigs.get(i);
                }
                Fragment f = fs.instantiate(mHost, mParent, childNonConfig);//实例化
                if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                mActive.add(f);
                // Now that the fragment is instantiated (or came from being
                // retained above), clear mInstance in case we end up re-restoring
                // from this FragmentState again.
                fs.mInstance = null;
            } else {
                mActive.add(null);
                if (mAvailIndices == null) {
                    mAvailIndices = new ArrayList<Integer>();
                }
                if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
                mAvailIndices.add(i);
            }
        }
        ...
}

寻找关键代码,我们发现了Fragment f = fs.instantiate(mHost, mParent, childNonConfig);这句,这里应该是Fragment重新实例化的地方了吧,赶紧点进去瞧瞧:

public Fragment instantiate(FragmentHostCallback host, Fragment parent,
            FragmentManagerNonConfig childNonConfig) {
        if (mInstance == null) {
            final Context context = host.getContext();
            if (mArguments != null) {
                mArguments.setClassLoader(context.getClassLoader());
            }

            mInstance = Fragment.instantiate(context, mClassName, mArguments);//创建Fragment对象的地方

            if (mSavedFragmentState != null) {
                mSavedFragmentState.setClassLoader(context.getClassLoader());
                mInstance.mSavedFragmentState = mSavedFragmentState;
            }
            mInstance.setIndex(mIndex, parent);
            mInstance.mFromLayout = mFromLayout;
            mInstance.mRestored = true;
            mInstance.mFragmentId = mFragmentId;
            mInstance.mContainerId = mContainerId;
            mInstance.mTag = mTag;
            mInstance.mRetainInstance = mRetainInstance;
            mInstance.mDetached = mDetached;
            mInstance.mHidden = mHidden;
            mInstance.mFragmentManager = host.mFragmentManager;

            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
                    "Instantiated fragment " + mInstance);
        }
        mInstance.mChildNonConfig = childNonConfig;
        return mInstance;
    }

继续跟进mInstance = Fragment.instantiate(context, mClassName, mArguments);看看里面的真正实现:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = args;//将之前设置的参数保存在自己身上
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        }
    }

终于走到了Fragment最终被实例化创建的地方,我们可以看到Fragment对象被反射创建之后,会调用这么一句代码:f.mArguments = args; 哦,原来如此,Fragment在重新创建的时候只会调用无参的构造方法,并且如果之前通过fragment.setArguments(bundle)这种方式设置过参数的话,Fragment重建时会得到这些参数,所以,在onCreate中我们可以通过getArguments()的方式拿到我们之前设置的参数。同时由于Fragment在重建时并不会调用我们自定义的带参数的构造方法,所以我们传递的参数它也就获取不到了,这就是为什么会出现上述情况的原因。

细心的童鞋可以发现,上面的代码在catch语句当中抛出了几个异常,意思是:在Fragment重建过程中,确保你的Fragment的类是public的,并且带有一个public的空参的构造器,否则就让你崩溃~~~

好了,抛弃之前那些不好的代码习惯吧,支持谷歌,拥抱变化。

你可能感兴趣的:(源码,android,实例)