Android Fragment 從源碼的角度去解析(下)

1.概述


上一篇博客已經(jīng)簡單的講了一下Fragment的使用并寫了一個基本的實例,接下來就將其整合到項目中。附視頻地址:http://pan.baidu.com/s/1mhUus56
  
  

這里寫圖片描述

2.效果實現(xiàn)


2.1 整合上一個實例:

列表和輪播條不做過多的解釋就是訪問接口獲取數(shù)據(jù)而已,這個在Android Studio自定義模板Android無限廣告輪播都講過了。我們直接整合進去這個時候我們發(fā)現(xiàn)一個奇怪的問題,就是切換之后會去重新加載數(shù)據(jù)很不正常。
  一般的思路我們會換實現(xiàn)方法,當然其他方式肯定也可以實現(xiàn)如ViewPager+Fragment但是我們需要預加載要不然也會出問題,一旦預加載就需要去訪問網(wǎng)絡,即使用戶可能不切換Fragment就退出App了這個時候其實加載了所有Fragment的數(shù)據(jù),而且主頁一旦復雜有可能會崩潰或造成內存溢出的問題。
  我的簽名就是,忘記不了銘記,堅持不了放棄,但只要活著... 既然這樣我必須得看看源碼:
  
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方法發(fā)現(xiàn)是個抽象方法:

    /**
     * Calls {@link #add(int, Fragment, String)} with a null tag.
     */
    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);

點擊fragmentManager.beginTransaction()發(fā)現(xiàn)也是一個抽象方法:

    /**
     * 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方法,發(fā)現(xiàn)并沒有寫注釋這個google工程師有點打醬油節(jié)奏啊!只好自己來吧在需要的地方寫一寫。我們看下面貼出來的源碼其實可以知道,add方法其實只是設置了一些必要參數(shù),并沒有做任何的處理,這也是說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是什么?就當是一些基本參數(shù)吧
        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方法只是設置了一些參數(shù)而已,那么肯定就在commit()中做了些什么,找啊找啊找啊找,找到這么個方法(有些代碼我就省略):

void moveToState(Fragment f, int newState, int transit, 
    int transitionStyle, boolean keepActive){
                // ... 省略部分代碼
                f.onAttach(mHost.getContext());
                // 這個方法一調用就會執(zhí)行Fragment的onAttach(Activity activity)這個生命周期方法
                if (f.mParentFragment == null) {
                    mHost.onAttachFragment(f);
                }

                if (!f.mRetaining) {
                    f.performCreate(f.mSavedFragmentState);
                    // 執(zhí)行生命周期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);
                    // 這個方法過后會執(zhí)行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);
                        // 執(zhí)行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添加進去,那豈不是每點擊一個上一就被銷毀了,那之前華東到哪里來了做了寫什么事都被干掉重新創(chuàng)建了。

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();
    }

這里已經(jīng)寫得太多了視頻里還做了一個代碼優(yōu)化,具體附視頻地址:http://pan.baidu.com/s/1mhUus56

這里寫圖片描述
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容