面试官: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 年的今天还这么回答可能就不过关了。
https://android-review.googlesource.com/c/platform/frameworks/support/+/945776
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()
的实现原理?
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 列表,设置 Fragment 的 mMaxState
表明其被允许的最大生命周期。
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;
//...
}
}
mMaxState
的设置是通过在 FragmentManager 中的同名方法 setMaxLifeCycle()
完成的。
void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state) {
//...
f.mMaxState = state;
}
FragmentStateManager
调用 setMaxLifecycle()
之后, FragmentManager 会通过 FragmentStateManager 对 Fragment 生命周期做出限制。
值得一提的是,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()
中的一些操作。
Tips:Fragment 1.3.0-rc01 已经支持设置最大生命周期为 INITIALIZED。
-- End --
Android启动优化:合并三方SDK的多个FileProvider
Flutter的const关键字,加载Widget前代表什么?
本文对你有帮助吗?留言、转发、点好看是最大的支持,谢谢!