1. 程式人生 > >Android Fragment(生命週期+懶載入)

Android Fragment(生命週期+懶載入)

以前面試的時候吧,能把Fragment的生命週期從頭到尾背一遍,然後懶載入也是知道怎麼實現,但是呢,沒有寫過demo具體研究過,於是就準備寫篇部落格就當筆記了。
先附上一張面試時候常考的一張Fragment生命週期圖:
網上隨便找的哈
測試的佈局activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height
="match_parent" android:orientation="vertical">
<android.support.design.widget.TabLayout android:id="@+id/id_tab" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/id_viewpager"
android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@android:color/white" />
</LinearLayout>

測試的Fragment:

/**
 * @author EX_YINQINGYANG
 * @version [Android PABank C01, @2016-09-21]
 * @date 2016-09-21
 * @description
*/
public class MyFragment extends Fragment { private static final String PAGE_NO = "PAGE_NO"; private static final String TAG = "MyFragment"; private int pageNo; public static Fragment newInstance(int pageNo){ MyFragment fragment=new MyFragment(); Bundle bundle=new Bundle(); bundle.putInt(PAGE_NO,pageNo); fragment.setArguments(bundle); return fragment; } @Override public void onAttach(Context context) { super.onAttach(context); pageNo=getArguments().getInt(PAGE_NO,0); Log.e(TAG+" "+pageNo,"---->onAttach"); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG+" "+pageNo,"---->onCreate"); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.e(TAG+" "+pageNo,"---->onCreateView"); TextView view=new TextView(getActivity()); view.setGravity(Gravity.CENTER); view.setTextColor(Color.BLACK); view.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18f); view.setText("Fragment"+pageNo); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.e(TAG+" "+pageNo,"---->onActivityCreated"); } @Override public void onResume() { super.onResume(); Log.e(TAG+" "+pageNo,"---->onResume"); } @Override public void onPause() { super.onPause(); Log.e(TAG+" "+pageNo,"---->onPause"); } @Override public void onStop() { super.onStop(); Log.e(TAG+" "+pageNo,"---->onStop"); } @Override public void onDestroyView() { super.onDestroyView(); Log.e(TAG+" "+pageNo,"---->onDestroyView"); } @Override public void onDestroy() { super.onDestroy(); Log.e(TAG+" "+pageNo,"---->onDestroy"); } @Override public void onDetach() { super.onDetach(); Log.e(TAG+" "+pageNo,"---->onDetach"); } }

測試的MainActivity:

public class MainActivity extends AppCompatActivity {
    private TabLayout mTab;
    private ViewPager mPager;
    private List<Fragment>fragments;
    private String[]titles={"page1","page2","page3"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTab= (TabLayout) findViewById(R.id.id_tab);
        mPager= (ViewPager) findViewById(R.id.id_viewpager);
        mPager.setOffscreenPageLimit(0);
        initDatas();
    }

    private void initDatas() {
        fragments=new ArrayList<>(3);
        fragments.add(MyFragment.newInstance(1));
        fragments.add(MyFragment.newInstance(2));
        fragments.add(MyFragment.newInstance(3));
        mPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return fragments.get(position);
            }

            @Override
            public int getCount() {
                return fragments.size();
            }
            @Override
            public CharSequence getPageTitle(int position) {
                return titles[position];
            }
        });
        mTab.setTabMode(TabLayout.MODE_FIXED);
        mTab.setupWithViewPager(mPager);
        mTab.setTabTextColors(Color.GRAY,Color.RED);
    }
}

執行一下程式碼如圖:
這裡寫圖片描述

相信大家走到這一步都是so easy的啦,我就不具體解釋程式碼了,
首先我們看一下列印的log:

這裡寫圖片描述
是不是跟我們一開始貼的那張圖期待的是一樣的?是的! 然後我們注意這麼一段程式碼

mPager.setOffscreenPageLimit(0);

我們改為:

mPager.setOffscreenPageLimit(1);

執行看到log:
這裡寫圖片描述
結果跟我們上面設定為0是一樣的效果,也就是說即使我設定了0,ViewPager任然快取了1個Fragment,我們看看ViewPager原始碼。

 public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) {
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                    DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }

我們看看DEFAULT_OFFSCREEN_PAGES這個屬性預設是多少?我們可以看到DEFAULT_OFFSCREEN_PAGES 預設是1,跟我們猜測是一樣的,也就是說ViewPager預設快取了一個item。

private static final int DEFAULT_OFFSCREEN_PAGES = 1;

那麼如果不想讓ViewPager做快取怎麼辦呢? 只需要自己創一個ViewPager叫MyViewPager,然後把ViewPager的所有原始碼匯入進去,修改下

private static final int DEFAULT_OFFSCREEN_PAGES = 0;

即可!!,是不是soeasy呢? 這也算是一小小技巧吧,我們接著往下走……

