1. 程式人生 > >如何向一個Fragment傳遞引數---setArguments方法的介紹

如何向一個Fragment傳遞引數---setArguments方法的介紹

在我們平常開發中經常會用到Fragment,當我們使用Fragment時一般是通過new Fragment的構造方法來實現,如果我問你怎麼向一個Fragment傳遞引數,你是不是會首先想到通過構造方法,當面試被問到這個問題的時候我也是這麼想的,後來發現自己錯了,現在給大家講一下究竟該怎麼做。

首先我們看構造方法這種方式為什麼不行,根據Android文件說明,當一個fragment重新建立的時候,系統會再次呼叫 Fragment中的預設建構函式。 注意這裡:是 預設建構函式。
這句話更直白的意思就是:當你小心翼翼的建立了一個帶有重要引數的Fragment的之後,一旦由於什麼原因(橫豎屏切換)導致你的Fragment重新建立。——-很遺憾的告訴你,你之前傳遞的引數都不見了,因為recreate你的Fragment的時候,呼叫的是預設建構函式。

首先我們通過建構函式來傳遞引數,程式碼如下

public class MainActivity extends FragmentActivity {

    private FragmentManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        manager = getSupportFragmentManager();
        /*
         * 這裡為什麼要進行空判斷,因為在螢幕旋轉的時候,系統會執行onSaveInstanceState
         * 方法去儲存當前activity的狀態,然後activity會重建,執行onCreate方法,如果我們不判斷
         * savedInstanceState是否為空,那麼每次就會執行下面的commit操作,向Fragmnet傳遞引數,
         * 這樣引數的卻會保留下來,但是我們不應該每次都去傳遞引數。當進行了空判斷時,當Activity重建
         * 的時候,會呼叫Fragment的預設建構函式,所以我們傳遞過去的引數不能保留了。
         */
if(savedInstanceState == null){ manager.beginTransaction().replace(R.id.fl_main, new FragmentOne("params")).commit(); } } @Override protected void onSaveInstanceState(Bundle outState) { System.out.println("=========savedInstanceState "); super
.onSaveInstanceState(outState); } }
public class FragmentOne extends Fragment {

    private TextView textView;
    private String params = "default";
    public FragmentOne(){
        System.out.println("===========default constructor");
    }
    /**
     * 通過構造方法接收傳遞過來的引數
     * @param content
     */
    public FragmentOne(String content){
        params = content;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container,false);
        textView = (TextView) view.findViewById(R.id.textview);
        textView.setText(params);
        return view;
    }
}

這裡寫圖片描述

可以看到此時傳遞過來的引數已經不見了,說明通過構造方法傳遞引數是不行的。

我們看看控制檯的列印

這裡寫圖片描述

注意:
這裡我們必須寫出預設的建構函式,因為Fragment重建的時候,會呼叫預設的建構函式,也就是空引數的建構函式,如果我們只是給出了帶引數的建構函式,系統是不會為我們建立空引數的建構函式的,如果你不寫,在Fragment重建的時候就會發生下面的錯誤。

這裡寫圖片描述

接下來看看官方推薦的setArguments方法:

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.fl_main, FragmentOne.newInstance("params"))
                    .commit();
        }
    }
}
public class FragmentOne extends Fragment{
    private TextView textView;
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, null);
        textView = (TextView) view.findViewById(R.id.textview);
        if(getArguments()!=null){
            //取出儲存的值
            textView.setText(getArguments().getString("name"));
        }
        return view;
    }
    public static  FragmentOne newInstance(String text){
        FragmentOne fragmentOne = new FragmentOne();
        Bundle bundle = new Bundle();
        bundle.putString("name", text);
        //fragment儲存引數,傳入一個Bundle物件
        fragmentOne.setArguments(bundle);
        return fragmentOne;
    }
}

這裡寫圖片描述

可以看到,螢幕旋轉以後引數也保留下來了。

接下來我們通過原始碼看看Bundle這個引數到底如何保留下來的,
點進去Fragment的setArguments方法:

  public void setArguments(Bundle args) {
        if (mIndex >= 0) {
            throw new IllegalStateException("Fragment already active");
        }
        mArguments = args;
    }

首先將當前的bundle物件賦值給一個全域性的mArguments物件,mArguments時FragmentState物件的一個屬性,FragmentState時Fragment的一個內部類,代表著Fragment的狀態。

final class FragmentState implements Parcelable {
    final String mClassName;
    final int mIndex;
    final boolean mFromLayout;
    final int mFragmentId;
    final int mContainerId;
    final String mTag;
    final boolean mRetainInstance;
    final boolean mDetached;
    final Bundle mArguments;
    final boolean mHidden;

