安卓性能优化之懒加载的原理及实现(超详细仿微信头条实现Fragment数据懒加载)(1)

  1. 本文part1和part2没有特别直接的关联,如果不关新ViewPager预加载原理的同学,可以直接跳过Part1
  2. 附上项目的下载地址以供参考:Demo下载地址

Part1:懒加载概述

1: 懒加载的由来和预加载的弊端

在Android开发过程中fragment通常会和ViewPager结合使用,通过Viewpager的预加载机制,提升App的体验效果,但是这样把我们看不到的页面的数据也加载了,初始化时浪费资源大大降低了性能,容易造成卡顿现象,尤其是在页面请求数据比较多、网络比较慢时它的缺点也就暴露了越来越明显。懒加载的出现就是为了解决以上Viewpager的预加载的弊端。

2:什么是懒加载?

懒加载通俗点讲一句话:当用户可以看见的时候才去进行网络数据的加载。懒加载的作用就是解决ViewPager和fragment结合使用时的弊端。目前一些大厂APP的首页都是通过懒加载实现,如微信、今日头条等。

从技术层面上讲,懒加载的原理是通过Fragment的生命周期中的几个方法 和非生命周期setUserVisibleHint以及onHiddenChanged方法结合使用,实现关闭ViewPager的预加载机制,从而实现懒加载。

3:从ViewPager的源码理解为什么不能通过setOffscreenPageLimit方法关闭ViewPager的预加载?

既然通过ViewPager的setOffscreenPageLimit(int i)实现的预加载,那么能不能通过setOffscreenPageLimit(0)去避免,预加载从而实现懒加载呢?当然是不能了,因为在ViewPagersetOffscreenPageLimit方法中对参数做了校验,当小于1时传入默认值1,无法关闭预加载从而实现懒加载。

接下看看和ViewPager预加载相关的几个方法的源码去理解一下,ViewPager如何实现的预加载以及setOffscreenPageLimit方法中参数为0时为什么不能关闭预加载机制。

1:ViewPager的 setOffscreenPageLimit()方法

ViewPager的 setOffscreenPageLimit()方法分析:主要是用来控制缓存fragment的数量,这里容易有个误区:Fragment本身是不具有缓存功能,只有在ViewPager+Fragment+PagerAdpater结合使用时,通过ViewPager实现的fragment的缓存 。

/**

  • 作用:控制缓存Fragment的数量, 默认缓存为1,
  •  因为当参数limit小于1时,赋值为1,所以默认缓存一帧
    
  •  这也就是设置为0是,无法关闭预加载的的原因
    
  • @param limit
    */
    public void setOffscreenPageLimit(int limit) {
    if (limit < 1) {
    Log.w(“ViewPager”, "Requested offscreen page limit " + limit + " too small; defaulting to " + 1);
    //当小于1时赋值为1,所以默认缓存一帧
    limit = 1;
    }
    if (limit != this.mOffscreenPageLimit) {
    //记录limit
    this.mOffscreenPageLimit = limit;
    //调用populate方法,进行布局排列
    this.populate();
    }
    }
2:ViewPager的 populate()方法

这里populate方法是 ViewPager 一个核心方法,非常重要是缓存功能实现:缓存功能是 populate方法在不同时机调用PagerAdapter的相关方法实现,缓存的原理是存放到一个ArrayList集合中,这里只是简过一下PagerAdapter或FragmentPagerAdapter的执行流程,详细源码,日后将专门写一篇关于ViewPager的源码分析,这里主要分析一下和预加载实现相关的源码:

