編輯:關於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的數據呢?為什麼只顯示默認的數據?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, ListnonConfigList) { 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的空參的構造器,否則就讓你崩潰~~~
好了,拋棄之前那些不好的代碼習慣吧,支持谷歌,擁抱變化。
Linux內核啟動之後就到Android Init進程,進而啟動Android相關的服務和應用。啟動的過程如下圖所示: 下面將從Android4.0源碼中,和
本文為那些不錯的Android開源項目第三篇——優秀項目篇,主要介紹那些還不錯的完整Android項目。記錄的項目主要依據是項目有意思或項目分層規
Google已經建議Android開發全部轉向Android Studio開發,Android Studio 是使用gradle編譯、打包的,那麼問題來了,gradle可
夜深也是無聊,翻看以前的老代碼,發現那個我們經常用的菊花圈,原來是幀動畫做的,有點意思。突然感覺幀動畫做的東西效果不錯啊,至少看起來聽耐看的。開工上代碼: 先是布局文件: