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); } ...}@Overridepublic 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缓存的分析就是这么多了,欢迎指正。
Android中FragmentPagerAdapter对Fragment的缓存(二)的更多相关文章
- Android中FragmentPagerAdapter对Fragment的缓存(一)
ViewPager + FragmentPagerAdapter,时我们经常使用的一对搭档,其实际应用的代码也非常简单,但是也有一些容易被忽略的地方,这次我们就来讨论下FragmentPagerAda ...
- Android笔记——Android中数据的存储方式(二)
我们在实际开发中,有的时候需要储存或者备份比较复杂的数据.这些数据的特点是,内容多.结构大,比如短信备份等.我们知道SharedPreferences和Files(文本文件)储存这种数据会非常的没有效 ...
- Android中的一些基础知识(二)
这几天在回顾Android的基础知识,就把一些常见的知识点整理一下,以后忘了也可以翻出来看一看. 简单介绍一下Activity的生命周期 在API文档中对生命周期回调的函数描述的很详细,这里我只是翻译 ...
- Android笔记(四十) Android中的数据存储——SQLite(二) insert
准备工作: 我们模拟一个注册的页面,先看UI 我们需要创建一个数据库:user,数据库包含表user,user表包含字段id.username.password.mobilephone MainAct ...
- android 中的 ViewPager+ Fragment
android的Viewpager 的各种经常的用法,朋友问我要过,所以就稍微总结一下, ViewPager + Fragment 经常用到 代码是从 actionbarsherlock 中提取 ...
- 在Android中Intent的概念及应用(二)——Intent过滤器相关选项
一.如果多个Activity拥有同一个Intent Action,启动时用同一个Action启动会是什么情况? 如何指定某一个Activity启动? 在多个Activity拥有同一个Intent Ac ...
- Android中AIDL的理解与使用(二)——跨应用绑定Service并通信
跨应用绑定Service并通信: 1.(StartServiceFromAnotherApp)AIDL文件中新增接口: void setData(String data); AppService文件中 ...
- Android中Activity加入Fragment使用注意事项及常用技巧
Fragment中AlertDialog弹出窗口的使用 Fragment默认不具有Content的一些方法和属性,因此在Activity中可以使用的一些方法在Fragment中使用就需要一些小技巧了 ...
- Android中高效的显示图片之二——在非UI线程中处理图片
在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘.网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行.因为它的加载时间不可预测且依赖于一系列因 ...
随机推荐
- BitNami一键安装Redmine(转)
1. 简介 对于一个新手,如果严格按照官方文档来安装redmine,我想会“疯”掉的.有没有一种简便的方法.有滴,那就是BitNami. BitNami提供redmine的一键安装程序,简单.易用.方 ...
- ajax post 跨域
H5页面永远无法避开跨域问题-- php中, header('Access-Control-Allow-Origin:*'); 搞定. 兼容性先不管了. 来自为知笔记(Wiz)
- sigleSchool 存储过程例1
CREATE OR REPLACE PROCEDURE SINGLSCHOOL( PICIID IN VARCHAR2, SCHOOLID IN NUMBER, SCHETYPE IN number, ...
- hdoj 2112 HDU Today
题目传送:http://acm.hdu.edu.cn/showproblem.php?pid=2112 分析:多了一个地方的条件,用map来映射地点编号,Dijkstra求解即可 //2013-10- ...
- SPOJ LCS 后缀自动机
用后缀自动机求两个长串的最长公共子串,效果拔群.多样例的时候memset要去掉. 解题思路就是跟CLJ的一模一样啦. #pragma warning(disable:4996) #include< ...
- 毕向东JAVA视频讲解(第七课)
构造函数:构建创造对象时调用的函数.作用:可以给对象进行初始化. 创建对象都必须要通过构造函数初始化. 一个类中如果没有定义过构造函数,那么该类中会有一个默认的空参数构造函数. 如果在类中定义了指定的 ...
- jsp是有自己的编译引擎的
什么是jsp? JSP(Java Server Pages)是指: 在HTML中嵌入Java脚本代码 由应用服务器中的JSP引擎来编译和执行嵌入的Java脚本代码 然后将生成的整个页面信息返回给客户端 ...
- Visual StudioTools for Unity 使用技巧2
在之前的博客介绍了 Visual Studio Tools for Unity的安装和使用. http://www.cnblogs.com/petto/p/3886811.html 其实这个工具还提供 ...
- 64位下好神奇啊(增加了PatchGuard技术保护自己,SSDT是相对地址,参数通过寄存器与rdi来传递)
近期可能会有一个64位平台的驱动开发任务,找了些资料,对64位平台下的驱动开发略知一二了,好神奇. 一.在64位系统下,有一项PatchGuard技术,它是微软为了防止自己的代码被Patch,进而影响 ...
- (六)CSS伪元素
CSS伪元素用于向某些选择器设置特殊效果. 伪元素的用法和伪类相似: selector:pseudo-element {property:value;} CSS类也可以与伪元素配合使用: select ...