Android面试题避坑:ViewPager中的Framgent如何实现懒加载?
Posted Young-G2333
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试题避坑:ViewPager中的Framgent如何实现懒加载?相关的知识,希望对你有一定的参考价值。
问:ViewPager中的Fragment如何实现懒加载?
当被问到上述问题时,很多人可能首先会想到借助setUserVisiblity
实现
如下,当Fragment可见时调用 onVisible
从而实现异步加载
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInVisible();
}
}
放在两年前,这个答案是OK的,但是2021年的今天还这么回答可能就不过关了。
androidX 自 1.1.0-alpha07 起, 为 FragmentTransaction
增加了新的方法 setMaxLifeCycle
, 官方建议开发者以此取代setUserVisibleHint,这将带来如下好处:
- 基于 Lifecycle 的懒加载更加科学,可以配合 Livedata 等组件在MVVM架构中使用
- setMaxLifeCycle 无需额外定义 Fragment 基类,使用起来更加无侵
使用 setMaxLifecycle 进行懒加载
FragmentPagerAdapter 的构造方法新增了一个 behavior
参数, 当被设置为FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
时,会通过setMaxLifecycle
来限制 Fragment 的生命周期:只有当 Fragment 显示在屏幕中时才执行onResume()
。
这样就可以把加载数据等处理放在 onResume() 中从而实现懒加载了。
代码如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
val viewPager: ViewPager = findViewById(R.id.viewpager)
val fragmentList: MutableList<Fragment> = ArrayList()
fragmentList.add(Fragment1())
fragmentList.add(Fragment2())
fragmentList.add(Fragment3())
// 为MyPagerAdapter适配器设置FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 参数
val myPagerAdapter: MyPagerAdapter = MyPagerAdapter(
getSupportFragmentManager(),
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, fragmentList
)
viewPager.setAdapter(myPagerAdapter)
// 设置预加载为3页,来测试懒加载是否成功
viewPager.offscreenPageLimit = 3
}
class MyPagerAdapter(
fm: FragmentManager,
behavior: Int,
val fragmentList: List<Fragment>
) :
FragmentPagerAdapter(fm, behavior) {
override fun getCount() = fragmentList.size
override fun getItem(position: Int) = fragmentList[position]
}
}
FragmentPagerAdapter 在创建 Fragment后,根据 behavior 调用了setMaxLifecycle。
//FragmentPagerAdapter.java
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
...
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
// mBehaviour为1的时候走新逻辑
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
// 初始化item时将其生命周期限制为STARTED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
// 兼容旧版逻辑
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
...
// 滑走的会变成非主item, 设置其Lifecycle为STARTED
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
...
// 设置新滑到的主item的Lifecycle为RESUMED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
不借助 behavior,在自定义Adapter中构建 Framgent时直接调用setMaxLifecycle 也是等价的。
setMaxLifecycle 实现原理
setMaxLifecycle 使用方法很简单,接下来通过梳理源码了解一下实现原理(基于1.3.0-rc01),即使面试官追问其原理你也能沉着应对。
OP_SET_MAX_LIFECYCLE
我们知道 FramgentTransition 对 Fragment 的所有操作都将转换为一个Op,针对setMaxLifecycle也同样增加了一个新的Op -- OP_SET_MAX_LIFECYCLE
, 专门用来设置生命周期的上限。
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
当 FramgentTransition 对 Frament 添加了 OP_SET_MAX_LIFECYCLE 后,在实现类 BackStackRecord 中, FragmentManager 会遍历 Transaction 的 Op 列表
void executeOps() {
final int numOps = mOps.size();
for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = mOps.get(opNum);
final Fragment f = op.mFragment;
//...
switch (op.mCmd) {
//...
// 新引入的这个Op类型, 在这里会给这个Fragment设置允许的生命周期上限
case OP_SET_MAX_LIFECYCLE:
mManager.setMaxLifecycle(f, op.mCurrentMaxState);
break;
//...
}
}
当遇到 OP_SET_MAX_LIFECYCLE 时,通过调用 FragmentManager 的 setMaxLifeCycle 方法设置 fragment 的 mMaxState,以标记其生命周期上限
void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state) {
//...
f.mMaxState = state;
}
FragmentStateManager
FragmentManager 通过 FragmentStateManager 推进 Fragment 的生命周期。 推进过程中根据 mMaxState 对生命周期
值得一提的是,FragmentStateManager 是 1.3.0-alpha08 之后新增的类,将原来和 State 相关的逻辑从FragmentManager 抽离了出来, 降低了与 Fragment 的耦合, 职责更加单一。
看一下在 FragmentStateManager 中具体是如何推进 Fragment 生命周期的:
void moveToExpectedState() {
try {
...
// 循环计算声明周期是否可以推进
while ((newState = computeExpectedState()) != mFragment.mState) {
if (newState > mFragment.mState) {
// 生命周期向前推进
int nextStep = mFragment.mState + 1;
//...
switch (nextStep) {
//...
case Fragment.ACTIVITY_CREATED:
//...
case Fragment.STARTED:
start();
break;
//...
case Fragment.RESUMED:
resume();
break;
}
} else {
// 如果应有的生命周期小于当前, 后退
int nextStep = mFragment.mState - 1;
//...
switch (nextStep) {
// 与上面的switch类似
//...
}
}
}
...
}
...
}
int computeExpectedState() {
// 其他计算expected state的逻辑, 算出maxState
//...
// mMaxState 对生命周期做出限制
switch (mFragment.mMaxState) {
case RESUMED:
break;
case STARTED:
maxState = Math.min(maxState, Fragment.STARTED);
break;
case CREATED:
maxState = Math.min(maxState, Fragment.CREATED);
break;
default:
maxState = Math.min(maxState, Fragment.INITIALIZING);
}
// 其他计算expected state的逻辑, 算出 maxState
// ...
return maxState;
}
整体流程图如下
最后
除了使用默认的 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,我们甚至可以在自定义 Adapter 的instantiateItem 中为将 Fragment的 MaxLifecycle 设置为 CREATED
, 这样可以让 Fragment 只走到onCreate 从而延迟更多操作,比如在 onCreateView 中的 inflate 以及 onViewCreated 中的一些操作。 Fragment 1.3.0-rc01 已经支持设置最大生命周期为 INITIALIZED
面试前复习路线参考
接下来分享的系统学习资源以详解各大互联网公司的 Android 常见面试题为主线,从面试的角度带你介绍必备知识点,以及该知识点在项目中的实际应用。
帮你在现在的基础上,重新梳理和建立 Android 开发的知识体系。无论是你短期内想提升 Android 内功实力,突破自己工作中的能力瓶颈,还是准备参加 Android 面试,都会在这份资料中有所一些收获。
从架构基础开始,分了8个模块来逐步从基础进阶到架构师的环节:
多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!
首先是超级详细得不能再详细的Android开发学习思维导图,因为图片实在是太大了,所以我就只把二级目录的内容放出来,更加详细的你们可以点击这里。
接下来就需要梳理知识,提升储备了!(Android移动架构师七大专题学习资源)
- 架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO
- Android高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化
- 360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化
- 解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack
- NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发
- 微信小程序:小程序介绍+UI开发+API操作+微信对接
- Hybrid 开发与Flutter:html5项目实战+Flutter进阶
知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。
然后再是通过源码来系统性地学习
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《486页超全面Android开发相关源码精编解析》
刷大厂面试题备战,增加大厂通过率
历时半年,整理了这份市面上最全面的安卓面试题解析大全。
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《379页Android开发面试宝典》
以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。
最后还有耗时一年多整理的一系列Android学习资源:Android源码解析、Android第三方库源码笔记、Android进阶架构师七大专题学习、历年BAT面试题解析包、Android大佬学习笔记
等等,这些内容均免费分享给大家。
以上是关于Android面试题避坑:ViewPager中的Framgent如何实现懒加载?的主要内容,如果未能解决你的问题,请参考以下文章
面试题:ViewPager中的Framgent如何实现懒加载?
Android ViewPager切换之PageTransformer接口中transformPage方法解析
android ViewPager+Fragment 如何在ViewPager的Activity中获取Fragment中的控件对象