使用newInstance()來例項化fragment並傳遞資料操作
好問題。答案就是這篇文章的題目所建議的,這是一種合理的設計。在這種情況下,newInstance()方法是一種“靜態工廠方法",讓我們在初始化和設定一個新的fragment的時候省去呼叫它的建構函式和額外的setter方法。
為你的Fragment提供靜態工廠方法是一種好的做法,因為它封裝和抽象了在客戶端構造物件所需的步驟。
例如,考慮下面的程式碼:
public class MyFragment extends Fragment { /** * 靜態工廠方法需要一個int型的值來初始化fragment的引數, * 然後返回新的fragment到呼叫者 */ public static MyFragment newInstance(int index) { MyFragment f = new MyFragment(); Bundle args = new Bundle(); args.putInt("index",index); f.setArguments(args); return f; } }
不要讓客戶端去呼叫預設的建構函式,然後手動地設定fragment的引數。我們直接為它們提供一個靜態工廠方法。這樣做比呼叫預設構造方法好,有兩個原因:一個是,它方便別人的呼叫。另一個是,保證了fragment的構建過程不會出錯。通過提供一個靜態工廠方法,我們避免了自己犯錯--我們再也不用擔心不小心忘記初始化fragmnet的引數或者沒正確設定引數。
總的來說,雖然兩者的區別只在於設計,但是他們之間的差別非常大。因為提供靜態工廠方法有向上抽象了一個級別,讓程式碼更容易懂。
譯者注:
其實提供靜態工廠而不是使用預設建構函式或者自己定義一個有參的建構函式還有至關重要一點。fragmnet經常會被銷燬重新例項化,Android framework只會呼叫fragment無參的建構函式。在系統自動例項化fragment的過程中,你沒有辦法干預。一些需要外部傳入的引數來決定的初始化就沒有辦法完成。使用靜態工廠方法,將外部傳入的引數可以通過Fragment.setArgument儲存在它自己身上,這樣我們可以在Fragment.onCreate(...)呼叫的時候將這些引數取出來。
傳遞資料
public static LoginFragment newInstance(String param) { LoginFragment fragment = new LoginFragment(); Bundle args = new Bundle(); args.putString("name",param); fragment.setArguments(args); return fragment; }
在fragment 的onCreatView裡獲取資料
@Overridepublic View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) { // Inflate the layout for this fragment View myView = inflater.inflate(R.layout.xxx,container,false); String args = getArguments().getString("name"); return myView;}
在Activity裡
LoginFragment loginFragment= LoginFragment.newInstance(想要傳遞的引數); SignUpFragment signUpFragment= SignUpFragment.newInstance(想要傳遞的引數); List<Fragment> allFragment = new ArrayList<Fragment>(); allFragment.add(loginFragment); allFragment.add(signUpFragment);
補充知識:正確使用Fragment之建立/傳參——newInstance方法(native)
說來懺愧,近來越發覺得寫不出可分享的東西,更糟糕的是,甚至覺得可記錄的東西都不多。
這實在是一個非常糟的訊號——說明我開始逐漸把自己放在安全邊際內了。
人若總是將自己畏縮在安全邊際之內,不去做一些陣痛的改變,埋下的會是病來如山倒般的災難種子。
好在,好在我還在不斷的學習,只是但前處於一種較混沌的狀態,需要踏出去更多一步。
今天來說一個簡單的話題,找回一些狀態。
關於Fragment,相信大家已經熟之不能再熟了。然而,
使用頻率如此之高的Fragment,你的使用姿勢,真的正確嗎?
先對比一下兩種使用姿勢:
1.姿勢A:
MyFragment mFragment = new MyFragment(); Bundle bundle = new Bundle(); bundle.putString("arg1","a"); bundle.putString("arg2","b"); bundle.putString("arg3","c"); mFragment.setArguments(bundle); getSupportFragmentManager().beginTransaction().replace(R.id.frame,mFragment).commit();
2.姿勢B:
MyFragment mFragment = MyFragment.newInstance("a","b","c");
getSupportFragmentManager().beginTransaction().replace(R.id.frame,mFragment).commit();
有沒有,有沒有覺得第二種姿勢特別爽。
接來下進入今天的正題,關於Fragment.newInstance()這個方法。
我先宣告,其實第一種姿勢沒什麼問題,(引用斯坦福白鬍子老頭一句話)”這只是程式碼風格的問題,但我不建議這麼做。”
使用Android Studio新建一個Fragment就一切明瞭了:
我們看到,Studio預設幫我們建立的Fragment中,有這樣一段程式碼:
// 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; }
一個靜態方法,返回我們建立的Fragment類本身,顯而易見的是,這個方法幫我們做了姿勢A中我們手寫的方法。
再來關注看我們較少Override的方法onCreate(這裡預設直接幫我們Override了)
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); } }
到這裡,我們先捋一捋。姿勢B的機理在於,通過傳遞引數給Fragment.newInstance()方法,它會建立一個該Fragment類,並通過建立Bundle把我們的引數代入。然後在onCreate()生命週期中,把引數拿回出來。(為什麼這麼做?是本文後半部分傳參討論的內容,先跳過),之後的事情大家都是熟手了,把引數拿來用就好。
為什麼谷歌預設要使用這樣一個工廠方式建立我們的Fragment呢?
既然newInstance()是父類Fragment的方法,我們跟進去一看究竟:
//可以看到這是一個native方法
public native T newInstance() throws InstantiationException,IllegalAccessException;
題外話:關於native方法: native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前檔案,而是在用其他語言(如C和C++)實現的檔案中。Java語言本身不能對作業系統底層進行訪問和操作,但是可以通過JNI介面呼叫其他語言來實現對底層的訪問。
就感覺線索斷了一樣,這是要往下去讀C/C++啊,拋開底層機理不知,不說,姑且猜測為,二者完全是一樣的方式,只是姿勢B封裝了一點內容,讓Fragment的宿主Activity更加整潔一些,僅此而已。
既然如此,我們轉為本文的下半部分,關於傳參。
想過嗎?Fragment作為java類
為什麼傳參需要用Fragment.setArguments(bundle)這樣的方式,
而不通過建構函式直接傳遞new Fragment(arg1,arg2);
實踐出真知,其實在大多數時候,這兩種方法傳遞引數都是沒有問題的。
但是,但是當某些情景發生,一切就不一樣了。(比如豎屏切換橫屏時),切換到橫屏時,構造方法傳遞的引數就找不到了。
原因很簡單,因為Fragment是有自己封裝的生命週期的,這一點和Activity類似,Activity傳參也不是用構造方法的方式。
但是究竟生命週期對構造方法傳遞引數有什麼影響呢?
原始碼中一探究竟:
在Fragment中,是通過Bundle來儲存引數的,它的私有宣告在此:
Bundle mArguments;
順著這個宣告的命名mArguments找下去,發現其實相關的主要方法並不多:
public FragmentState(Fragment frag) { ... mArguments = frag.mArguments; ... }
public void setArguments(Bundle args) { if (mIndex >= 0) { throw new IllegalStateException("Fragment already active"); } mArguments = args; }
final public Bundle getArguments() { return mArguments; }
這三個比較簡單,就不說了
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; }
在instantiate()例項化過程中,可以看到
if (mArguments != null) { mArguments.setClassLoader(context.getClassLoader()); }
也就是說,如果我們呼叫時使用setArguments()傳遞了Bundle,它會被儲存在mArguments 這個私有宣告中。
而如果是通過建構函式傳遞的引數,那很不幸,Fragment重建過程中,並沒有持有相應引數的屬性或方法,自然,你通過建構函式傳遞的引數就丟失了。
其實目前大家單純無參new Fragment()的方式並沒有錯,只是可以讓Activity更優雅的呼叫Fragment.newInstance(),
而如果涉及到傳遞引數,萬不可通過建構函式傳遞,會丟失。
知其然,知其所以然
總結,Fragment.newInstance(),別無其他,只是事關風格(程式碼”整”“潔”之道),建議大家以後均使用谷歌推薦的該方法
以上這篇使用newInstance()來例項化fragment並傳遞資料操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。