【Android】為什么要用newInstance來實(shí)例化Fragment

Android日常研發(fā)中不可避免的肯定要用到Fragment,你如何使用的呢?Compare the two methods of use,是否覺得第二種更加簡潔。這時很多人肯定提出疑問:這兩種使用方式有何區(qū)別,我的代碼中到底使用哪種方式更好一些,以及為什么要使用這種方式 and so on,各位看官稍安勿躁,且聽老衲娓娓道來。

Usage 1:

@Override
    public void initView(Bundle savedInstanceState) {
        BlankFragment mFragment = new BlankFragment();
        Bundle bundle = new Bundle();
        bundle.putString("arg1", "a");
        bundle.putString("arg2", "b");
        bundle.putString("arg3", "c");
        mFragment.setArguments(bundle);
        getFragmentManager().beginTransaction().replace(R.id.frame, mFragment).commit();
    }

Usage 2:

@Override
    public void initView(Bundle savedInstanceState) {
        getFragmentManager().beginTransaction().replace(R.id.frame, BlankFragment.newInstance("a", "b")).commit();
    }

首先我們新建一個fragment,我們一起來看一下android建議的fragment如何編寫(請嚴(yán)格按照截圖的來步步創(chuàng)建哦)

Create BlankFragment.png

package com.itbird.utils;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.itbird.base.R;

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link BlankFragment.OnFragmentInteractionListener} interface
 * to handle interaction events.
 * Use the {@link BlankFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class BlankFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;

    private OnFragmentInteractionListener mListener;

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     * <p>
     * See the Android Training lesson <a href=
     * "http://developer.android.com/training/basics/fragments/communicating.html"
     * >Communicating with Other Fragments</a> for more information.
     */
    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

上述代碼其實(shí)就是在一個Fragment的newInstance方法中傳遞兩個參數(shù),并且通過fragment.setArgument保存在它自己身上,而后通過onCreate()調(diào)用的時候?qū)⑦@些參數(shù)取出來。這樣寫沒什么特殊的啊,不就是用靜態(tài)工廠方法傳個參數(shù)么,用構(gòu)造器傳參數(shù)不一樣處理么?No,No,No,如果僅僅是個靜態(tài)工廠而已,又怎么能成為谷歌推薦呢。

實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),我們一起通過一個樣例來實(shí)際操作一番
fragment_main.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">

    <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>
fragment_main.png

由圖和代碼可知,我們在xml中定義兩個FrameLayout,平分整個屏幕高度

MainActivity.java

package com.itbird.myapplication;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;


public class MainActivity extends BaseActivity {

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

BlankFragment.java

package com.itbird.myapplication;

import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class BlankFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;


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

    @SuppressLint("ValidFragment")
    public BlankFragment(String mParam1, String mParam2) {
        this.mParam1 = mParam1;
        this.mParam2 = mParam2;
    }
    /**
     * 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);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_blank, container, false);
        TextView textView = view.findViewById(R.id.text);
        textView.setText(mParam1 + mParam2);
        return view;
    }
}

fragment_blank.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" />

</FrameLayout>

通過閱讀代碼可知,我們通過兩種不同的方式創(chuàng)建fragment,同樣在其中心textview中展示相應(yīng)拼接字段。

運(yùn)行效果.png

嗯,效果如預(yù)期的一樣完美,此時,我們把屏幕橫過來,看看會出現(xiàn)怎樣的狀況

橫屏?xí)r.png

My god,頂部的fragment 文本內(nèi)容咋都變成null了。。。
我們來分析一下產(chǎn)生上述情況的原因:當(dāng)我們橫豎屏切換的時候,activity會重建,相應(yīng)的,依附于它上面的Fragment也會重新創(chuàng)建。好,順著這個思路,進(jìn)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);
        }
        mFragments.dispatchCreate();
        getApplication().dispatchActivityCreated(this, savedInstanceState);
        if (mVoiceInteractor != null) {
            mVoiceInteractor.attachActivity(this);
        }
        mCalled = true;
    }

顯而易見,fragment的重建是在restoreAllState方法中,跟進(jìn)
FragmentController.java

 /**
     * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
     * instances retained across configuration changes, including nested fragments
     *
     * @see #retainNestedNonConfig()
     */
    public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
        mHost.mFragmentManager.restoreAllState(state, nonConfig);
    }

繼續(xù)跟進(jìn)
FragmentManager.java

 void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
        // If there is no saved state at all, then there can not be
        // any nonConfig fragments either, so that is that.
        if (state == null) return;
        FragmentManagerState fms = (FragmentManagerState)state;
        if (fms.mActive == null) return;

        List<FragmentManagerNonConfig> childNonConfigs = null;

        // First re-attach any non-config instances we are retaining back
        // to their saved state, so we don't try to instantiate them again.
        ...
        
        // Build the full list of active fragments, instantiating them from
        // their saved state.
        mActive = new ArrayList<>(fms.mActive.length);
        if (mAvailIndices != null) {
            mAvailIndices.clear();
        }
        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<>();
                }
                if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
                mAvailIndices.add(i);
            }
        }
        
        // Update the target of all retained fragments.
       ...

        // Build the list of currently added fragments.
       ...
        
        // Build the back stack.
        ...
    }

通過閱讀, 找到關(guān)鍵代碼

 Fragment f = fs.instantiate(mHost, mParent, childNonConfig);

然后鍥而不舍跟進(jìn)
FragmentManager.java

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

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

跟進(jìn)到這里,終于有點(diǎn)頭緒了,至少看到fragment實(shí)例化的地方了,迫不及待的再次點(diǎn)擊去view一下下
Fragment.java

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);
                if (!Fragment.class.isAssignableFrom(clazz)) {
                    throw new InstantiationException("Trying to instantiate a class " + fname
                            + " that is not a Fragment", new ClassCastException());
                }
                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);
        }
    }

山重水復(fù)疑無路,柳暗花明又一村
原來Fragment對象被反射創(chuàng)建之后,會調(diào)用這么一句代碼
f.mArguments = args;
哦,なるほど(原來如此),F(xiàn)ragment在重新創(chuàng)建的時候只會調(diào)用無參的構(gòu)造方法,并且如果之前通過fragment.setArguments(bundle)這種方式設(shè)置過參數(shù)的話,F(xiàn)ragment重建時會得到這些參數(shù),所以,在onCreate中我們可以通過getArguments()的方式拿到我們之前設(shè)置的參數(shù)。同時由于Fragment在重建時并不會調(diào)用我們自定義的帶參數(shù)的構(gòu)造方法,所以我們傳遞的參數(shù)它也就獲取不到了。

也許有網(wǎng)友依然會繼續(xù)追問,重新set時,mArguments確定不會為空嗎?Fragment銷毀時,這個變量不會置空嗎?我們通過源碼看一下:
Fragment.java

 /**
     * Called when the view previously created by {@link #onCreateView} has
     * been detached from the fragment.  The next time the fragment needs
     * to be displayed, a new view will be created.  This is called
     * after {@link #onStop()} and before {@link #onDestroy()}.  It is called
     * <em>regardless</em> of whether {@link #onCreateView} returned a
     * non-null view.  Internally it is called after the view's state has
     * been saved but before it has been removed from its parent.
     */
    @CallSuper
    public void onDestroyView() {
        mCalled = true;
    }

    /**
     * Called when the fragment is no longer in use.  This is called
     * after {@link #onStop()} and before {@link #onDetach()}.
     */
    @CallSuper
    public void onDestroy() {
        mCalled = true;
        //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
        //        + " mLoaderManager=" + mLoaderManager);
        if (!mCheckedForLoaderManager) {
            mCheckedForLoaderManager = true;
            mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false);
        }
        if (mLoaderManager != null) {
            mLoaderManager.doDestroy();
        }
    }

看到此處,相信各位看官已經(jīng)有“了然大明白”的感覺了,我就不再多說了。

總結(jié)

1.通過對比兩種使用方式,我們知道兩種方式別無其他,只是事關(guān)風(fēng)格而已(代碼”整”“潔”之道)
2.使用Fragment過程中在涉及到傳參時,千萬不要通過構(gòu)造方法或者setParam方式直接賦值傳入?yún)?shù),必須使用setArguments來傳參,否則程序在某些應(yīng)用情景下,會丟參
強(qiáng)烈建議:兩者雖無嚴(yán)格的對錯之分,都可以使用,但是newInstance方式無論從代碼整潔之道還是程序規(guī)范的穩(wěn)定性而言,都是每個程序員應(yīng)該學(xué)習(xí)使用的方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 她是個騙子,除了愛情幾乎什么都騙。不過男人用不著有什么擔(dān)心的,反正天天帶著塊兒“一團(tuán)和氣”,有哪個男人不“買單...
    龍瞳鬼佑閱讀 228評論 0 0
  • 示例代碼 給出方法test: 使用如下代碼去測試test方法: 則控制臺輸出如下內(nèi)容: 規(guī)則 方法中使用到的變量的...
    _Dansh閱讀 246評論 0 0
  • Spring Web MVC的處理器攔截器類似于Servlet開發(fā)中的過濾器Filter,用于對處理器進(jìn)行預(yù)處理和...
    H_Man閱讀 342評論 0 6
  • 在清單四中提到,腦子里一次只裝一件事,那么到底該裝什么事呢?裝的就是下一步行動。 001 下一步行動 行動就是可執(zhí)...
    我是硬柿子閱讀 191評論 0 0
  • 第一階段,隨著音樂,梵音,吟誦經(jīng)文一直在耳邊縈繞,生活中的畫面像放電影一樣,一個,一個的閃過!有和辟谷老師在一起的...
    jessica的記憶閱讀 260評論 0 0

友情鏈接更多精彩內(nèi)容