Android Fragment 從原始碼的角度去解析(下)
1.概述
上一篇部落格已經簡單的講了一下Fragment的使用並寫了一個基本的例項,接下來就將其整合到專案中。附視訊地址:http://pan.baidu.com/s/1mhUus56
2.效果實現
列表和輪播條不做過多的解釋就是訪問介面獲取資料而已,這個在Android Studio自定義模板和Android無限廣告輪播都講過了。我們直接整合進去這個時候我們發現一個奇怪的問題,就是切換之後會去重新載入資料很不正常。
一般的思路我們會換實現方法,當然其他方式肯定也可以實現如ViewPager+Fragment但是我們需要預載入要不然也會出問題,一旦預載入就需要去訪問網路,即使使用者可能不切換Fragment就退出App了這個時候其實載入了所有Fragment的資料,而且主頁一旦複雜有可能會崩潰或造成記憶體溢位的問題。
我的簽名就是,忘記不了銘記,堅持不了放棄,但只要活著… 既然這樣我必須得看看原始碼:
2.2 Fragment原始碼分析:
把一個Fragment加到ViewGroup中就這麼幾行程式碼:add(@IdRes int containerViewId, Fragment fragment)和commit(),就這麼兩個方法:
@Override
protected void initData() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
mHomeFragment = new HomeFragment();
fragmentTransaction.add(R.id.main_tab_fl, mHomeFragment);
fragmentTransaction.commit();
}
我們點選add方法發現是個抽象方法:
/**
* Calls {@link #add(int, Fragment, String)} with a null tag.
*/
public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
點選fragmentManager.beginTransaction()發現也是一個抽象方法:
/**
* Start a series of edit operations on the Fragments associated with
* this FragmentManager.
*
* <p>Note: A fragment transaction can only be created/committed prior
* to an activity saving its state. If you try to commit a transaction
* after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
* (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
* or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
* This is because the framework takes care of saving your current fragments
* in the state, and if changes are made after the state is saved then they
* will be lost.</p>
*/
public abstract FragmentTransaction beginTransaction();
所以只能點選getSupportFragmentManager()方法這個方法在FragmentActivity中:
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
*/
public FragmentManager getSupportFragmentManager() {
return mFragments.getSupportFragmentManager();
}
一路摸索才找到這個add方法,發現並沒有寫註釋這個google工程師有點打醬油節奏啊!只好自己來吧在需要的地方寫一寫。我們看下面貼出來的原始碼其實可以知道,add方法其實只是設定了一些必要引數,並沒有做任何的處理,這也是說google為什麼一定要我們不要忘記commit()的原因:
public FragmentTransaction add(int containerViewId, Fragment fragment) {
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
fragment.mFragmentManager = mManager;
// tag可以說是唯一標識我們可以通過它從FragmentManager中找到對應的Fragment
if (tag != null) {
if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
throw new IllegalStateException("Can't change tag of fragment "
+ fragment + ": was " + fragment.mTag
+ " now " + tag);
}
fragment.mTag = tag;
}
if (containerViewId != 0) {
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
+ " now " + containerViewId);
}
// 把Fragment的ContainerId和FragmentId指定為我們傳遞過來的佈局中的ViewGroup的id。
fragment.mContainerId = fragment.mFragmentId = containerViewId;
}
// 見名思意 Op是什麼?就當是一些基本引數吧
Op op = new Op();
op.cmd = opcmd;
op.fragment = fragment;
addOp(op);
}
void addOp(Op op) {
if (mHead == null) {
mHead = mTail = op;
} else {
op.prev = mTail;
mTail.next = op;
mTail = op;
}
op.enterAnim = mEnterAnim;
op.exitAnim = mExitAnim;
op.popEnterAnim = mPopEnterAnim;
op.popExitAnim = mPopExitAnim;
mNumOp++;
}
既然add方法只是設定了一些引數而已,那麼肯定就在commit()中做了些什麼,找啊找啊找啊找,找到這麼個方法(有些程式碼我就省略):
void moveToState(Fragment f, int newState, int transit,
int transitionStyle, boolean keepActive){
// ... 省略部分程式碼
f.onAttach(mHost.getContext());
// 這個方法一呼叫就會執行Fragment的onAttach(Activity activity)這個生命週期方法
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
}
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
// 執行生命週期onCreate(savedInstanceState);
}
f.mRetaining = false;
if (f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
//從activity中找到我們需要存放Fragment的ViewGroup佈局
container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ f.getResources().getResourceName(f.mContainerId)
+ ") for fragment " + f));
}
}
// For fragments that are part of the content view
// layout, we need to instantiate the view immediately
// and the inflater will take care of adding it.
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
// 這個方法過後會執行onCreateView()生命週期且f.mView就是我們自己覆蓋Fragment返回的View
if (f.mView != null) {
f.mInnerView = f.mView;
// v4包相容11以下的版本我還是沒說錯啊
if (Build.VERSION.SDK_INT >= 11) {
ViewCompat.setSaveFromParentEnabled(f.mView, false);
} else {
f.mView = NoSaveStateFrameLayout.wrap(f.mView);
}
if (container != null) {
Animation anim = loadAnimation(f, transit, true,
transitionStyle);
if (anim != null) {
setHWLayerAnimListenerIfAlpha(f.mView, anim);
f.mView.startAnimation(anim);
}
// 如果ViewGroup不等於null就把從onCreateView()生命週期中獲得的View新增到該佈局中
// 最主要的就是這個方法,其實我們可以把Fragment理解成一個自定義的類
// 通過onCreateView()獲取的到View新增到一個FragmentActivity的一個ViewGroup中
// 只不過它有自己的生命週期而已......
container.addView(f.mView);
}
// 如果是隱藏那就設定為不可見
if (f.mHidden) f.mView.setVisibility(View.GONE);
// 執行onViewCreated()生命週期方法
f.onViewCreated(f.mView, f.mSavedFragmentState);
} else {
f.mInnerView = null;
}
f.performActivityCreated(f.mSavedFragmentState);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
// 程式碼省略......
}
}
// 後面的我們就不看了,這上面的程式碼我自己做了一些整合,把它連貫起來了
// 因為我們把add方法寫在了Activity中的onCreate()方法中所以做了一些處理......
到這裡應該能夠了解Fragment的工作流程了吧,接下來我們看replace方法中究竟做了?其實和add差不多隻是把int opcmd變成了OP_REPLACE替換操作:
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
if (containerViewId == 0) {
throw new IllegalArgumentException("Must use non-zero containerViewId");
}
doAddOp(containerViewId, fragment, tag, OP_REPLACE);
return this;
}
這個時候去commit會呼叫mManager.removeFragment(old, transition, transitionStyle)方法把原來的移除,然後把當前的Fragment新增進去,那豈不是每點選一個上一就被銷燬了,那之前華東到哪裡來了做了寫什麼事都被幹掉重新建立了。
if (mManager.mAdded != null) {
for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
Fragment old = mManager.mAdded.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
}
mManager.removeFragment(old, transition, transitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
}
到這裡原始碼就以完畢有興趣的小夥伴可以自己仔細去看看原始碼,接下來我們就來解決問題,我們肯定在呼叫replace方法的時候希望它不要移除原來的,那怎麼辦改Android的底層原始碼嗎?那就只能換方法了,思路就是如果該Fragment不存在FragmentManager中我們就去新增,否則我們把之前的隱藏而不是替換移除,把當前的顯示即可,最後程式碼就是:
private void homeRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
}
fragmentTransaction.show(mHomeFragment);
fragmentTransaction.commit();
}
@OnClick(R.id.find_rb)
private void findRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
}
if(mFindFragment == null){
mFindFragment = new FindFragment();
fragmentTransaction.add(R.id.main_tab_fl,mFindFragment);
}else {
fragmentTransaction.show(mFindFragment);
}
fragmentTransaction.commit();
}
@OnClick(R.id.new_rb)
private void newRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
}
if(mNewFragment == null){
mNewFragment = new NewFragment();
fragmentTransaction.add(R.id.main_tab_fl,mNewFragment);
}else {
fragmentTransaction.show(mNewFragment);
}
fragmentTransaction.commit();
}
@OnClick(R.id.message_rb)
private void messageRbClick() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment fragment : fragments) {
fragmentTransaction.hide(fragment);
}
if(mMessageFragment == null){
mMessageFragment = new MessageFragment();
fragmentTransaction.add(R.id.main_tab_fl,mMessageFragment);
}else {
fragmentTransaction.show(mMessageFragment);
}
fragmentTransaction.commit();
}