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

时间: 2024-08-28 02:36:29

Android中FragmentPagerAdapter对Fragment的缓存(二)的相关文章

Android中FragmentPagerAdapter对Fragment的缓存(一)

ViewPager + FragmentPagerAdapter,时我们经常使用的一对搭档,其实际应用的代码也非常简单,但是也有一些容易被忽略的地方,这次我们就来讨论下FragmentPagerAdapter对Fragment的缓存应用.  我们可以先看看最简单的实现,自定义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 public class

Android中关于JNI 的学习(二)对于JNI方法名,数据类型和方法签名的一些认识

处理特征数据 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26240241 输入文件:  1. 归一化后的特征文件, 第1列是标签, 其余列是特征; 2. 特征最大值向量文件: 前3列是标准格式, 其余列是最大值; 输出文件: 1. 符合SVM训练数据的特征格式; 2. Mat存储的标准XML文件; 代码: /* 处理特征数据程序 By C.L.Wang 数据格式: 特征数据: 第1列是标签, 其余列是特征; 最大

Android中的软件安全和逆向分析[二]—apk反破解技术与安全保护机制

在Android应用开发中,当我们开发完软件之后,我们不希望别人能够反编译破解我们的应用程序,不能修改我们的代码逻辑.实际上,在应用程序的安全机制考虑中,我们希望自己的应用程序安全性高,通过各种加密操作等来增大竞争对手的反编译破解成本.设想,竞争对手开发一个同样的应用程序需要10天,而破解我们的软件程序需要100天,那么势必会打消黑客程序员破解我们应用程序的念头.如何增加对手的破解成本,就需要考验我们应用程序的安全性有多高,加密技术有多强.一个优秀的应用程序,不仅能为用户带来利益,同时也能保护自

Android笔记——Android中数据的存储方式(二)

我们在实际开发中,有的时候需要储存或者备份比较复杂的数据.这些数据的特点是,内容多.结构大,比如短信备份等.我们知道SharedPreferences和Files(文本文件)储存这种数据会非常的没有效率.如果学过JavaWeb的朋友,首先可能想到的是数据库.当然了数据库是一个方案,那么是否还有其他的解决方案呢?今天我们在讲下Android笔记——Android中数据的存储方式(一) 提到的除了SharedPreferences和Files(文本文件)以外的其他几种数据储存方式:xml文件.SQL

Android——用FragmentPagerAdapter实现Fragment的滑动效果

效果: ViewPage来源于android -support.v4 什么是viewPage?ViewPage 类似于ListView 用于显示多个View集合. 支持页面左右滑动. 如何使用viewPage以及需要注意点?ViewPage 需要Adapter:PagerAdapter 有四个重要方法:(1) void destroyItem(ViewGroup container, int position, Object object): 销毁(2)Object instantiateIte

在Android中Intent的概念及应用(二)——Intent过滤器相关选项

一.如果多个Activity拥有同一个Intent Action,启动时用同一个Action启动会是什么情况? 如何指定某一个Activity启动? 在多个Activity拥有同一个Intent Action的情况下,如若想启动某一指定Activity,则在该<intent-filter> 中添加<data android:scheme="app"/>.而且,将启动的代码写为: startActivity(new Intent("com.w.learn

Android中高效的显示图片之二——在非UI线程中处理图片

在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘.网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行.因为它的加载时间不可预测且依赖于一系列因素(磁盘读写速度.图片大小.CPU频率等).如果在主线程中执行这个操作,一旦它阻塞了主线程,就会导致系统ANR.本节介绍使用AsyncTask在后台处理图片和演示怎么处理并发问题. 一.使用一个AsyncTask AsyncTask类提供一个简易的方法在后台线程中执行一些任务并把结果发布到UI线程.

Android中Context的理解及使用(二)——Application的用途

实现数据共享功能: 多个Activity里面,可以使用Application来实现数据的共享,因为对于同一个应用程序来说,Application是唯一的. 1.实现全局共享的数据App.java继承自Application: public class App extends Application { private String textData = "default"; public void setTextData(String textData) { this.textData

android中的HttpUrlConnection的使用之二

httpUrlConnection主要用于网络传输当中,前面已经提及到了使用HttpUrlConnection来加载一个网站,这里我记录一下:用它在网络上下载一张图片并且加载到imageview当中.我们需要注意的是:当前很多网站上的图片传输的模式主要分两种:1.一是加密传输,使用HttpsUrlConnection进行链接:2.而是非加密传输,使用HttpUrlConnection来传输.代码如下(非加密传输): xml代码 1 <LinearLayout xmlns:android="