Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 從源碼的角度分析——為什麼要用newInstance來實例化Fragment

Android 從源碼的角度分析——為什麼要用newInstance來實例化Fragment

編輯:關於Android編程

最近在看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,如果僅僅是個靜態工廠而已,又怎麼能成為谷歌推薦呢。

我們先來看一個小例子:


    <framelayout android:id="@+id/layout_top" android:layout_height="0dp" android:layout_weight="1" android:layout_width="match_parent">
    <framelayout android:id="@+id/layout_bottom" android:layout_height="0dp" android:layout_weight="1" android:layout_width="match_parent">
</framelayout></framelayout>

在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 nonConfigList) {
        mHost.mFragmentManager.restoreAllState(state, nonConfigList);
}

找到FragmentManager這個類,查看它的restoreAllState方法:

void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
    ...
    for (int i=0; i();
                }
                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的空參的構造器,否則就讓你崩潰~~~

好了,拋棄之前那些不好的代碼習慣吧,支持谷歌,擁抱變化。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved