Fragment 的懒加载分析(延迟加载数据,性能优化)


Fragment 使用已经很广泛了,它的生命周期比Activity的稍微复杂点,Fragment 不是 View,它的职责,更像是 MVP 模式中的 P。Fragment 经常嵌套于Activity中,或与 ViewPager 配套使用;ViewPager + Fragment 应该是最常用的一种使用方法,但也会引出一个问题,那就是 Fragment 延迟加载的优化。


ViewPager 是有预加载机制的,默认提前加载下一个,这就导致在当前页面,会执行下一个页面Fragment的 onCreateView() 和 onResume() 方法,这就有点坑了,如果我们在上面两个方法中加载数据,就提前加载并刷新页面了,在底端手机上可能会造成UI的不流畅,或浪费了一次请求,或者一些根据生命周期回调做的页面展示统计,可能就不准确了。这里还有一个方法要提及 setUserVisibleHint(),这个方法是根据 Fragment 的展示或隐藏回调的,关于它的逻辑,可以参考FragmentPagerAdapter 类。

如果做懒加载呢?做个功能总是要有逻辑分析的,那么就一步步处理吧。
一、在 onCreateView() 方法中初始化布局及控件的初始化 initView(),为了防止布局多次初始化,可以针对根布局 mRootView 做个非空判断,初始化一次;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(mRootView == null){
            View view = inflater.inflate(R.layout.fragment_tab_sticky, container, false);
            initView();
            mRootView = view;
        } else {
            ViewGroup mParent = (ViewGroup) mRootView.getParent();
            if (mParent != null) {
                mParent.removeView(mRootView);
            }
        }
        return mRootView;

    }

二、如果在 onCreateView() 方法中加载数据,即使在 initView() 方法中,那么在 ViewPager 这种情况下,仍然会预加载,那么我们就考虑另外一个方法 setUserVisibleHint(),我们可以在这个方法中来进行数据请求;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
    }

三、假如说 ViewPager 中添加了5个 Fragment,那么第一个Fragment被加载时,会先执行  setUserVisibleHint() 方法回调,然后才是 onCreateView() 方法,并且 setUserVisibleHint() 方法会执行多次,如果直接在 setUserVisibleHint(boolean isVisibleToUser) 方法中根据 isVisibleToUser 的值进行数据请求并刷新UI,则大概率会报控件的空指针。 (可以自己写个日志打印下)


四、可以在 onCreateView() 中添加一个 mRootView 初始化的标志 boolean mIsViewCreated,在 setUserVisibleHint() 方法中,只有 mIsViewCreated 为 true,才进入数据请求的判断。但由于 setUserVisibleHint(boolean isVisibleToUser) 会多次调用,所以判断还要加上 isVisibleToUser 的值。

    boolean mIsViewCreated = false;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ... // 布局初始化
        mIsViewCreated = true;
        return mRootView;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(mIsViewCreated){
            if(isVisibleToUser){
                takeData(); // 数据获取
            }
        }
    }

五、第一个Fragment被加载,setUserVisibleHint(boolean isVisibleToUser) 会执行两次,并且都是在 onCreateView() 之前,所以按照上面的写法,第一个Fragment不会执行数据加载的逻辑,所以可以在 onCreateView() 中在布局初始化之后,并且是可视的情况下,主动调用 setUserVisibleHint() 方法。

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ... // 布局初始化
        mIsViewCreated = true;
        if(getUserVisibleHint()){
            setUserVisibleHint(true);
        }
        return mRootView;
    }

以上初步完成了数据的懒加载,但有几个问题:1、第一个Fragment,或者ViewPager左右反复滑动,takeData() 方法会多次执行,还需要加入其它判断条件;2、我们做的这个懒加载,不止是数据,还有其他也可以延迟加载,所以这个地方可以与业务逻辑剥离,takeData() 代码的地方,做成界面展示获取焦点的方法;3、既然有获取焦点的方法,那么最好也提供失去焦点的方法,便于业务逻辑;4、我们没考虑 Activity 被覆盖,回到桌面,重新进来 这些场景,此时 setUserVisibleHint() 和 onCreateView() 都不会执行,所以还需要考虑 onResume() 方法。

六、重写定义一个方法 dispatchVisibleHintState(boolean state) ,意思是展示或隐藏界面,在这个方法中,我们定义写具体的细节。

    private void dispatchVisibleHintState(boolean state){
        if(state){
            dispatchShow();
        }else{
            dispatchDis();
        }
    }

    private void dispatchShow() {
      // 获取焦点,显示UI
    }

    private void dispatchDis() {
      // 失去焦点,隐藏UI
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(mIsViewCreated){
            dispatchVisibleHintState(isVisibleToUser);
        }
    }

