Android仿微信Viewpager-Fragment惰性加载(lazy-loading)

发布时间 - 2026-01-11 02:50:42    点击率:

前言

今天起床,拿起手机开机第一时间当然是打开微信了,左右滑动Viewpager,发现它使用了一种叫惰性加载,或者说懒加载(lazy-loading)的方式加载Viewpager中的Fragment。效果如图:

什么是lazy-loading呢?顾名思义就是在必要的时候才加载,否则不进行View的绘制和数据的加载。原因是Viewpager一次只会显示一个页卡,那么刚开始的时候,只需加载第一张Fragment页卡,其他的不加载,当用户向右滑动切换再进行加载。因为其他Fragment对于用户来说是不可见的,如果一开始就把全部Fragment一起加载,可能造成启动时卡顿的问题,更重要的是可能白白耗费用户的流量,因为用户可能并不需要其他Fragment的信息。

今天Google了有关Fragment惰性加载的资料,并没有找到介绍得清楚详细的博文+demo。所以我找到了Github上的一个开源项目demo里有关惰性加载的代码,学习了这个知识点,并把它整理出来分享给大家。

你应该知道

你应该知道viewPager.setOffscreenPageLimit();方法。该方法设置ViewPager允许有多少张pages存在于屏幕外(不包括正在显示的page),默认值是1。在范围之外的pages 的View会被销毁,即onDestroyView()会被执行。

Viewpager里面FragmentPagerAdapter、FragmentStatePagerAdapter的区别:

1.FragmentPagerAdapter会将每一个生成的Fragment都放到内存中,即无论怎么滑动切换ViewPager,都不会有一个Fragment的onDestroy方法被调用。但Fragment不在viewPager.setOffscreenPageLimit(3);保护的范围内会调用FragmentManager的detach()方法,相应的Fragment的onDestroyView会执行,但Fragment实例仍在!所以该类适用于需要展示的Fragment比较少的情况。

2.FragmentStateAdapter 有点类似于LIstview的RecyclerBin机制,当Fragment不在viewPager.setOffscreenPageLimit(3);保护的范围内,Fragment的就会被销毁,onDestroy()、onDetach()方法会被执行。适用于要展示Fragment数量比较多,Fragment的子View和数据量复杂的情况。
熟知Fragment的生命周期。

Fragment的生命周期

3.刚被new出来的Fragment并没有开始它的生命周期,当它被添加到FragmentManager时生命周期才开始。

4.我们通常是在onCreateView()中对Fragment完成视图的构建。若是要实现延迟加载,可以在调用onCreateView时获得一个空container的引用。当等待用户切换到屏幕的时候,开始加载数据和视图。

那么如何得知我们的Fragment何时被切换到屏幕呢?核心方法就是getUserVisibleHint()和在Fragment中重写setUserVisibleHint(boolean isVisibleToUser){…}方法。

在官方文档是这样描述该方法的:

public void setUserVisibleHint (boolean isVisibleToUser)
Added in API level 15
Set a hint to the system about whether this fragment's UI is currently visible to the user. This hint defaults to true and is persistent across fragment instance state save and restore.
An app may set this to false to indicate that the fragment's UI is scrolled out of visibility or is otherwise not directly visible to the user. This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.
Parameters
isVisibleToUser true if this fragment's UI is currently visible to the user (default), false if it is not.

该方法的作用是设置一个提示或者标志,该标志代表的是Fragment在当前是否处于对用户的可见状态。注意这里的可见并不能与Activity或Fragment的onStart或者onResume混淆。因为Fragment处于onResume状态并不代表它对用户是可见的!仍觉得很困惑?那我们一起来Log一下吧。

我们把生命周期回调方法加了Log语句。

  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    Log.d("TAG", "setUserVisibleHint() called with: " + "isVisibleToUser = [" + isVisibleToUser + "]");
  }

我们允许有4张页卡的缓存,因为微信是有4个tab的。这样ViewPager来回切换就不会有页卡被销毁了。

viewPager.setOffscreenPageLimit(3);

启动ViewPager后的Log:

D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [true]
D/TAG: onCreateView() : getUserVisibleHint():true
D/TAG: onStart() : getUserVisibleHint():true
D/TAG: onResume() : getUserVisibleHint():true
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onCreateView() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false
D/TAG: onStart() : getUserVisibleHint():false
D/TAG: onResume() : getUserVisibleHint():false

从Log中,可得知,setUserVisibleHint()比onCreateView()先调用,并且只有一个方法的isVisbleToUser==true。由此我们可以断定,正在展示的fragment对应的isVisibleToUser才为true。我们现在有4个page,onCreateView()、onStart()、onResume()分别共调用了4次,由此可知,尽管Fragment没有被展示,ViewPager也会将它们构建起来,会回调onStart、onResume。那么ViewPager初始化时构建Fragment的个数与什么有关呢?这个主要跟使用的Adapter类型和setOffscreenPageLimit()有关。

