Android开发经验谈Android技术知识Android开发

ViewPager懒加载极致优化

2020-09-07  本文已影响0人  Android_Anan

01.ViewPager简单介绍

02.ViewPager弊端分析

普通的viewpager如果你不使用setoffscreenpagelimit(int limit)这个方法去设置默认加载数的话是会默认加载页面的左右两页的,也就是说当你进入viewpager第一页的时候第二页和第一页是会被一起加载的,这样同时加载就会造成一些问题,试想我们如果设置了setoffscreenpagelimit为3的话,那么进入viewpager以后就会同时加载4个fragment,像我们平时的项目中在这些fragment中一般都是会发送网络请求的,也就是说我们有4个fragment同时发送网络请求去获取数据,这样的结果显而易见给用户的体验是不好的(如:浪费用户流量,造成卡顿等等)。

懒加载的实现弊端

03.ViewPager预加载

ViewPager的预加载机制。那么,我们可不可以设置ViewPager的预加载为0,不就解决问题了吗?也就是代码这样操作:

`vp.setOffscreenPageLimit(0);` 

然后看一下源码

`public void setOffscreenPageLimit(int limit) {
    if (limit < 1) {
        Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
        limit = 1;
    }

    if (limit != this.mOffscreenPageLimit) {
        this.mOffscreenPageLimit = limit;
        this.populate();
    }

}` 

ViewPager默认情况下的加载,当切换到当前页面时,会默认预加载左右两侧的布局到ViewPager中,尽管两侧的View并不可见的,我们称这种情况叫预加载;由于ViewPager对offscreenPageLimit设置了限制,页面的预加载是不可避免……

初始化缓存(mOffscreenPageLimit == 1)

image

中间页面缓存(mOffscreenPageLimit == 1)

04.ViewPager部分源码

ViewPager.setAdapter方法

ViewPager.populate(int newCurrentItem)

ViewPager.dataSetChanged()

ViewPager.scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected)

ViewPager.calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo)

05.懒加载出现问题

发现Fragment中有一个setUserVisibleHint(boolean isVisibleToUser)方法,这个方法就是告诉用户,UI对用户是否可见,可以做懒加载初始化操作。

懒加载需要处理的几个问题

视图保存

是否已经被用户所看到

06.如何实现预加载机制

主要的方法是Fragment中的setUserVisibleHint(),此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法,使用getUserVisibleHint() 可以返回fragment是否可见状态。在BaseLazyFragment中需要在onActivityCreated()及setUserVisibleHint()方法中都调了一次lazyLoad() 方法。如果仅仅在setUserVisibleHint()调用lazyLoad(),当默认首页首先加载时会导致viewPager的首页第一次展示时没有数据显示,切换一下才会有数据。因为首页fragment的setUserVisible()在onActivityCreated() 之前调用,此时isPrepared为false 导致首页fragment 没能调用onLazyLoad()方法加载数据。

 `/**
 * <pre>
 *     @author yangchong
 *     blog  : https://github.com/yangchong211
 *     time  : 2017/7/22
 *     desc  : 懒加载
 *     revise: 懒加载时机:onCreateView()方法执行完毕 + setUserVisibleHint()方法返回true
 * </pre>
 */
public abstract class BaseLazyFragment extends BaseFragment {

    /*
     * 预加载页面回调的生命周期流程:
     * setUserVisibleHint() -->onAttach() --> onCreate()-->onCreateView()-->
     *              onActivityCreate() --> onStart() --> onResume()
     */

    /**
     * 懒加载过
     */
    protected boolean isLazyLoaded = false;
    /**
     * Fragment的View加载完毕的标记
     */
    private boolean isPrepared = false;

    /**
     * 第一步,改变isPrepared标记
     * 当onViewCreated()方法执行时,表明View已经加载完毕,此时改变isPrepared标记为true,并调用lazyLoad()方法
     */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isPrepared = true;
        //只有Fragment onCreateView好了
        //另外这里调用一次lazyLoad()
        lazyLoad();
    }

    /**
     * 第二步
     * 此方法会在onCreateView()之前执行
     * 当viewPager中fragment改变可见状态时也会调用
     * 当fragment 从可见到不见,或者从不可见切换到可见,都会调用此方法
     * true表示当前页面可见,false表示不可见
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        LogUtil.d("setUserVisibleHint---"+isVisibleToUser);
        //只有当fragment可见时,才进行加载数据
        if (isVisibleToUser){
            lazyLoad();
        }
    }

    /**
     * 调用懒加载
     * 第三步:在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载
     */
    private void lazyLoad() {
        if (getUserVisibleHint() && isPrepared && !isLazyLoaded) {
            showFirstLoading();
            onLazyLoad();
            isLazyLoaded = true;
        } else {
            //当视图已经对用户不可见并且加载过数据,如果需要在切换到其他页面时停止加载数据,可以覆写此方法
            if (isLazyLoaded) {
                stopLoad();
            }
        }
    }

    /**
     * 视图销毁的时候讲Fragment是否初始化的状态变为false
     */
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLazyLoaded = false;
        isPrepared = false;
    }

    /**
     * 第一次可见时,操作该方法,可以用于showLoading操作,注意这个是全局加载loading
     */
    protected void showFirstLoading() {
        LogUtil.i("第一次可见时show全局loading");
    }

    /**
     * 停止加载
     * 当视图已经对用户不可见并且加载过数据,但是没有加载完,而只是加载loading。
     * 如果需要在切换到其他页面时停止加载数据,可以覆写此方法。
     * 存在问题,如何停止加载网络
     */
    protected void stopLoad(){

    }

    /**
     * 第四步:定义抽象方法onLazyLoad(),具体加载数据的工作,交给子类去完成
     */
    @UiThread
    protected abstract void onLazyLoad();
}` 