    Bundle mSavedFragmentState;

    Fragment mInstance;

    public FragmentState(Fragment frag) {
        mClassName = frag.getClass().getName();
        mIndex = frag.mIndex;
        mFromLayout = frag.mFromLayout;
        mFragmentId = frag.mFragmentId;
        mContainerId = frag.mContainerId;
        mTag = frag.mTag;
        mRetainInstance = frag.mRetainInstance;
        mDetached = frag.mDetached;
        mArguments = frag.mArguments;
        mHidden = frag.mHidden;
    }

然後Activity儲存狀態的時候會呼叫onSaveInstanceState方法

protected void onSaveInstanceState(Bundle outState) {
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
        // 呼叫saveAllState方法儲存Fragment狀態
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            // 將結果儲存到Bundle中
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        if (mAutoFillResetNeeded) {
            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
            getAutofillManager().onSaveInstanceState(outState);
        }
        getApplication().dispatchActivitySaveInstanceState(this, outState);
    }

saveAllState方法最終呼叫到FragmentManager的saveAllState方法中

Parcelable saveAllState() {     
        // 找到所有的存活的Fragment
        int N = mActive.size();
        // 代表Fragment狀態的陣列
        FragmentState[] active = new FragmentState[N];
        boolean haveFragments = false;
        for (int i=0; i<N; i++) {
            Fragment f = mActive.valueAt(i);
            if (f != null) {
                if (f.mIndex < 0) {
                    throwException(new IllegalStateException(
                            "Failure saving state: active " + f
                            + " has cleared index: " + f.mIndex));
                }

                haveFragments = true;
                // 找到所有的Fragment,為FragmentState陣列初始化
                FragmentState fs = new FragmentState(f);
                active[i] = fs;
                // 保證Fragment已經建立了並且沒有引數儲存過
                if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                // 儲存Fragment的狀態
                    fs.mSavedFragmentState = saveFragmentBasicState(f);
        FragmentManagerState fms = new FragmentManagerState();
        // active是上面代表Fragment狀態的陣列,至此,fragment的狀態就被儲存到
        了FragmentManagerState中
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        fms.mNextFragmentIndex = mNextFragmentIndex;
        if (mPrimaryNav != null) {
            fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
        }
        saveNonConfig();
        return fms;
    }

接下來我們看看恢復資料的流程

在Activity的onCreate中有下面的程式碼

// 取出之前儲存的資料
 Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                    ? mLastNonConfigurationInstances.fragments : null);

最終呼叫到了FragmentManager的restoreAllState方法

void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
        if (state == null) return;
           // 取出儲存的資料
        FragmentManagerState fms = (FragmentManagerState)state;
        if (fms.mActive == null) return;

       // ... 省去部分程式碼
        // Build the full list of active fragments, instantiating them from
        // their saved state.
        // 根據之前儲存的狀態初始化新的Fragment
        mActive = new SparseArray<>(fms.mActive.length);
        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);
                }
                // 呼叫instantiate方法建立Fragment
                Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
                if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                mActive.put(f.mIndex, 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;
            }
        }
