Android | Fragment.setUserVisibleHint()懒加载过时问题优化

简介: Fragment.setUserVisibleHint()懒加载过时问题优化

ViewPager+FragmentPagerAdapter 被标记为过时

Tab页是绝大多数项目中很常见的样式了,如果Tab采用的是ViewPager+FragmentPagerAdapter的方式来布局,使用如下:

val mViewPager: ViewPager by id(R.id.m_pager)
val mViewPagerAdapter = ChapterViewPagerAdapter(childFragmentManager)
mViewPager.adapter = mViewPagerAdapter

注:其中ChapterViewPagerAdapter对应的FragmentPagerAdapter实现。

源码浅析

FragmentPagerAdapter点进去发现目前的使用方式已经被标记为过时了,源码如下:

private final int mBehavior;

@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

之前使用的一个参数的构造方法已经被标记位Deprecated了,系统推荐使用2个参数的构造方法,所以直接来看第2个参数的含义是什么:

@Retention(RetentionPolicy.SOURCE)
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }

@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;

public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

FragmentPagerAdapter构造方法第2个参数传入的是一个int值并且只能传入上面对应的两个值:BEHAVIOR_SET_USER_VISIBLE_HINT、BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT

FragmentPagerAdapter(FragmentManager fm)默认给我们传入的是BEHAVIOR_SET_USER_VISIBLE_HINT,此值也被标记为Deprecated了,重点来看BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT的含义,先来看一下被赋值的mBehavior都在哪里使用了,按关键字搜一下:

@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            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) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        }

        mCurrentPrimaryItem = fragment;
    }
}

instantiateItem()是初始化Fragment时调用的方法,setPrimaryItem()是替换显示的Fragment时执行,逻辑很简单,以instantiateItem()为例,
看下mBehavior做了哪些改动:

if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
   mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
 } else {
   fragment.setUserVisibleHint(false);
}

可以看到如果FragmentPagerAdapter构造函数中传入的是BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则会执行FragmentTransaction.setMaxLifecycle(),否则会执行我们熟悉的Fragment.setUserVisibleHint()方法,继续看setMaxLifecycle()用来干什么的。

@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
        @NonNull Lifecycle.State state) {
    addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
    return this;
}

第2个参数传入的是Lifecycle.State,其实是依赖 Jetpack Lifecycle 生命周期来管理Fragment 的状态,顾名思义,setMaxLifecycle是为了设置Fragment的最大状态、

  • Fragment状态值INITIALIZING、CREATED、ACTIVITY_CREATED、STARTED、RESUMED
  • Lifecycle State:生命周期状态,包括DESTROYED、INITIALIZED、CREATED、STARTED、RESUMED ,两者关系:

Fragment与Lifecycle关系
FragmentTransaction.setMaxLifecycle()参数传入Lifecycle.State.STARTED为例:

  • Lifecycle.State.STARTED对应FragmentSTARTED状态,如果当前Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以当前Fragment状态为CREATED为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()方法;
  • 如果当前Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()方法。
  • 如果当前Fragment的状态恰好为STARTED,那么就什么都不做。

结论

看到这里,基本知道如何替换setUserVisibleHint()了,列一下结论:

  • 使用FragmentPagerAdapter时直接使用两个参数的构造方法 FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
  • 本质上是通过FragmentTransaction的setMaxLifecycle()方法来替代setUserVisibleHint()方法实现Fragment的懒加载效果。instantiateItem()setMaxLifecycle()设置的Fragment状态为STARTED,即通过ViewPager.offscreenPageLimit 设置提前初始化时,临近的Fragment最多执行到onStart()方法,不会再执行onResume()方法了。
  • 如果需要Fragment只执行一次对应逻辑且Fragment重新加载View时需要重置,之前通常会通过setUserVisibleHint(isVisibleToUser: Boolean)里通过isVisibleToUser以及自定义的isFirstLoad去判断,现在可以直接将逻辑写到onResume中,形如:
private var isFirstLoad: Boolean = true //是否第一次加载
       
@Override
public void onResume() {
    super.onResume();
    if (isFirstLoad) {
        //......
        isFirstLoad = false;
    }
}

//对应Fragment的CREATED状态
@Override
public void onDestroyView() {
    super.onDestroyView();
    isFirstLoad = true;
} 

举个栗子

//LazyViewPagerAdapter.kt
class LazyViewPagerAdapter(fm: FragmentManager) :
    FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    override fun getCount(): Int = 3

    override fun getItem(position: Int): Fragment {
        return LazyFragment.newInstance(position.toString())
    }
}


//MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var mViewPager: ViewPager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mViewPager = findViewById(R.id.view_pager)
        val adapter = LazyViewPagerAdapter(supportFragmentManager)
        mViewPager.adapter = adapter
        mViewPager.offscreenPageLimit = 1 //默认是1  小于1的时候会被置为1
    }
}


//LazyFragment.kt
class LazyFragment : Fragment() {
    private var position: String? = null
    private var param2: String? = null
    private lateinit var mTvContent: TextView

    override fun onAttach(context: Context) {
        super.onAttach(context)
        arguments?.let {
            position = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
        log("Fragment$position: onAttach()")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        log("Fragment$position: onCreate()")
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_lazy, container, false)
        mTvContent = view.findViewById(R.id.tv_content)
        mTvContent.text = position.toString()
        log("Fragment$position: onCreateView()")
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        log("Fragment$position: onActivityCreated()")
    }

    override fun onStart() {
        super.onStart()
        log("Fragment$position: onStart()")
    }

    override fun onResume() {
        super.onResume()
        log("Fragment$position: onResume()")
    }

    override fun onPause() {
        super.onPause()
        log("Fragment$position: onPause()")
    }

    override fun onStop() {
        super.onStop()
        log("Fragment$position: onStop()")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        log("Fragment$position: onDestroyView()")
    }

    override fun onDestroy() {
        super.onDestroy()
        log("Fragment$position: onDestroy()")
    }

    override fun onDetach() {
        super.onDetach()
        log("Fragment$position: onDetach()")
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        log("Fragment$position: setUserVisibleHint()->$isVisibleToUser")
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String = "") =
            LazyFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, "")
                    position = param1
                }
            }
    }
}

不传BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时

LazyViewPagerAdapter构造参数中没有传 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,日志如下:
执行结果
可以看到,setUserVisibleHint()会执行,且当前显示Fragment0的时候,Fragment1onResume()也执行了。

传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时

LazyViewPagerAdapter构造参数中传入BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数,日志如下:

执行结果

可以看到,如果把BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数去掉,setUserVisibleHint()不再执行,且当前显示Fragment0的时候,Fragment1的执行到onStart()之后不再执行。对应了instantiateItem()mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED)

其他

测试中的Fragmentandroidx.fragment:fragment:1.1.0版本,且使用的是ViewPager。在Fragment高版本(如测试使用1.3.6版本)中,FragmentPagerAdapter整个类已经被标记为过时了,推荐直接使用ViewPager2实现懒加载效果。

相关文章
|
8月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1018 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
9月前
|
存储 消息中间件 人工智能
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
583 10
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4962 77
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
287 10
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
473 1
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
352 20
Android经典面试题之图片Bitmap怎么做优化
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
445 33
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
574 31
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
309 4
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。

热门文章

最新文章