接下来向右滑,切换到第二页,Log如下:

D/TAG: setUserVisibleHint() called with: isVisibleToUser = [false]
D/TAG: setUserVisibleHint() called with: isVisibleToUser = [true]

这次只会调用两次setUserVisibleHint(),将要刚刚显示的Fragment的isVisibleToUser 设置为false,并把将要显示的Fragment的isVisibleToUser 设置为true。

当我退出程序,Log如下:

D/TAG: onPause() : getUserVisibleHint():true
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onPause() : getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():true
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onStop() called: getUserVisibleHint():false
D/TAG: onDestroyView() : getUserVisibleHint():true
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :
D/TAG: onDestroyView() : getUserVisibleHint():false
D/TAG: onDestroy() :
D/TAG: onDetach() :

从这“死亡日志”中,我们发现,getUserVisibleHint()贯穿着Fragment的凋亡生命线。
到此,对这个关键的方法,我们算是有了一个宏观的认识。

具体实现

那么具体应该怎么实现呢?我们可以在自定义一个抽象类LazyFragment,重写onCreateView()方法,只返回一个简单的,甚至是空的(不是null)的ViewGroup作为Container,比如return new FrameLayout();当然这个ViewGroup我们需要保存为成员变量。接下来重写setUserVisibleHint(boolean isVisibleToUser)方法,如果该Fragment处于用户可见状态,就会调用该方法,并传过来的isVisibleToUser==true。所以根据这个hint做一个判断,若等于true,立即加载原本要正常显示的视图和数据。当然这个方法可以作为一个抽象方法交给子类去实现。具体的实现就是这样!Talk is simple,show you the code!

LazyFragment:

省去了一些,回调方法,只给出了核心的几个方法,完整的可以看文章末尾的项目源码。注释已经写得相对完善,如果有不明白的地方欢迎评论留言。

public class LazyFragment extends BaseFragment {
 private boolean isInit = false;//真正要显示的View是否已经被初始化(正常加载)
 private Bundle savedInstanceState;
 public static final String INTENT_BOOLEAN_LAZYLOAD = "intent_boolean_lazyLoad";
 private boolean isLazyLoad = true;
 private FrameLayout layout;
 private boolean isStart = false;//是否处于可见状态,in the screen

 @Deprecated
 protected final void onCreateView(Bundle savedInstanceState) {
  Log.d("TAG", "onCreateView() : " + "getUserVisibleHint():" + getUserVisibleHint());
  super.onCreateView(savedInstanceState);
  Bundle bundle = getArguments();
  if (bundle != null) {
   isLazyLoad = bundle.getBoolean(INTENT_BOOLEAN_LAZYLOAD, isLazyLoad);
  }
  //判断是否懒加载
  if (isLazyLoad) {
   //处于完全可见、没被初始化的状态,调用onCreateViewLazy显示内容
   if (getUserVisibleHint() && !isInit) {
    this.savedInstanceState = savedInstanceState;
    onCreateViewLazy(savedInstanceState);
    isInit = true;
   } else {
    //进行懒加载
    layout = new FrameLayout(getApplicationContext());
    layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.fragment_lazy_loading, null);
    layout.addView(view);
    super.setContentView(layout);
   }
  } else {
   //不需要懒加载,开门江山,调用onCreateViewLazy正常加载显示内容即可
   onCreateViewLazy(savedInstanceState);
   isInit = true;
  }
 }

 @Override
 public void setUserVisibleHint(boolean isVisibleToUser) {
  super.setUserVisibleHint(isVisibleToUser);
  Log.d("TAG", "setUserVisibleHint() called with: " + "isVisibleToUser = [" + isVisibleToUser + "]");
  //一旦isVisibleToUser==true即可对真正需要的显示内容进行加载

  //可见,但还没被初始化
  if (isVisibleToUser && !isInit && getContentView() != null) {
   onCreateViewLazy(savedInstanceState);
   isInit = true;
   onResumeLazy();
  }
  //已经被初始化(正常加载)过了
  if (isInit && getContentView() != null) {
   if (isVisibleToUser) {
    isStart = true;
    onFragmentStartLazy();
   } else {
    isStart = false;
    onFragmentStopLazy();
   }
  }
 }

 @Override
 public void setContentView(int layoutResID) {
  //判断若isLazyLoad==true,移除所有lazy view,加载真正要显示的view
  if (isLazyLoad && getContentView() != null && getContentView().getParent() != null) {
   layout.removeAllViews();
   View view = inflater.inflate(layoutResID, layout, false);
   layout.addView(view);
  }
  //否则,开门见山,直接加载
  else {
   super.setContentView(layoutResID);
  }
 }

 @Override
 public void setContentView(View view) {
  //判断若isLazyLoad==true,移除所有lazy view,加载真正要显示的view
  if (isLazyLoad && getContentView() != null && getContentView().getParent() != null) {
   layout.removeAllViews();
   layout.addView(view);
  }
  //否则,开门见山,直接加载
  else {
   super.setContentView(view);
  }
 }
}

