前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android面试题避坑:ViewPager中的Framgent如何实现懒加载?

Android面试题避坑:ViewPager中的Framgent如何实现懒加载?

原创
作者头像
Android技术干货分享
修改2021-05-13 18:11:29
9080
修改2021-05-13 18:11:29
举报
文章被收录于专栏:Android技术分享

问:ViewPager中的Fragment如何实现懒加载?

当被问到上述问题时,很多人可能首先会想到借助setUserVisiblity实现

如下,当Fragment可见时调用 onVisible 从而实现异步加载

代码语言:javascript
复制
@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,这将带来如下好处:

  1. 基于 Lifecycle 的懒加载更加科学,可以配合 Livedata 等组件在MVVM架构中使用
  2. setMaxLifeCycle 无需额外定义 Fragment 基类,使用起来更加无侵

使用 setMaxLifecycle 进行懒加载

FragmentPagerAdapter 的构造方法新增了一个 behavior 参数, 当被设置为FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时,会通过setMaxLifecycle 来限制 Fragment 的生命周期:只有当 Fragment 显示在屏幕中时才执行onResume()

这样就可以把加载数据等处理放在 onResume() 中从而实现懒加载了。

代码如下:

代码语言:javascript
复制
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。

代码语言:javascript
复制
//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, 专门用来设置生命周期的上限。

代码语言:javascript
复制
@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 列表

代码语言:javascript
复制
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,以标记其生命周期上限

代码语言:javascript
复制
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 生命周期的:

代码语言:javascript
复制
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类似
                   //...
                }
            }
        }
        ...
    }
    ...
}
代码语言:javascript
复制
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文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

刷大厂面试题备战,增加大厂通过率

历时半年,整理了这份市面上最全面的安卓面试题解析大全。

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。 2.五角星数表示面试问到的频率,代表重要推荐指数

以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问:ViewPager中的Fragment如何实现懒加载?
  • 使用 setMaxLifecycle 进行懒加载
  • setMaxLifecycle 实现原理
    • OP_SET_MAX_LIFECYCLE
      • FragmentStateManager
      • 最后
        • 面试前复习路线参考
        相关产品与服务
        云开发 CloudBase
        云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档