public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
            Fragment parent, FragmentManagerNonConfig childNonConfig) {
        if (mInstance == null) {
            final Context context = host.getContext();
            if (mArguments != null) {
                mArguments.setClassLoader(context.getClassLoader());
            }
            // 初始化Fragment,把之前儲存的引數傳過去
            if (container != null) {
                mInstance = container.instantiate(context, mClassName, mArguments);
            } else {
                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;
    }

// 初始化的方法

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無引數的建構函式
            Fragment f = (Fragment) clazz.getConstructor().newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                // 設定引數,然後我們就可以用getArgument方法獲取了
                f.setArguments(args);
            }
            return f;

注意:
setArguments方法的呼叫必須要在Fragment與Activity關聯之前。
這句話可以這樣理解,setArgument方法的使用必須要在FragmentTransaction 的commit之前使用。

相關推薦

如何一個Fragment傳遞引數---setArguments方法介紹

在我們平常開發中經常會用到Fragment,當我們使用Fragment時一般是通過new Fragment的構造方法來實現,如果我問你怎麼向一個Fragment傳遞引數,你是不是會首先想到通過構造方法,當面試被問到這個問題的時候我也是這麼想的,後來發現自己錯了,

python指令碼傳遞引數方法

需要模組:sys 引數個數:len(sys.argv) 指令碼名:    sys.argv[0] 引數1:     sys.argv[1] 引數2:     sys.argv[2] 引數列表:sys.argv[1:] 下面通過示例程式碼及操作來說明引數傳遞的具體使用。示例1

從java層jni中傳遞GLSurfaceView的方法

bool 很多 code http codec android class ble extern 從java朝jni中傳遞各種數據,是在android開發中經常需要面對的事情。對於一些典型的數據類型,網上已經有很多文章介紹,這裏列出一些數據類型: 對於GLSurfa

H5頁面小程式傳遞引數

H5頁面 js; <script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script> $(function () { //小程式傳送資訊 wx.miniProg

ansible命令列傳遞引數方法

    在命令列裡面傳值得的方法: ansible-playbook testyml --extra-vars "hosts=vm-1 user=root"   還可以用json格式傳遞引數: ansible-playbook test.yml

Java中eclipse與命令列main函式傳遞引數

  我們知道main函式是java程式的入口,main函式的引數型別是String[]。 1.Eclipse中向main方法傳遞引數 例如: public class Mytest { public static void main(String[] args) {

微信小程式點選事件傳遞引數方法

小程式在元件上繫結事件後,傳遞引數的方式不同於前端開發其他場景中直接加引數的方式,小程式在引數的傳遞時,採用事件物件的自定義屬性的方式,具體實現如下: wxml: <view bindtap="passQuery" data-index="1">點選事件傳參</view&g

一個Controller傳遞引數到另一個Controller(addFlashAttribute)

搞了半小時才成功 /** * 線上支付:整理支付單資訊 * @param request * @param response * @param attr * @return paymentbill */ @RequestMapping(value = "paymentbill

關於Mybatis的@Param註解 及 mybatis Mapper中各種傳遞引數方法

  原文:https://blog.csdn.net/mrqiang9001/article/details/79520436 關於Mybatis的@Param註解   Mybatis 作為一個輕量級的資料持久化框架,目前(2018)的應用非常廣泛,基本可以取代Hiberna

父元件子元件傳遞引數的demo (元件通過區域性定義)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head&

在VS中命令列新增引數方法

在VS中向命令列新增引數,即向main()函式傳遞引數的方法: 右鍵單擊要 新增引數的工程-->屬性-->配置屬性-->除錯,在右側“命令引數”欄輸入要新增的引數,各引數間用空格分離。例如: "-hide_banner"  "Z:/media/subti

[SSM]Spring MVC3在controller和檢視之間傳遞引數方法

Spring MVC3在controller和檢視之間傳遞引數的方法: 一, 從controller往檢視傳遞值, controller—->檢視 1)簡單型別,如int, String,直接寫在controller方法的引數裡,是無法傳遞到檢視頁面上

如何 Docker 容器傳遞引數

我們在執行 docker 映象時希望能用下面的命令向容器傳遞命令列引數 docker run <image-name> <command> arg1 arg2 docker run <image-name> arg1 arg2

四種傳遞引數方法

主程式在呼叫子程式時,往往要向子程式傳遞一些引數;同樣,子程式執行後也經常要把一些結果引數傳回給主程式。主程式與子程式之間的這種資訊傳遞稱為引數傳遞。  引數傳遞有四種方法:暫存器引數傳遞,約定儲存單元引數傳遞,利用CALL後續區進行引數傳遞,利用堆疊進行引數傳遞。 一、

Vue2.0中子元件父元件傳遞資料的方法,以完整demo演示

子元件child.vue原始碼:<template> <div class="child"> <button @click="sendData">點擊向父元件傳資料</button> </div> &

Java程式碼:呼叫外部介面(使用Json格式傳遞引數)的方法

程式碼如下: String url="所給外部介面的url"; //建立連線物件 HttpClient httpClient = new HttpClient(); //建立請求

shell呼叫python指令碼,並且python指令碼傳遞引數

shell中: python test.py $para1 $para2 python中: import sys def main($canshu1, $canshu2)   ..... ma

JSP中四種傳遞引數方法

今天老師講了jsp中四種傳遞引數的方法,我覺得總結一下,挺好的,以備後用! 1、form表單 2、request.setAttribute();和request.getAttribute(); 3、超連結:<a herf="index.jsp"?a=a&b=b

Spring MVC3在controller和檢視之間傳遞引數方法

一, 從controller往檢視傳遞值,controller—->檢視 1)簡單型別,如int, String,直接寫在controller方法的引數裡,是無法傳遞到檢視頁面上的(經測試) 2)可以用Map,其鍵值可以在頁面上用EL表示式${鍵值名}

Fragment傳遞引數 —— FragmentArgumentsSupport

/** * Demonstrates a fragment that can be configured through both Bundle arguments * and layout attributes. */ //展示了一個可以通過屬性和budle來配置f