具体的实现类:

public class MoreFragment extends LazyFragment {
 private TextView tvLoading;
 private ImageView ivContent;
 private int tabIndex;
 public static final String INTENT_INT_INDEX="index";

 public static MoreFragment newInstance(int tabIndex) {

  Bundle args = new Bundle();
  args.putInt(INTENT_INT_INDEX, tabIndex);
  MoreFragment fragment = new MoreFragment();
  fragment.setArguments(args);
  return fragment;
 }
 @Override
 protected void onCreateViewLazy(Bundle savedInstanceState) {
  super.onCreateViewLazy(savedInstanceState);
  setContentView(R.layout.fragment_tabmain_item);
  tabIndex = getArguments().getInt(INTENT_INT_INDEX);
  ivContent = (ImageView) findViewById(R.id.iv_content);
  tvLoading = (TextView) findViewById(R.id.tv_loading);
  getData();
 }

 private void getData() {
  new Thread(new Runnable() {
   @Override
   public void run() {
    //异步处理加载数据
    //...
    //完成后,通知主线程更新UI
    handler.sendEmptyMessageDelayed(1, 2000);
   }
  }).start();
 }

 @Override
 public void onDestroyViewLazy() {
  super.onDestroyViewLazy();
  handler.removeMessages(1);
 }

 private Handler handler = new Handler() {
  public void handleMessage(android.os.Message msg) {
   tvLoading.setVisibility(View.GONE);
   int id=0;
   switch (tabIndex){
    case 1:
     id=R.drawable.a;
     break;
    case 2:
     id=R.drawable.b;
     break;
    case 3:
     id=R.drawable.c;
     break;
    case 4:
     id=R.drawable.d;
     break;
   }
   ivContent.setImageResource(id);
   ivContent.setVisibility(View.VISIBLE);
  }
 };
}


为了简化布局,demo中只用了微信上的几张截图,希望大家能专注重点。具体效果如图:

听说留下完整示例代码和demo是一种美德。(^__^) —Github跳转

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Android  # 微信  # Viewpager  # Fragment  # 惰性加载  # Android仿ios加载loading菊花图效果  # Android通用LoadingView加载框架详解  # Android自定义View实现loading动画加载效果  # Android自定义加载loading view动画组件  # Android实现退出界面弹出提示对话框  # Android加载loading对话框的功能及实例代码(不退出沉浸式效果)  # 加载  # 重写  # 的是  # 就会  # 回调  # 切换到  # 开门见山  # 适用于  # 我们可以  # 只会  # 如图  # 设置为  # 移除  # 你应该知道  # 几个  # 是在  # 会有  # 还没  # 都不  # 也会 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Thinkphp 中 distinct 的用法解析  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  如何用狗爹虚拟主机快速搭建网站?  瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口  Laravel如何实现API速率限制?(Rate Limiting教程)  如何快速上传建站程序避免常见错误?  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  Laravel如何设置自定义的日志文件名_Laravel根据日期或用户ID生成动态日志【技巧】  如何在景安服务器上快速搭建个人网站?  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】  今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】  想要更高端的建设网站,这些原则一定要坚持!  创业网站制作流程,创业网站可靠吗?  昵图网官方站入口 昵图网素材图库官网入口  Laravel如何操作JSON类型的数据库字段?(Eloquent示例)  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  如何在万网开始建站?分步指南解析  如何续费美橙建站之星域名及服务?  Internet Explorer官网直接进入 IE浏览器在线体验版网址  如何有效防御Web建站篡改攻击?  如何用VPS主机快速搭建个人网站?  佛山网站制作系统,佛山企业变更地址网上办理步骤?  Laravel如何与Docker(Sail)协同开发?(环境搭建教程)  济南网站建设制作公司,室内设计网站一般都有哪些功能?  Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)  Laravel如何连接多个数据库_Laravel多数据库连接配置与切换教程  大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?  html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】  详解vue.js组件化开发实践  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  免费视频制作网站,更新又快又好的免费电影网站?  动图在线制作网站有哪些,滑动动图图集怎么做?  Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧  zabbix利用python脚本发送报警邮件的方法  公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?  详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点  常州企业网站制作公司,全国继续教育网怎么登录?  Laravel如何使用模型观察者?(Observer代码示例)  lovemo网页版地址 lovemo官网手机登录  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  宙斯浏览器怎么屏蔽图片浏览 节省手机流量使用设置方法  网站制作免费,什么网站能看正片电影?  Laravel Debugbar怎么安装_Laravel调试工具栏配置指南  如何快速配置高效服务器建站软件?  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  ,交易猫的商品怎么发布到网站上去?  如何用腾讯建站主机快速创建免费网站?  猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?