zoukankan      html  css  js  c++  java
  • Android中FragmentPagerAdapter对Fragment的缓存(二)

    上一篇我们谈到了,当应用程序恢复时,由于FragmentPagerAdapter对Fragment进行了缓存的读取,导致其并未使用在Activity中新创建的Fragment实例。今天我们来看如何解决这种情况。

     根据上篇Blog的描述,我们不难发现,目前需要解决的问题有以下两个:

     1. 缓存Fragment内部成员变量缺失的问题。

     2. 新Fragment的创建和缓存Fragment使用之间的矛盾。

     下面先来解决第一个问题,缓存Fragment内部成员变量缺失。上篇Blog中,Fragment当中,有一个成员变量mText,是通过setter的方式在创建Fragment之初设置进去的。但是在经历了一系列的存储和恢复操作过后,其值在最终却为空,导致了程序展示的异常。那么能不能让mText也在Fragment中同步缓存和恢复呢?

     最先能想到的方法,就是通过Fragment的onSaveInstanceState方法在进程被杀掉时存储,当恢复时通过onCreateView的savedInstanceState参数取出;代码如下:

    [代码]java代码:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            mText = savedInstanceState.getString(SAVED_KEY_TEXT);
        }
        ...
    }
     
     
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(SAVED_KEY_TEXT, mText);
    }

     这种Activity和Fragment通用的方法,无疑是应用被杀掉时我们存储数据比较好的选择。不过还有其他方式吗?

     目前,mText是通过setter向Fragment设置的,这样做从实现来讲没有问题,不过其实并不是Android官方文档推荐的最佳实践; 官方文档上不推荐使用setter或者重写默认构造器的方式来传递参数:

    It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().

     原因是,当Fragment重新被恢复时,不会去重新调用这些setter/有参构造方法; 而是会调用onCreateView,我们却可以在其中重新调用getArguments去获取这些参数。这就保证了在恢复过后,我们需要传入的参数可以重新被设置。一番改造之后如下:

    [代码]java代码:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    TestFragment fragmentOne = new TestFragment();
       Bundle bundleOne = new Bundle();
       bundleOne.putString(TestFragment.PARAM_KEY_TEXT, "One");
       fragmentOne.setArguments(bundleOne);
     
       TestFragment fragmentTwo = new TestFragment();
       Bundle bundleTwo = new Bundle();
       bundleTwo.putString(TestFragment.PARAM_KEY_TEXT, "Two");
       fragmentTwo.setArguments(bundleTwo);
     
       TestFragment fragmentThree = new TestFragment();
       Bundle bundleThree = new Bundle();
       bundleThree.putString(TestFragment.PARAM_KEY_TEXT, "Three");
       fragmentThree.setArguments(bundleThree);

    这样传入的参数,就不需要在onSaveInstanceState里面去手动保存了。

    [代码]java代码:

    1
    2
    3
    4
    5
    6
    7
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_test, container, false);
            TextView textView = (TextView) view.findViewById(R.id.center_text_view);
            mText = (getArguments() != null) ? getArguments().getString(PARAM_KEY_TEXT) : "";
            textView.setText(mText);
            return view;
        }

     第一个问题到这里就处理好了,接下来看看第二个问题:怎样解决onCreate中新实例化的Fragment,与Adapter中FragmentManager中取出的Fragment不一致的冲突。

     虽然mText找回来了,但是如果我们需要对Activity中实例化的Fragment做一些进一步的操作,比如传入一些Listener之类的事情,就会遇到一些麻烦,因为毕竟我们处理的这些Fragment,实际上并不是当前展示在屏幕上的Fragment。

    上篇Blog中讲到,FragmentPagerAdapter使用container.getId()与getItemId拼接的字符串作为FragmentManager中缓存的Key,FragmentPagerAdapter代码如下:

    [代码]java代码:

    1
    2
    3
    4
    5
    6
    7
    8
    String name = makeFragmentName(container.getId(), itemId);
      Fragment fragment = mFragmentManager.findFragmentByTag(name);
     
      ...
     
      private static String makeFragmentName(int viewId, long id) {
            return "android:switcher:" + viewId + ":" + id;
        }

    从上面的代码来看,其实要避免缓存和新创建的Fragment不一致,最简单的方式是,通过重写getItemId()方法,让每次打开应用返回不同的值(比如随机数之内的),让FragmentPagerAdapter找不到之前的缓存,就会使用我们新传入的实例了。

     不过这样做,看起来既不优雅,也不靠谱。毕竟Android官方给我们提供了这样一种缓存机制,那我们还是应该考虑怎样利用才好。

     1. 既然有缓存,那我们不必在Activity中每次都去新创建Fragment实例了。从源码中可以看出,每次如果FragmentPagerAdapter需要新实例化Fragment的话,都回去调用getItem方法,所以,可以考虑把Fragment的实例化工作放到getItem当中去。

     2. 考虑到后面我们会使用到这些Fragment实例,可以考虑在instantiateItem当中去获取并存放在数组当中。这里选择到instantiateItem,而不是getItem方法中去取的原因是:如果一旦出现有缓存的情况,FragmentPagerAdapter并不会调用getItem方法,如下:

    [代码]java代码:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    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));
        }

     按照上面两点想法,经过改造的Adapter的代码如下:

    [代码]java代码:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    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
    public class CustomPagerAdapter extends FragmentPagerAdapter {
        private static final int COUNT = 3;
     
        private Fragment[] mFragments;
        private Context mContext;
     
        public CustomPagerAdapter(Context context, FragmentManager fm) {
            super(fm);
            this.mContext = context;
            this.mFragments = new Fragment[COUNT];
        }
     
        @Override
        public Fragment getItem(int position) {
            String text;
            switch (position) {
                case 0:
                    text = "One";
                    break;
                case 1:
                    text = "Two";
                    break;
                case 2:
                    text = "Three";
                    break;
                default:
                    text = "";
            }
            Bundle bundle = new Bundle();
            bundle.putString(TestFragment.PARAM_KEY_TEXT, text);
            return Fragment.instantiate(mContext, TestFragment.class.getName(), bundle);
        }
     
        @Override
        public int getCount() {
            return COUNT;
        }
     
        @Override
        public long getItemId(int position) {
            return position;
        }
     
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment fragment = (Fragment) super.instantiateItem(container, position);
            mFragments[position] = fragment;
            return fragment;
        }
     
        public Fragment[] getFragments() {
            return mFragments;
        }
    }

    有一点需要注意的是,mFragment数组需要在每个页面都实例化好了之后才会填充完成,需要注意调用的时机。

    FragmentPagerAdapter对Fragment缓存的分析就是这么多了,欢迎指正。

  • 相关阅读:
    比较好的总结runtime
    开发证书详解
    删除数组中特定元素推荐做法
    pbxproj文件冲突解决办法
    svn不提交user文件
    c提高第六次课 文件读取
    c++函数指针
    c提高第五次作业
    c提高第四次作业
    c提高第四课
  • 原文地址:https://www.cnblogs.com/android-blogs/p/5524188.html
Copyright © 2011-2022 走看看