源码的分析过程请看下面注释中的:Step1-Step5
void populate() {
//调用重载方法
this.populate(this.mCurItem);
}
void populate(int newCurrentItem) {
//用于存储缓存的内容的实体,这里主要是存放Fragment
ViewPager.ItemInfo oldCurInfo = null;
if (this.mCurItem != newCurrentItem) {
//当不同时,取出旧的Fragment
oldCurInfo = this.infoForPosition(this.mCurItem);
//更新缓存数量
this.mCurItem = newCurrentItem;
}

if (this.mAdapter == null) {
this.sortChildDrawingOrder();
} else if (this.mPopulatePending) {
this.sortChildDrawingOrder();
} else if (this.getWindowToken() != null) {
/**

  • Step1:mAdapter.startUpdate(this),PagerAdapter 的startUpdate方法
  • 开始更新
    /
    this.mAdapter.startUpdate(this);
    int pageLimit = this.mOffscreenPageLimit;
    int startPos = Math.max(0, this.mCurItem - pageLimit);
    /
    *
  • Step2:调用PagerAdapter的getCount方法获取fragment的总数
  • 确定缓存空间[startPos,endPos]即[ this.mCurItem - pageLimit,this.mCurItem + pageLimit]
    

*/
int N = this.mAdapter.getCount();
int endPos = Math.min(N - 1, this.mCurItem + pageLimit);
if (N != this.mExpectedAdapterCount) {
String resName;
try {
resName = this.getResources().getResourceName(this.getId());
} catch (NotFoundException var17) {
resName = Integer.toHexString(this.getId());
}

throw new IllegalStateException("The application’s PagerAdapter changed the adapter’s contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: " + this.mExpectedAdapterCount + ", found: " + N + " Pager id: " + resName + " Pager class: " + this.getClass() + " Problematic adapter: " + this.mAdapter.getClass());
} else {
int curIndex = true;
ViewPager.ItemInfo curItem = null;

int curIndex;
for(curIndex = 0; curIndex < this.mItems.size(); ++curIndex) {
ViewPager.ItemInfo ii = (ViewPager.ItemInfo)this.mItems.get(curIndex);
if (ii.position >= this.mCurItem) {
if (ii.position == this.mCurItem) {
curItem = ii;
}
break;
}
}

/**

  • Step3:如果没有找到curItem,即没有缓存fragment,则调用addNewItm加载一个 addNewItem中:
  • 1: ii.object = this.mAdapter.instantiateItem(this, position);
    
  •     即通过PagerAdapter的instantiateItem方法创建一个fragment并返回,存放到li(ViewPager.ItemInfo li)
    
  •     的object属性中,此过程需要看子类FragmentPagerAdapter中对应的方法,其源码不在这里描述
    
  • 2: 然后先把把li放到一个ArrayList的集合中,最后将li返回
    
  • 3:缓存的fragment的生命周期就从此开始
    

*/
if (curItem == null && N > 0) {
curItem = this.addNewItem(this.mCurItem, curIndex);
}

int itemIndex;
ViewPager.ItemInfo ii;
int i;
if (curItem != null) {
//左边item进行缓存处理
float extraWidthLeft = 0.0F;
itemIndex = curIndex - 1;
…省略部分代码
for(int pos = this.mCurItem - 1; pos >= 0; --pos) {
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
…省略部分代码
if (pos == ii.position && !ii.scrolling) {
this.mItems.remove(itemIndex);
/**

  • Step4:当item不在缓存范围内时,调用mAdapter.destroyItem销毁
    */
    this.mAdapter.destroyItem(this, pos, ii.object);
    –itemIndex;
    –curIndex;
    ii = itemIndex >= 0 ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
    }
    …省略部分代码
    }

float extraWidthRight = curItem.widthFactor;
itemIndex = curIndex + 1;
//处理右边的缓存
if (extraWidthRight < 2.0F) {
ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
float rightWidthNeeded = i <= 0 ? 0.0F : (float)this.getPaddingRight() / (float)i + 2.0F;
for(int pos = this.mCurItem + 1; pos < N; ++pos) {
if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
…省略部分代码
if (pos == ii.position && !ii.scrolling) {
this.mItems.remove(itemIndex);
/**

  • Step4:当item不在缓存范围内时,调用mAdapter.destroyItem销毁
    /
    this.mAdapter.destroyItem(this, pos, ii.object);
    ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null;
    }
    } else if (ii != null && pos == ii.position) {
    …省略部分代码
    } else {
    …省略部分代码
    }
    }
    }
    this.calculatePageOffsets(curItem, curIndex, oldCurInfo);
    this.mAdapter.setPrimaryItem(this, this.mCurItem, curItem.object);
    }
    /
    *
  • Step5:调用PagerAdapter的finishUpdate方法完成更新
    */
    this.mAdapter.finishUpdate(this);
    //。。。省略部分代码,主要是对子fragment的缓存处理
    int childCount = this.getChildCount();
    …代码
    }
    }
    }
3:populate(即缓存)方法总结一下:
  1. 执行完populate方法后至少缓存1帧的数据
  2. ViewPage的缓存功能是通过将缓存的frament存放到ArrayList中。
  3. 预加载的设计是通过PagerAdapter穿插管理
  4. 这种缓存就是我们所说的预加载,预加载会造成浪费资源、耗内存、卡顿等一系列的问题。

Part2:懒加载的详细实现与封装

1:懒加载的整体设计思想

懒加载就是不让Viewpager进行预加载数据,即当用户肉眼可见时才去进行数据加载。
懒加载的流程将围绕Fragment可见状态实现分为以下三个种情况:

  1. 首先要区分Fragment第一次可见,因为Fragment的第一次和非第一次可见分别对应View的创建和View的更新两种情况,因为在非第一次可见时,大部分的信息都是从缓存中获取的。
  2. Fragment每次对用户可见时,才需要网络请求加载数据
  3. Fragment每次对用户不可见时,如果有线程加载数据,需要中断网络请求

2:fragment哪些方法与懒加载有关

懒加载和Fragment的声明周期有关的几个方法:

  1. onCreateView:初始化Fragment的布局,不建议在此处执行耗时操作
  2. onResume和onPause:此时视图是可见的即前台可见,只是onResume时可以和用户交互,onPause时不能和用户交互。注意区分这里的可见和和上面说到的对用户可见是两种情况不要混淆了
  3. onDestroyView: 销毁和Fragment相关的视图,但是此时未和Activity解绑,此时仍然可以通过onCreateView创建视图。

懒加载和Fragment的生命周期无关的方法:

  1. setUserVisibleHint方法,此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,使用getUserVisibleHint() 可以返回fragment是否可见状态,如果可见则进行懒加载操作
  2. onHiddenChanged方法,在使用FragmentTransactionhidden 和show方法管理fragment时,是不会走Fragment正常的生命周期,而是走onHiddenChanged方法,此方法可以监听Fragment 的显示与隐藏。

在以上六个方法中分别作对应的逻辑处理才能真正实现懒加载。

3:懒加载LazyFragment的详细代码实现和封装

在讲代码之前理解三个概念,以便理解懒加载的实现过程,当前Fragment、目标Fragment和缓存Fragment。
例如:页面有三个Fragment:Fragment1、Fragment2 、Fragment3,当用户在Fragment1上进行操作,打算跳到Fragment2中,则三个角色分别为

  1. 当前Fragment:当前用户操作的fragment,即Fragment1。
  2. 目标Fragment:要切换的fragment,即Fragment2。
  3. 缓存Fragment:预加载的Fragment,即Fragment3。

首先看定义的三个成员变量:

boolean mIsViewCreated=false;//Fragment是否已经创建,
boolean mIsFirstVisiable=true;//Fragment是否第一次可见
boolean mCurrentVisiableState=false;//标记保存Fragment的可见状态,表示当前Fragment是否分发过, 可见到不可见 不可见到可见才可以调用 disPatchVisibaleHint 防止重复调用

step1:setUserVisibleHint方法的逻辑处理

因为setUserVisibleHint方法是最先执行的在此先看他的实现

//与生命周期无关的函数
/**

  • onCreateView在此方法之后 第一次时 mIsViewCreated=false
  • @param isVisibleToUser
    /
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
    //用户是否可见
    Log.e(TAG,“setUserVisibleHint----”+isVisibleToUser);
    super.setUserVisibleHint(isVisibleToUser);
    /
    *
  • 1:分发的前提是FragmentView已经创建,因为setUserVisibleHint在fragment 的声明周期之前执行,
  • 如果页面没创建就去分发容易造成空指针异常
  • 2:因为此方法是由setUserVisibleHint方法调用,而setUserVisibleHint方法由系统的Viewpager调用多次,我们没办法控制它,
  • 但是我们可以控制disPatchVisibaleHint的分发
    */
    if (mIsViewCreated){
    //只有Fragment创建了后才进行分发
    if (isVisibleToUser&& !mCurrentVisiableState){//防止重复调用disPatchVisibaleHint !mCurrentVisiableState表示没有分发过时才分发
    //用户可见时进行分发事件
    disPatchVisibaleHint(true);
    }else if (mCurrentVisiableState&& !isVisibleToUser){
    //用户不可见时不分发事件
    disPatchVisibaleHint(false);
    }
    }
    }

/**

  • 分发可见
  • 调用的前提:可见–》不可见 或者不可见–》可见
  • @param isVisiable
    */
    private void disPatchVisibaleHint(boolean isVisiable) {
    Log.e(TAG,“disPatchVisibaleHint----”+isVisiable);

if (mCurrentVisiableState==isVisiable){
//防止调用两次更新,存在当 mCurrentVisiableState=isVisiable;执行前setUserVisibleHint可能被调用两次
return;
}
mCurrentVisiableState=isVisiable;//进行赋值操作
if (isVisiable){
//可见
if (mIsFirstVisiable){
mIsFirstVisiable=false;
//处理第一次可见时
//公共方法,由子类实现
onFragmentFirstVisiable();
}
//复写onFragmentResume分发事件,网路请求
onFragmentResume();
//对viewpager嵌套使用时处理子的fragment
dispatChChildVisiableState(true);
}else {
//复写onFragmentPause终止数据请求
onFragmentPause();
//对viewpager嵌套使用时处理子的fragment
dispatChChildVisiableState(false);
}
}
/**

  • 下面抽取三个方法定义成public方法是因为:
  • 父类不知道子类要不要处理可见和不可见,因为子类有可能不需要懒加载,此时子类什么也不需要做
  • 当需要懒加载时,才需要复写这三个方法。
  • 因此这3个方法是非必须实现的方法,不能定义成抽象方法
    */

/**

  • 第一次可见时特殊处理
    */
    public void onFragmentFirstVisiable() {
    Log.e(TAG,“onFragmentFirstVisiable”);
    }

/**

  • 不可见时处理相关动作 停止数据的加载
    */
    public void onFragmentPause() {
    Log.e(TAG,“onFragmentPause”);
    }

/**

  • 表面可见时 加载数据
    */
    public void onFragmentResume() {
    Log.e(TAG,“onFragmentResume”);
    }
step2:onCreateView函数的实现

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
TAG=getClass().getSimpleName()+“----”;
Log.e(TAG,“onCreateView”);
//两个抽象方法,让子类实现initView和getLayoutRes
if (mRootView==null) {
mRootView=inflater.inflate(getLayoutRes(),null);
}
initView(mRootView);
//1:创建了Fragment 控制下面分发的前提,因为分发事件由setUserVisibleHint方法控制,而setUserVisibleHint最先执行
mIsViewCreated=true;

//2:对于默认Fragment的加载,可以在此分发一下,可见才分发
if (getUserVisibleHint()&&!isHidden()){
//可见
disPatchVisibaleHint(true);
}
return mRootView;
}

step3: onPause方法的实现

这里需要描述一下场景,当从fragment跳转到另一个Activity时需要做的处理,
只需对当前用户可见的Fragment的数据加载进行终止

@Override
public void onPause() {
Log.e(TAG,“onPause”);
if (mCurrentVisiableState&&getUserVisibleHint()){
disPatchVisibaleHint(false );
}
super.onPause();
}

step4: onResume方法的实现

这里需要描述一下场景,经过Step3后,又再次返回到Frangment所在的Activity时,只需要对当前用户可见的Fragment的数据加载

@Override
public void onResume() {
Log.e(TAG,“onResume”);
//只有当前可见的Fragment 才更新shuju 点击home键又返回
if (!mCurrentVisiableState&&getUserVisibleHint()&&!isHidden() ){
disPatchVisibaleHint(true );
}
super.onResume();
}

step5: onDestroyView方法的实现

主要是对控制变量进行恢复初始值操作##

@Override
public void onDestroyView() {
Log.e(TAG,“onDestroyView”);
super.onDestroyView();
//将所有的变量复位
mIsFirstVisiable=true;
mCurrentVisiableState=false;
mIsViewCreated=false;
}

step6:onHiddenChanged方法的实现

题外话

我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-0T4IH7RZ-1714624470823)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值