Android中FragmentPagerAdapter对Fragment的缓存(二)

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

 根据上篇Blog的描述,咱们不难发现,目前须要解决的问题有如下两个:java

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

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

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

 最早能想到的方法,就是经过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或者重写默认构造器的方式来传递参数:ide

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().ui

 缘由是,当Fragment从新被恢复时,不会去从新调用这些setter/有参构造方法; 而是会调用onCreateView,咱们却能够在其中从新调用getArguments去获取这些参数。这就保证了在恢复事后,咱们须要传入的参数能够从新被设置。一番改造以后以下:this

[代码]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缓存的分析就是这么多了,欢迎指正。

相关文章
相关标签/搜索