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

2021/4/27 18:25:20

本文主要是介绍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



这篇关于ViewPager(三)两个熊孩子天生不一样的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程