美文网首页FragmentAndroidAndroid开发
fragment延迟加载,多方法测试择优

fragment延迟加载,多方法测试择优

作者: Jafir | 来源:发表于2016-11-01 15:53 被阅读786次

    废话不多说,fragment延迟加载,网上有好几种方法,想要求同存异有些耗费时间,如果你不想花时间研究写demo,就往下看吧。如果你想要花时间研究,也可以看一下前面几段就不用往下看了。

    1、所谓的fragment延迟加载,目前为止按类型分有两种:

    • 1)一种是常见的APP主页,activity+fragments ,下面简称af
    • 2)一种是viewpager + fragments,下面简称vf

    2、如果vf按类别分的话,还可以分:

    • 1) 在fragment内部调用延迟加载方法
    • 2)在fragment外部viewpager那层手动调用延迟加载方法

    目前我就查到了这几种,如果还有请留言,感谢。


    先上af代码:

    **activity里面代码**
    
    //hide 和 show才能触发onHidden add不能触发 
    //所以要想做延迟加载 在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add 
    //但是 add了还不行  必须 要commit 一次,否则 如果放在同一个commit执行
    //那么 onCreate 之后就会立马执行onHidden而此时还咩有onCreateView所以会报空指针
       private void initFragments() {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            if (fragmentList.size() == 0) {
                for (int i = 0; i < 3; i++) {
                    MainItemFragment fragment = new MainItemFragment();
                    Bundle bundle = new Bundle();
                    bundle.putInt("type", i + 1);
                    fragment.setArguments(bundle);
                    fragmentList.add(fragment);
                    fragmentTransaction.add(R.id.container, fragment, "fragment" + i);
                    Log.d("debug", ":add :" + i);
                }
            }
            fragmentTransaction.commitAllowingStateLoss();
        }
    
      private void changeFragment(int index) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    //      如果不等于空就初始化
    //        hide 和 show才能触发onHidden add不能触发 所以要想做延迟加载
    //       在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add
            hideAll(fragmentTransaction);
            fragmentTransaction.show(fragmentList.get(index));
            fragmentTransaction.commitAllowingStateLoss();
        }
    
        private void hideAll(FragmentTransaction fragmentTransaction) {
            for (int i = 0; i < fragmentList.size(); i++) {
                if (fragmentList.get(i) != null) {
                    fragmentTransaction.hide(fragmentList.get(i));
                }
            }
        }
    
    **fragment里面代码**
    
     @Override
        public void onHiddenChanged(boolean hidden) {
            super.onHiddenChanged(hidden);
            if (!hidden) {
                delayInit();
            }
            Log.d("debug", type + ":onHiddenChanged" + hidden);
        }
    
     private boolean canLoad = true;
    
      public void delayInit() {
           if (!canLoad) {
                return;
           }
            //todo
      }
    

    代码解释:上面的代码大家可以直接复制粘贴过去用,很方便,只需按需改动一下即可。
    如果是求便捷的同学可以拿走了,没你事儿了,下面是测试过程,就是在写Demo的过程中的收获和需要注意的地方。

    注意:

    • 1、我们的fragment里面 有onCreate,onCreateView,onActivityCreated 这几个主要方法,调用顺序也如此。而我们的加载view必须要放在 onCreateView之后,因为你看在onCreateView之后才有根布局,如果根布局都没有,那就会报空指针之类的错误。
    • 2、hide 和 show 才能触发onHidden,并且是在状态改变的时候。而 add不能触发 。

    • 3、需要两次commit ,一次是在init初始化的时候,需要把所有fragment add进去,然后commit一次,然后另一次是在changeFragment的时候commit.,并且,在show之前最好是把所有的fragment都hide掉(图方便,其实代码里面循环里做了判断的)

    **log代码 **


    这是第一次commit,执行add 1\2\3 验证一个道理:
    **add 是不会让fragment执行 onCreate onCreatView 等一系列生命周期方法的 **
    第二次commit, 执行了生命周期方法,验证一个道理:
    只有当fragment show出来的时候才会执行其生命周期方法

    回到上面那个问题,如果只有一次commit,我当初就是为了图方便,直接把initFragment写在changeFragment里面同一个transaction,结果:

        private void changeFragment(int index) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    //      如果不等于空就初始化
    //        hide 和 show才能触发onHidden add不能触发 所以要想做延迟加载
    //       在onHidden里面处理的话 需要初始化的时候就把所有的fragment都add
            if (fragmentList.size() == 0) {
                for (int i = 0; i < 3; i++) {
                    MainItemFragment fragment = new MainItemFragment();
                    Bundle bundle = new Bundle();
                    bundle.putInt("type", i + 1);
                    fragment.setArguments(bundle);
                    fragmentList.add(fragment);
                    fragmentTransaction.add(R.id.container, fragment, "fragment" + i);
                    Log.d("debug", ":add :" + i);
                }
            }
            hideAll(fragmentTransaction);
            fragmentTransaction.show(fragmentList.get(index));
            fragmentTransaction.commitAllowingStateLoss();
        }
    
    **如图,onHiddenChanged方法在onCreate之后 在onCreateView之前了**

    这样的话,第一次是不会执行延迟加载方法的,进入是一片空白。只有所有的都add一遍了,然后再来切换,也就是commit add 所有fragment了一回,再commit change 才会执行延迟加载方法,呈现出布局。

    OK以上的就是对于af这种类型的测试精简结果。


    对于vf

    鄙人拙见,所谓的延迟加载,如果在Viewpager里面的话,如果在不修改viewpager源码设置 defaultlimit = 0 的情况下,无论如何都会预加载1页,也就是每次都会最多加载2页的数据,也就是说 延迟加载 如果只有3个fragment 最多 可以节约1个fragment的数据内存, 如果是4个话可以节约2个,也就是n-2 个数据内存。(如果有不同看法的,快点留言!我渴望你的答案)

    第一种:内部调用

     @Override
        public void setUserVisibleHint(boolean isVisibleToUser) {
            super.setUserVisibleHint(isVisibleToUser);
            if (getUserVisibleHint()) {
                isVisible = true;
                onFragmentVisible();
            } else {
                isVisible = false;
                onFragmentInvisible();
            }
            Log.d("debug", type + ":setUserVisibleHint" + isVisibleToUser);
        }
    //这里通过判断view是否隐藏或者显示状态来执行延迟加载方法
        private void onFragmentInvisible() {
            Log.d("debug", type + ":onFragmentInvisible" + isVisible);
        }
    
        private void onFragmentVisible() {
            Log.d("debug", type + ":onFragmentVisible" + isVisible);
            if (canLoad && isFirstLoad) {
                delayInit();
    
            }
        }
    
    
        public void delayInit() {
            isFirstLoad = false;
            textView = new TextView(mContext);
            textView.setText(type + "");
    
            ToggleButton button = new ToggleButton(mContext);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                }
            });
    
            handler.sendEmptyMessageDelayed(0, 2000);
    
    
            button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if (!isChecked) {
                        setUserVisibleHint(isChecked);
                    } else {
                        setUserVisibleHint(isChecked);
                    }
                }
            });
            mRoot.addView(textView);
            mRoot.addView(button);
        }
    
     @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            Log.d("debug", type + ":onCreateView");
            if (mRoot == null) {
                mRoot = new LinearLayout(container.getContext());
            }
            if (isFirstLoad) {
                delayInit();
            }
            return mRoot;
        }
    

    解释一下:上面有一些条件变量isFirstLoad、canLoad

    • isFirstLoad是判断是否是第一次加载,如果是第二次的话就不会再加载了。因为都已经加载过了。但是问题来了,viewpager如果不设置setOffscreenLimit(list.size() )的话,默认至少会预加载1页,那么,如果所有的3页都已经加载了,切换的时候会发生什么呢?
    第三页切换第二页,第一页也依旧执行了onCreateVIew
    所以,fragment除了onCreate只执行一次之外,onCreateView如果预加载的话也是再执行的。再具体一点:因为现在在第3页,会缓存第2页,但是第一页没有缓存已经destroyVIew了(因为limit == 1,如果是2那么第1页也缓存),而点击切换到第2页,有要默认预加载1页,第3页已经在缓存里面,而第1页没有,所以要执行onCreateView方法。
    我们的isFirstLod就是为了避免多次加载
    • canLoad 这个就是比较重点的地方了。看上面的代码,canLoad默认是false,只有在onCreate里面才设置为true.为什么呢?(将引出一个问题!)
      我们来看看如果没有canLoad的话log:

    注意: setUserVisibleHint 居然在onCreate前面执行了!!
    这是我测试时,万万没有想到的,找了半天的源码看看哪里调用,终于找到:它在FragmentPageAdapter里面被调用


    所以一定要小心了。canLoad就是为了解决这个问题,只有在onCreate(因为紧接着就是跟的onCreateView)或者onCreateView(最好这这里)之后才能去执行延迟加载方法

    还有一点要注意:

     @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            Log.d("debug", type + ":onCreateView");
            if (mRoot == null) {
                mRoot = new LinearLayout(container.getContext());
            }
            if (isFirstLoad) {
                delayInit();
            }
            return mRoot;
        }
    

    onCreateView里面,有一个对mRoot的判空,这里是可以判空的,因为已经执行过一次之后,就别再多次执行了,直接返回

    第二种:外部调用
    这种是群里朋友给我说的思路:给viewpager设置onPageChanged监听,onSelected里面 去调用延迟加载方法,并且把延迟加载方法public出来。

    多的代码我就不上了,fragment里面差不多,其余代码也相差不大。有一点就是:默认的第一页你要在fragment里面去调用加载方法,其实也需要设置 isFirstload 、canload这些变量,实现也跟上面的方法差不多,就只是 调用方法一个在onselected里, 一个在setUserVisibleHint里。据我demo来看,没有啥区别和优势(如果,我说错了,有更好的方法,快点留言!我渴望你的答案,也希望学习到)

    总结

    研究了一下写了个demo,如果想要demo的话,那分享一下 github
    吧。testdelayloadfragment这个module下的。demo里面很乱很杂,我不建议你pull下来。。。
    最后,希望和热爱研究的小伙伴一起交流讨论!

    相关文章

      网友评论

      • 989c0f6ce0e3:其实我很想知道作者的编辑器用的是什么字体…😳
        Jafir:@二锅紫菜 http://jafir-my-love.oss-cn-shanghai.aliyuncs.com/androidstudioSettings.jar 分享给你
        Jafir: @二锅紫菜 可以分享给你

      本文标题:fragment延迟加载,多方法测试择优

      本文链接:https://www.haomeiwen.com/subject/tyrmuttx.html