七、 setUserVisibleHint() 方法中的逻辑需要修改了,可以再添加一个成员变量 mCurrentVisibleState,默认 false, 它的意思就是当前是否正在显示,放在 dispatchVisibleHintState(boolean state) 中接收 state 的值。每个页面加载数据,我们需要保证该页面是可见的,并且 mCurrentVisibleState 是false,说明没加载过,此时调用 dispatchVisibleHintState(true) 方法,改变 mCurrentVisibleState 的值,这样问题1便被解决了; 失去焦点的前提是获取过焦点,所以失去焦点的判断则与获取焦点的判断相反,见代码

    boolean mCurrentVisibleState = false;
    
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(mIsViewCreated){
            if(!mCurrentVisibleState && getUserVisibleHint()){
                dispatchVisibleHintState(true);
            } if(mCurrentVisibleState && !getUserVisibleHint()){
                dispatchVisibleHintState(false);
            }
        }
    }

    private void dispatchVisibleHintState(boolean state){
        mCurrentVisibleState = state;
        if(state){
            dispatchShow();
        }else{
            dispatchDis();
        }
    }

八、问题4提到的地方,在这些场景中,需要用到 onResume() 和 onPause() 方法,这些场景都是获取过焦点,然后又失去了焦点,此时需要重新获取。当Activity切入后台,重新切回时,ViewPager 中当前页面和下一个页面的 Fragment 都会执行 onResume() 方法,那么我们就需要同时判断 mCurrentVisibleState 为false 且 getUserVisibleHint()为true的情况了,因为切入前台后,才会执行 onResume() 方法,此时当前 Fragment 是可见的,下一个 Fragment 则是不可见的; 同理,当app切入后台时,mCurrentVisibleState必须是true,并且 getUserVisibleHint() 也得是true,应为改页面是从可见变为不可见。

    @Override
    public void onResume() {
        super.onResume();
        if(!mCurrentVisibleState && getUserVisibleHint()){
            dispatchVisibleHintState(true);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if(mCurrentVisibleState && getUserVisibleHint()){
            dispatchVisibleHintState(false);
        }
    }


    
注意,setUserVisibleHint(boolean isVisibleToUser) 方法中的两种状态,判断条件的相反的;onResume() 和 onPause() 中,getUserVisibleHint() 都是true,必须是可见的,然后根据 mCurrentVisibleState 状态来判断,这样这两个方法与 setUserVisibleHint(boolean isVisibleToUser) 都调用dispatchVisibleHintState()方法,但不会引起冲突。以上就是 dispatchShow() 获取界面,dispatchDis() 失去界面, 我们可以在里面做相应的数据及界面UI刷新;有些数据是一次性的,可以加boolean值,有些是根据时间限制来的,比如说再次来到该页面,与上次相比,超过15分钟就重新获取数据,都可以在这个里面处理逻辑。

 

最后上个稍微完整点的代码

 

Fragment:


    View mRootView;
    boolean mIsViewCreated = false; // 其实可以用 mRootView == null 来代替这个属性,这里是为了逻辑分隔
    boolean mCurrentVisibleState = false;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        if(mRootView == null){
            View view = inflater.inflate(R.layout.fragment_tab_sticky, container, false);
            initView(view);
            mRootView = view;
        } else {
            ViewGroup mParent = (ViewGroup) mRootView.getParent();
            if (mParent != null) {
                mParent.removeView(mRootView);
            }
        }

        mIsViewCreated = true;
        if(getUserVisibleHint()){
            setUserVisibleHint(true);
        }
        return mRootView;

    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(mIsViewCreated){
            if(!mCurrentVisibleState && getUserVisibleHint()){
                dispatchVisibleHintState(true);
            } else if(mCurrentVisibleState && !getUserVisibleHint()){
                dispatchVisibleHintState(false);
            }
        }
    }


    @Override
    public void onResume() {
        super.onResume();
        if(!mCurrentVisibleState && getUserVisibleHint()){
            dispatchVisibleHintState(true);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if(mCurrentVisibleState && getUserVisibleHint()){
            dispatchVisibleHintState(false);
        }
    }

    private void dispatchVisibleHintState(boolean state){

        mCurrentVisibleState = state;
        if(state){
            dispatchShow();
        }else{
            dispatchDis();
        }
    }

    private void dispatchShow() {

    }


    private void dispatchDis() {

    }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值