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链接:

https://github.com/913453448/FragmentDemo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值