onLazyLoad()加载数据条件

还有几个细节需要优化一下

07.懒加载配合状态管理器

什么是状态管理器?

如何降低偶性和入侵性

让View状态的切换和Activity彻底分离开,必须把这些状态View都封装到一个管理类中,然后暴露出几个方法来实现View之间的切换。在不同的项目中可以需要的View也不一样,所以考虑把管理类设计成builder模式来自由的添加需要的状态View。

那么如何降低耦合性,让代码入侵性低。方便维护和修改,且移植性强呢?大概具备这样的条件……

那么具体怎么操作呢?

可以自由切换内容,空数据,异常错误,加载,网络错误等5种状态。父类BaseFragment直接暴露5中状态,方便子类统一管理状态切换,这里fragment的封装和activity差不多。

 *     @author yangchong
 *     blog  : https://github.com/yangchong211
 *     time  : 2017/7/20
 *     desc  : fragment的父类
 *     revise: 注意,该类具有懒加载
 * </pre>
 */
public abstract class BaseStateFragment extends BaseLazyFragment {

    protected StateLayoutManager statusLayoutManager;
    private View view;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        if(view==null){
            view = inflater.inflate(R.layout.base_state_view, container , false);
            initStatusLayout();
            initBaseView(view);
        }
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initView(view);
        initListener();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    /**
     * 获取到子布局
     * @param view              view
     */
    private void initBaseView(View view) {
        LinearLayout llStateView = view.findViewById(R.id.ll_state_view);
        llStateView.addView(statusLayoutManager.getRootLayout());
    }

    /**
     * 初始化状态管理器相关操作
     */
    protected abstract void initStatusLayout();

    /**
     * 初始化View的代码写在这个方法中
     * @param view              view
     */
    public abstract void initView(View view);

    /**
     * 初始化监听器的代码写在这个方法中
     */
    public abstract void initListener();

    /**
     * 第一次可见状态时,showLoading操作,注意下拉刷新操作时不要用该全局loading
     */
    @Override
    protected void showFirstLoading() {
        super.showFirstLoading();
        showLoading();
    }

    /*protected void initStatusLayout() {
        statusLayoutManager = StateLayoutManager.newBuilder(activity)
                .contentView(R.layout.common_fragment_list)
                .emptyDataView(R.layout.view_custom_empty_data)
                .errorView(R.layout.view_custom_data_error)
                .loadingView(R.layout.view_custom_loading_data)
                .netWorkErrorView(R.layout.view_custom_network_error)
                .build();
    }*/

    /*---------------------------------下面是状态切换方法-----------------------------------------*/

    /**
     * 加载成功
     */
    protected void showContent() {
        if (statusLayoutManager!=null){
            statusLayoutManager.showContent();
        }
    }

    /**
     * 加载无数据
     */
    protected void showEmptyData() {
        if (statusLayoutManager!=null){
            statusLayoutManager.showEmptyData();
        }
    }

    /**
     * 加载异常
     */
    protected void showError() {
        if (statusLayoutManager!=null){
            statusLayoutManager.showError();
        }
    }

    /**
     * 加载网络异常
     */
    protected void showNetWorkError() {
        if (statusLayoutManager!=null){
            statusLayoutManager.showNetWorkError();
        }
    }

    /**
     * 加载loading
     */
    protected void showLoading() {
        if (statusLayoutManager!=null){
            statusLayoutManager.showLoading();
        }
    }
}

//如何切换状态呢?
showContent();
showEmptyData();
showError();
showLoading();
showNetWorkError();

//或者这样操作也可以
statusLayoutManager.showLoading();
statusLayoutManager.showContent();` 

状态管理器的设计思路

项目地址:https://github.com/yangchong211/YCStateLayout

上一篇下一篇

猜你喜欢

热点阅读