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


    ViewPager+FragmentPagerAdapter 被标记为过时

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

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

    注:其中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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    之前使用的一个参数的构造方法已经被标记位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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

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

    if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
       mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
     } else {
       fragment.setUserVisibleHint(false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到如果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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    举个栗子

    //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
                    }
                }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    不传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实现懒加载效果。

  • 相关阅读:
    ICML-2022 | 强化学习论文清单(附链接)
    web share api 分享
    高斯模糊的细枝末节
    力扣每日一题-第31天-13.罗马数组转整数
    【数据结构】选择排序
    【四】3D Object Model之创建Creation——read_object_model_3d()算子
    哪吒X选车指南:推荐哪吒X 500lite 版
    Node学习十八 —— Node调试器和检查器
    C++项目实战——基于多设计模式下的同步&异步日志系统-⑫-日志宏&全局接口设计(代理模式)
    pycharm plot不显示的问题
  • 原文地址:https://blog.csdn.net/u013700502/article/details/127653482