下面我們來說說Fragment的懶載入,使用場景ViewPager+Fragment的時候,Fragment對使用者可見的時候去載入資料,以時間換空間,載入到哪個Fragment的時候才去載入資料,而不是一出來就載入所有資料,上網一搜一大堆實現方式,但是主要的還是利用重寫Fragment的setUserVisibleHint方法實現。

下面我們來看看為什麼通過重寫這個方法就可以實現了,我們看看我們的FragmentPagerAdapter的原始碼。

public abstract class FragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentPagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public FragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    /**
     * Return the Fragment associated with a specified position.
     */
    public abstract Fragment getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) {
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment)object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

    /**
     * Return a unique identifier for the item at the given position.
     *
     * <p>The default implementation returns the given position.
     * Subclasses should override this method if the positions of items can change.</p>
     *
     * @param position Position within this adapter
     * @return Unique identifier for the item at position
     */
    public long getItemId(int position) {
        return position;
    }

    private static String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }

其實呢也就是繼承了一個PagerAdapter,跟我們平時的用法一樣,我們看看程式碼會發現在這個類中setUserVisibleHint方法多次被呼叫,我們簡單擼一擼原始碼,

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

在Adapter的instantiateItem方法中,將即將展現的Fragment的fragment.setUserVisibleHint(false);isVisible賦值為了false,也就是此時Fragment還不可見。

我們緊接著看看FragmentPagerAdapter中有一個setPrimaryItem的方法,將setUserVisibleHint賦值為了true,也就是說此時的Fragment狀態變為了對使用者可見,是的!setPrimaryItem方法是在Fragment即將對使用者可見的時候呼叫。

  @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

到了這裡,想必大家對Fragment懶載入的實現有了一點小小的思路了咯。
我們把我們程式碼改改(重寫下setUserVisibleHint方法):

 @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(TAG+" "+pageNo,"---->setUserVisibleHint-->"+isVisibleToUser);
    }

我們來執行下程式碼看看log:
這裡寫圖片描述

我們看見當呼叫完第一個Fragment的onCreate方法的時候呼叫了setUserVisibleHint–>true此時第一個Fragment對使用者可見。同時也發現,setUserVisibleHint先於onCreate方法執行,並且執行了兩次,但是此時的Fragment對使用者不可見。

我們從1—–>2,然後看看log:
這裡寫圖片描述

第一個Fragment的setUserVisibleHint設為了false,即將出現的第二個Fragment的setUserVisibleHint設定為了true。

我們從2—–>1,然後看看log:
這裡寫圖片描述

第一個Fragment的setUserVisibleHint設為了true,即將消失的第二個Fragment的setUserVisibleHint設定為了false。

好了,到了這裡相信你一定很有思路了。。。。。。
那我們就一起來實現一下Fragment的懶載入。

首先建立一個BaseFragment:

**
 * Author:Yqy
 * Date:2016-09-21
 * Desc:
 * Company:cisetech
 */
public abstract class BaseFragment extends Fragment {
    /**
     * 是否對使用者可見標記
     */
    protected boolean isVisible;
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(isVisibleToUser){
            isVisible = true;
            onVisible();
        }else{
            isVisible = false;
            onInvisible();
        }
    }

    /**
     * 不可見的時候操作
     */
    protected void onVisible(){
        lazyLoad();
    }

    /**
     * 抽象lazyLoad方法,讓子類實現。
     */
    protected abstract void lazyLoad();
    protected void onInvisible(){}
}

然後讓MyFragment繼承BaseFragment:

**
 * @author EX_YINQINGYANG
 * @version [Android PABank C01, @2016-09-21]
 * @date 2016-09-21
 * @description
 */
public class MyFragment extends BaseFragment {
    /**
     * 初始化View完畢的標記
     */
    private boolean isPrepared;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        TextView view=new TextView(getActivity());
        view.setGravity(Gravity.CENTER);
        view.setTextColor(Color.BLACK);
        view.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18f);
        isPrepared=true;
        lazyLoad();
        return view;
    }
    private ProgressDialog dialog;
    @Override
    protected void lazyLoad() {
        if(!isPrepared || !isVisible) {
            return;
        }
        dialog=ProgressDialog.show(getActivity(),"","載入中,請稍後...");
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getActivity(),"載入完畢!",Toast.LENGTH_SHORT).show();
                ((TextView)getView()).setText(""+getArguments().getInt("PAGE_NO"));
                dialog.dismiss();
            }
        },3000);
        //填充各控制元件的資料
    }

    public static Fragment newInstance(int i) {
        MyFragment fragment=new MyFragment();
        Bundle bundle=new Bundle();
        bundle.putInt("PAGE_NO",i);
        fragment.setArguments(bundle);
        return fragment;
    }
}

因為setUserVisibleHint會先於onCreateView執行,如果這個時候載入資料的話,然後賦值會報各種空指標錯誤,所以當執行完onCreateView方法後,此時的isVisible也為true,所以再去執行一次lazyLoad();方法。

Fragment就暫時先研究到這裡了,3Q!!!
最後給出demo的github連結: