• ViewPager(三)两个熊孩子天生不一样


    回顾上一篇内容ViewPager(二) Adapter的爱恨情仇,我们了解到ViewPager对页面的加载需要PagerAdapter来辅助,而PagerAdapter中涉及开发者操作的核心四个方法分别是:

    Int getCount() //返回显示的子View数量
    
    Boolean isViewFromObject(View view, Object object) //加载前确认加载类型是否一致
    
    Object instantiateItem(ViewGroup container, int position) //返回具体加载的子元素
    
    Void destroyItem(ViewGroup container, int position, Object object) //提供销毁子view策略
    

    在我们给ViewPager设置适配器,绑定之后,ViewPager在适当的时候会调用Adapter的以上四个方法准确无误的加载需要显示的子View,并且这四个方法都必须提供实现。

    两个PagerAdapter到底有什么不同呢?

    在ViewPager系列第一篇我们也提到,直接继承Viewpager需要实现以上四个方法,并且子View是Fragment的这种情景又比较常见,而Fragment的管理是个麻烦事,意味着Adapter中更多的代码量,针对这种情况,谷歌推荐开发者直接继承,PagerAdapter的两个直接子类FragmentPagerAdapter和FragmentStatePagerAdapter,这样开发者不用关注Fragment的管理,而且只需要提供两个方法,就行了。

    按照谷歌给的提示,这两个子类主要是Fragment内存管理状态的不同,为了验证,我们在Fragment的生命周期中添加Log,贴出其中一个Fragment的代码如下:

    
    public class MessageFragment extends Fragment {
        private final String TAG = getClass().getSimpleName();
        private static final String message = "message";
    
        private String messageTag;
    
        public MessageFragment() {
        }
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param Parameter .
         * @return A new instance of fragment MessageFragment.
         */
        public static MessageFragment newInstance(String param) {
            MessageFragment fragment = new MessageFragment();
            Bundle args = new Bundle();
            args.putString(message, param);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onAttach(Context context) {
            super.onAttach(context);
            Log.e(TAG,"---onAttach----");
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.e(TAG,"---onCreate----");
            if (getArguments() != null) {
                messageTag = getArguments().getString(message);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            Log.e(TAG,"---onCreateView---");
            View view = inflater.inflate(R.layout.fragment_common_layout, container, false);
            TextView textView = view.findViewById(R.id.text_view);
            textView.setText(messageTag);
            return view;
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            Log.e(TAG,"----onDestroyView---");
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.e(TAG,"----onDestroy---");
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            Log.e(TAG,"----onDetach---");
        }
    }
    

    四个子View都是Fragment,其他的三个写法相同,简单介绍下Fragment主要干了些什么,newInstance(String param)方法是谷歌的创建Fragment实例的方法,Fragment构造方法私有,Activity通过这个方法传参数进来,Fragment的布局很简单,就是一个位于布局中心的TextView,显示Activity传进来的字符串参数。然后就是各个生命周期方法的Log打印。

    由于这里要根据Fragment的生命周期来分析,所以这里贴一张图,帮助大家回顾一下Fragment的生命周期
    image
    上图中不仅列出了Fragment生命周期,也同时列出了Activity的生命周期,因为Fragment的依赖性,所以他们之间的生命周期会产生联系。(注:上图参考谷歌源码)

    在代码中我们分别关注了Fragment的onAttach方法,onCreate方法,onCreateView方法,onDestroyView方法,onDestroy方法,onDetach方法。

    两个PagerAdapter实现也很简单,分别继承FragmentPagerAdapter和 FragmentStatePagerAdapter,并实现他们的抽象方法getItem()返回具体子View和getCount()返回要显示子View的数量。由于前边系列有贴代码,这里就不贴Adapter的代码了。然后在Activity中分别使用上述两个Adapter的实例设置给ViewPager,然后向右滑动ViewPager,我们看到了不一样的Log日志
    (Fragment的顺序:MessageFragment-> FriendFragment -> CircleFragment -> AccountFragment)

    采用了FragmentPagerAdapter 的Log记录

    //滑到第一页
    11-20 20:40:32.480 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onAttach----
    11-20 20:40:32.480 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onCreate----
    11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onAttach----
    11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onCreate----
    11-20 20:40:32.481 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ---onCreateView---
    11-20 20:40:32.483 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ---onCreateView---
    //滑到第二页
    11-20 20:40:37.370 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onAttach----
    11-20 20:40:37.370 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onCreate----
    11-20 20:40:37.371 6878-6878/com.hzx.viewpagerdirector E/CircleFragment: ---onCreateView---
    //滑到第三页
    11-20 20:40:39.316 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onAttach----
    11-20 20:40:39.316 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onCreate----
    11-20 20:40:39.317 6878-6878/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroyView---
    11-20 20:40:39.317 6878-6878/com.hzx.viewpagerdirector E/AccountFragment: ---onCreateView---
    //滑到第四页
    11-20 20:40:41.812 6878-6878/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroyView---
    

    采用了FragmentStatePagerAdapter的Log记录

    //滑到第一页
    11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onAttach----
    11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onCreate----
    11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onAttach----
    11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onCreate----
    11-20 17:39:13.562 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ---onCreateView---
    11-20 17:39:13.565 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ---onCreateView---
    //滑到第二页
    11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onAttach----
    11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onCreate----
    11-20 17:40:54.209 32391-32391/com.hzx.viewpagerdirector E/CircleFragment: ---onCreateView---
    //滑到第三页
    11-20 17:41:03.759 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onAttach----
    11-20 17:41:03.759 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onCreate----
    11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroyView---
    11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDestroy--- (笔者标记:不同的地方)
    11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/MessageFragment: ----onDetach---(笔者标记:不同的地方)
    11-20 17:41:03.760 32391-32391/com.hzx.viewpagerdirector E/AccountFragment: ---onCreateView---
    //滑到第四页
    11-20 17:43:03.554 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroyView---
    11-20 17:43:03.556 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDestroy---(笔者标记:不同的地方)
    11-20 17:43:03.557 32391-32391/com.hzx.viewpagerdirector E/FriendFragment: ----onDetach---(笔者标记:不同的地方)
    

    log分析
    我们没更改ViewPager的预加载状态,然后在翻到第一页(MessageFragment)的时候和翻到第二页(FriendFragment)的时候,两个完全Adapter相同,这是因为这个时候根据ViewPager的缓存策略,他会缓存3个子View,提高加载速度和显示流畅性。看日志可以证明证明:翻到第二页的时候第三个Fragment(CircleFragment)已经完成了加载操作。而且这个时候还没有回调Fragment卸载的相关方法。

    翻到第三页(CircleFragment)的时候和第四页(AccountFragment)的时候,出现了不同:

    当翻到第三页的时候,因为他要提前加载第四页(AccountFragment),又由于缓存的数量是3,所以第一页(MessageFragment)开始回调卸载方法,
    使用FragmentPagerAdapter,回调了 onDestroyView卸载方法
    使用FragmentStatePagerAdapter,回调了onDestroyView,onDestroy,onDetach卸载方法
    当翻到第四页(AccountFragment)的时候,又由于缓存的数量是3,而且是任一方向不保存1张以上,所以第二页(FriendFragment)开始回调卸载方法,
    使用FragmentPagerAdapter,回调了 onDestroyView卸载方法
    使用FragmentStatePagerAdapter,回调了onDestroyView,onDestroy,onDetach卸载方法

    所以很清楚了,采用FragmentStatePagerAdapter的ViewPager由于在意Fragment的State,为了节省内存,所以他回调了更彻底的onDestroy和onDetach方法,所以当需要重新使用完全卸载掉的Fragment的时候就需要通过getItem方法重新获取实例。而采用FragmentPagerAdapter的ViewPager,只会回调onDestroyView方法。当需要显示或者提前加载这个Fragment的时候重新走onCreateView迅速创建显示,同时也就会一直驻留在内存里(在一般情况下)。

    为什么会有这样的不同呢?

    为什么会出现呢?这种问题的指向性就很明确了,也就是从源码中找答案:
    不过,看官先别急。。。
    我们先猜测一下,熟悉Fragment的程序员知道,为了保证Fragment加载的安全性和管理的便捷性,他是通过FragmentManager(Activity调用getSupportFragmentManager()获得)统一管理,然后开启FragmentTransaction事务(了解DataBase的童鞋,都知道事务是可以做到操作的一致性,这样加载和卸载能保证协调一致,如果失败还可以回滚)来加载Fragment。

    然而对于事务在对Fragment的加载和卸载有两套方法,
    一套是:attach(),add()方法 , detach()方法
    另一套是:add()方法 ,remove()方法(replace()方法内部是先执行remove()方法,然后执行add()方法)

    注意:由于Fragment是用的时候是加载,并不能提前知道加载哪一个,所以add()方法和remove()方法必须分开,所以源码中没有用到replace()方法,但是在我们自己管理的时候,可以考虑使用replace()方法

    恰恰当执行detach方法的时候,Fragment会回调onDestroyView(),而执行remove()方法的时候,对应的Fragment会回调onDestroyView,onDestroy,onDetach。所以我们有理由相信,两个PagerAdapter的实现类中源码应该是这样的逻辑,为了验证这个,我们来看源码。。。

    因为他们都继承自PagerAdapter,所以在上一篇主要叙述的规则,他们一定是遵守的,加载View是调用
    instantiateItem()方法,而卸载是调用destroyItem()方法

    所以我们先看FragmentPagerAdapter的源码中的instantiateItem()方法

    
     public Object instantiateItem(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);
                //存在的时候,调用attach()方法,快速加载
                mCurTransaction.attach(fragment);
            } else {
            	//调用FragmentPagerAdapter的抽象方法getItem
                fragment = getItem(position);
                if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
                //第一次创建,不存在的时候,调用add()方法加载
                mCurTransaction.add(container.getId(), fragment,
                        makeFragmentName(container.getId(), itemId));
            }
            //采用懒加载策略
            if (fragment != mCurrentPrimaryItem) {
                fragment.setMenuVisibility(false);
                fragment.setUserVisibleHint(false);
            }
    
            return fragment;
        }
    

    我们再来看FragmentPagerAdapter的destroyItem()方法

    public void destroyItem(ViewGroup container, int position, Object object) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                    + " v=" + ((Fragment)object).getView());
            //采用detach()方法卸载
            mCurTransaction.detach((Fragment)object);
        }
    

    接下来是FragmentStatePagerAdapter的instantiateItem()方法

     public Object instantiateItem(ViewGroup container, int position) {
            // If we already have this item instantiated, there is nothing
            // to do.  This can happen when we are restoring the entire pager
            // from its saved state, where the fragment manager has already
            // taken care of restoring the fragments we previously had instantiated.
            if (mFragments.size() > position) {
                Fragment f = mFragments.get(position);
                if (f != null) {
                    return f;
                }
            }
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    		//调用FragmentStatePagerAdapter的抽象方法getItem
            Fragment fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
            if (mSavedState.size() > position) {
                Fragment.SavedState fss = mSavedState.get(position);
                if (fss != null) {
                    fragment.setInitialSavedState(fss);
                }
            }
            while (mFragments.size() <= position) {
                mFragments.add(null);
            }
            //懒加载策略
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
            mFragments.set(position, fragment);
            //添加Fragment,由于其卸载使用的是remove()方法,所以不能使用attach()方法进行加载,只能用add()方法
            mCurTransaction.add(container.getId(), fragment);
    
            return fragment;
        }
    

    再然后是FragmentStatePagerAdapter的destroyItem()方法

    public void destroyItem(ViewGroup container, int position, Object object) {
            Fragment fragment = (Fragment) object;
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                    + " v=" + ((Fragment)object).getView());
            while (mSavedState.size() <= position) {
                mSavedState.add(null);
            }
            mSavedState.set(position, fragment.isAdded()
                    ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
            mFragments.set(position, null);
    	//采用remove()方法卸载
            mCurTransaction.remove(fragment);
        }
    

    根据我再源码中添加的注释,很显然和我们的猜测是一致的,另外在FragmentStatePagerAdapter源码中还创建了ArrayList<Fragment.SavedState> mSavedState来保存对应位置的Fragment的状态,在instantiateItem()方法中获取状态,在destroyItem()中设置更新状态,这都是为更好的加载服务服务的。

    另外我们还发现,本来PagerAdapter需要我们实现的四个方法,经过这两个亲儿子的对instantiateItem和destroyItem方法的实现,所以,我们无论实现哪一个FragmentPagerAdapter都不需要再实现这两个方法了,而且内部也对isViewFromObject进行了实现

      @Override
        public boolean isViewFromObject(View view, Object object) {
            return ((Fragment)object).getView() == view;
        }
    

    所以我们都不用实现了,同时为了让用户设置加载的Fragment的,又提供了getItem抽象方法供子类继承

     /**
         * Return the Fragment associated with a specified position.
         */
        public abstract Fragment getItem(int position);
    

    而且这个抽象方法是在instantiateItem方法(在最上边比较两个Adapter区别的源码分析中能够看到)中获取子View的时候调用。

    因此我们在实现Adapter的时候只需要实现两个方法一个是getCount(),另一个是getItem()。

    应该怎样用?

    在我们使用ViewPager的,选择PagerAdapter的时候应遵循这样的原则:

    1. 当我们子View是普通的View,而非Fragment的时候,继承基类PagerAdapter实现必须实现的四个方法;

    2. 当子View是Fragment的时候,并且当子View中保存的内容比较少,轻量级,占用内存较小,为了提高加载流畅性,使用FragmentPagerAdapter;

    3. 当子View是Fragment的时候,并且其中有些子View保存的内存较多,占用内存较大时,如果经大量测试发现不会出现out of memory,那为了保证流畅性,还是建议是用FragmentPagerAdapter,但是如果发现很容易oom,或者频率很大,那就一定要抛弃FragmentPagerAdapter,而建议采用FragmentStatePagerAdapter来辅助ViewPager加载子View。这样做出的牺牲就是数据每次都要重新加载,页面也需要重新加载初始化。

    Adapter的小尾巴,到此我们终于解决了,因为他们加载卸载的机制的不同,导致FragmentPagerAdapter和FragmentStatePagerAdapter这对亲兄弟,天生具有不同的使用场景,并且各有利弊,如果读者发现自己有更好的思路来管理Fragment也可以实现自己项目的FragmentPagerAdapter基类。

    在下一篇我们将来列举一些小技巧和在使用过程中的一些坑,便于读者查漏补缺,定位bug

    请看 ViewPager (四)让ViewPager用起来更顺滑——换页监听及换页方法
    ————————————————
    版权声明:本文为CSDN博主「郝振兴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_39095733/article/details/84309051

  • 相关阅读:
    UNIX网络编程总结三
    UNIX网络编程总结二
    UNIX网络编程总结一
    KVM
    nginx+flask+gevent+uwsgi实现websocket
    Hypervisor
    JBPM4入门——4.封装流程管理的工具类(JbpmUtil)
    JBPM4入门——3.JBPM4开发环境的搭建
    JBPM4入门——2.在eclipse中安装绘制jbpm流程图的插件
    JBPM4入门——1.jbpm简要介绍
  • 原文地址:https://www.cnblogs.com/sishuiliuyun/p/14710148.html
Copyright © 2020-2023  润新知