ViewPage的源码去分析预加载机制的实现原理
和产生的问题,从而引出懒加载,ViewPager+Fragment
嵌套使用的时候子ViewPager中Fragment的
懒加载实现在Android开发过程中fragment通常会和ViewPager结合使用,通过Viewpager的预加载机制,提升App的体验效果,但是这样把我们看不到的页面的数据也加载了,初始化时浪费资源大大降低了性能,容易造成卡顿现象,尤其是在页面请求数据比较多、网络比较慢时它的缺点也就暴露了越来越明显。懒加载的出现就是为了解决以上Viewpager的预加载的弊端。
懒加载通俗点讲一句话:当用户可以看见的时候才去进行网络数据的加载。懒加载的作用就是解决ViewPager和fragment结合使用时的弊端。目前一些大厂APP的首页都是通过懒加载实现,如微信、今日头条等。
从技术层面上讲,懒加载的原理是通过Fragment的生命周期中的几个方法 和非生命周期setUserVisibleHint
以及onHiddenChanged
方法结合使用,实现关闭ViewPager的预加载机制,从而实现懒加载。
既然通过ViewPager的setOffscreenPageLimit(int i)
实现的预加载,那么能不能通过setOffscreenPageLimit(0)
去避免,预加载从而实现懒加载呢?当然是不能了,因为在ViewPager
的setOffscreenPageLimit
方法中对参数做了校验,当小于1时传入默认值1,无法关闭预加载从而实现懒加载。
接下看看和ViewPager预加载相关的几个方法的源码去理解一下,ViewPager如何实现的预加载以及setOffscreenPageLimit
方法中参数为0
时为什么不能关闭预加载机制。
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();
}
}
这里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();
......代码
}
}
}
populate
方法后至少缓存1
帧的数据懒加载就是不让Viewpager进行预加载数据,即当用户肉眼可见时才去进行数据加载。
懒加载的流程将围绕Fragment可见状态实现分为以下三个种情况:
懒加载和Fragment的声明周期有关
的几个方法:
onCreateView
:初始化Fragment的布局,不建议在此处执行耗时操作onResume
和onPause:此时视图是可见的即前台可见,只是onResume时可以和用户交互,onPause
时不能和用户交互。注意区分这里的可见和和上面说到的对用户可见是两种情况不要混淆了
。onDestroyView
: 销毁和Fragment相关的视图,但是此时未和Activity解绑,此时仍然可以通过onCreateView创建视图。懒加载和Fragment的生命周期无关
的方法:
setUserVisibleHint
方法,此方法会在onCreateView()之前执行,当viewPager中fragment改变可见状态时也会调用,使用getUserVisibleHint() 可以返回fragment是否可见状态,如果可见则进行懒加载操作onHiddenChanged
方法,在使用FragmentTransaction
的hidden
和show
方法管理fragment时,是不会走Fragment正常的生命周期,而是走onHiddenChanged
方法,此方法可以监听Fragment 的显示与隐藏。在以上六个方法中分别作对应的逻辑处理才能真正实现懒加载。
在讲代码之前理解三个概念,以便理解懒加载的实现过程,当前Fragment、目标Fragment和缓存Fragment。
例如:页面有三个Fragment:Fragment1、Fragment2 、Fragment3,当用户在Fragment1上进行操作,打算跳到Fragment2中,则三个角色分别为
首先看定义的三个成员变量:
boolean mIsViewCreated=false;//Fragment是否已经创建,
boolean mIsFirstVisiable=true;//Fragment是否第一次可见
boolean mCurrentVisiableState=false;//标记保存Fragment的可见状态,表示当前Fragment是否分发过, 可见到不可见 不可见到可见才可以调用 disPatchVisibaleHint 防止重复调用
因为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");
}
@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;
}
这里需要描述一下场景,当从fragment跳转到另一个Activity时需要做的处理,
只需对当前用户可见的Fragment的数据加载进行终止
@Override
public void onPause() {
Log.e(TAG,"onPause");
if (mCurrentVisiableState&&getUserVisibleHint()){
disPatchVisibaleHint(false );
}
super.onPause();
}
这里需要描述一下场景,经过Step3后,又再次返回到Frangment所在的Activity时,只需要对当前用户可见的Fragment的数据加载
@Override
public void onResume() {
Log.e(TAG,"onResume");
//只有当前可见的Fragment 才更新shuju 点击home键又返回
if (!mCurrentVisiableState&&getUserVisibleHint()&&!isHidden() ){
disPatchVisibaleHint(true );
}
super.onResume();
}
主要是对控制变量进行恢复初始值操作##
@Override
public void onDestroyView() {
Log.e(TAG,"onDestroyView");
super.onDestroyView();
//将所有的变量复位
mIsFirstVisiable=true;
mCurrentVisiableState=false;
mIsViewCreated=false;
}
@Override
//与生命周期无关的函数
//在使用`FragmentTransaction`的`hidden` 和`show`方法管理fragment时,是不会走Fragment正常的生命周期,
// 而是走`onHiddenChanged`方法,此方法可以监听Fragment 的显示与隐藏。
public void onHiddenChanged(boolean hidden) {
Log.e(TAG,"onHiddenChanged");
// FragmentTransaction管理Fragment时
super.onHiddenChanged(hidden);
if (hidden){
disPatchVisibaleHint(false);
}else {
disPatchVisibaleHint(true);
}
}
到此与懒加载相关的六个方法的逻辑已经处理完毕,详细原理请看代码中的注解,再此不做过多赘述,在step1中的disPatchVisibaleHint
方法中处理ViewPager嵌套使用时,对于子ViewPager
中的Frangment处理交给了dispatChChildVisiableState
方法,那这个处理过程是怎么什么样子的呢?请接着往下看。
ViewPager+Fragment的
嵌套使用时子ViewPager的Frangment如何实现懒加载先说原理:很简单,就是根据当父的Fragment对用户的可见状态,去处理子ViewPager中Fragment的事件分发。就是在上面disPatchVisibaleHint
方法中
代码实现:
/**
* ViewPage嵌套使用时处理子的Fragment懒加载逻辑
* 通过getChildFragmentManager();获取子Fragment
* @param visiable
*/
private void dispatChChildVisiableState(boolean visiable){
Log.e(TAG,"dispatChChildVisiableState ===== " + visiable);
FragmentManager childFragmentManager = getChildFragmentManager();
List childFragmentManagerFragments = childFragmentManager.getFragments();
if (childFragmentManagerFragments != null) {
for (Fragment fragment : childFragmentManagerFragments) {
//进行类型校验,只有继承了LazyFragment才进行懒加载的处理
if (fragment instanceof LazyFragment&&!fragment.isHidden()&&fragment.getUserVisibleHint()){
((LazyFragment) fragment).disPatchVisibaleHint(visiable);
}
}
}
}
LazyFragment
的完整代码和下载地址:public abstract class LazyFragment extends Fragment {
public String TAG="---";
View mRootView;
boolean mIsViewCreated=false;//Fragment是否已经创建,
boolean mIsFirstVisiable=true;//Fragment是否第一次可见
boolean mCurrentVisiableState=false;//标记保存Fragment的可见状态,表示当前Fragment是否分发过 可见到不可见 不可见到可见才可以调用 disPatchVisibaleHint 防止重复调用
@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;
}
@Override
public void onResume() {
Log.e(TAG,"onResume");
//只有当前可见的Fragment 才更新shuju 点击home键又返回
if (!mCurrentVisiableState&&getUserVisibleHint()&&!isHidden() ){
disPatchVisibaleHint(true );
}
super.onResume();
}
@Override
public void onPause() {
Log.e(TAG,"onPause");
if (mCurrentVisiableState&&getUserVisibleHint()){
disPatchVisibaleHint(false );
}
super.onPause();
}
@Override
public void onDestroyView() {
Log.e(TAG,"onDestroyView");
super.onDestroyView();
//将所有的变量复位
mIsFirstVisiable=true;
mCurrentVisiableState=false;
mIsViewCreated=false;
}
//与生命周期无关的函数
/**
* 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);
}
}
}
@Override
//与生命周期无关的函数
//在使用`FragmentTransaction`的`hidden` 和`show`方法管理fragment时,是不会走Fragment正常的生命周期,
// 而是走`onHiddenChanged`方法,此方法可以监听Fragment 的显示与隐藏。
public void onHiddenChanged(boolean hidden) {
Log.e(TAG,"onHiddenChanged");
// FragmentTransaction管理Fragment时
super.onHiddenChanged(hidden);
if (hidden){
disPatchVisibaleHint(false);
}else {
disPatchVisibaleHint(true);
}
}
/**
* 分发可见
* 调用的前提:可见--》不可见 或者不可见--》可见
* @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();
//处理子ViewPager嵌套使用时,子fragment懒加载的实现
dispatChChildVisiableState(true);
}else {
onFragmentPause();
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");
}
/**
* ViewPage嵌套使用时处理子的Fragment懒加载逻辑
* 通过getChildFragmentManager();获取子Fragment
* @param visiable
*/
private void dispatChChildVisiableState(boolean visiable){
Log.e(TAG,"dispatChChildVisiableState ===== " + visiable);
FragmentManager childFragmentManager = getChildFragmentManager();
List childFragmentManagerFragments = childFragmentManager.getFragments();
if (childFragmentManagerFragments != null) {
for (Fragment fragment : childFragmentManagerFragments) {
//进行类型校验,只有继承了LazyFragment才进行懒加载的处理
if (fragment instanceof LazyFragment&&!fragment.isHidden()&&fragment.getUserVisibleHint()){
((LazyFragment) fragment).disPatchVisibaleHint(visiable);
}
}
}
}
protected abstract int getLayoutRes();
protected abstract void initView(View rootView);
}
到此为止,懒加载原理和实现讲解结束结束,周末也到此结束,如果文章中有歧义的地方欢迎大家留言指出,共同进步。