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 CustomPagerAdapter extends FragmentPagerAdapter{

    private List<fragment> mFragments;

    public CustomPagerAdapter(FragmentManager fm, List<fragment> fragments) {

        super(fm);

        this.mFragments = fragments;

        fm.beginTransaction().commitAllowingStateLoss();

    }

    @Override

    public Fragment getItem(int position) {

        return this.mFragments.get(position);

    }

    @Override

    public int getCount() {

        return this.mFragments.size();

    }

    @Override

    public long getItemId(int position) {

        return position;

    }

}</fragment></fragment>

代码比较简单,就不解释了,接着在Activity中使用这个Adapter:

[代码]java代码:

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

ViewPager pager = (ViewPager) findViewById(R.id.view_pager);

List<fragment> fragmentList = new ArrayList<>()

TestFragment fragmentOne = new TestFragment();

fragmentOne.setText("One");

TestFragment fragmentTwo = new TestFragment();

fragmentTwo.setText("Two");

TestFragment fragmentThree = new TestFragment();

fragmentThree.setText("Three");

fragmentList.add(fragmentOne);

fragmentList.add(fragmentTwo);

fragmentList.add(fragmentThree);

CustomPagerAdapter adapter = new CustomPagerAdapter(getSupportFragmentManager(), fragmentList);

pager.setAdapter(adapter);</fragment>

这样就完成了一个FragmentPagerAdapter最基本的应用。现在,看上去一切都如我们所愿,但是真的没有任何问题了吗?

 接下来,我们来模拟一下程序运行在后台时,Android系统由于内存紧张,杀掉我们程序进程的情况:

  1. 首先运行程序至前台
  2. 接下来,点击Home键,返回桌面,同时我们的程序退回至后台运行。
  3. 进入Android Studio中,点击Android Monitor这个tab,并选择当前Device,并选择我们程序的进程名。
  4. 点击Terminal Application这个小红叉按钮,如下图:
  5. 这个时候后台进程已经被杀掉了,但是应用程序历史里我们的应用还在,所以长按Home键,并选择我们的程序,让其恢复到前台。
  6. 这时会看到,程序的确恢复到之前的页面。但奇怪的是,页面上却只有Hello,我们之前传入的Two到哪里去了?

 Fragment代码也比较简单,通过日志,我们发现恢复时,mText字段为空。所以页面上对应的TextView无法显示。

[代码]java代码:

?


01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

public class TestFragment extends Fragment {

    private String mText;

    @Nullable

    @Override

    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);

        textView.setText(mText);

        return view;

    }

    public void setText(String text) {

        this.mText = text;

    }

}

 我们知道,以上是模拟Android系统内存紧张时,杀掉后台应用的流程。另外,当用户安装了类似360安全管家等应用,选择清理内存时,也会触发以上情况。

 那么当上面的流程发生时,Activity的onSaveInstanceState会被调用,以便我们可以保存当前的用户数据/页面状态等。当恢复时,在onCreate时,我们通过savedInstanceState参数,可以取到之前存储的数据,然后重新绑定到View上。

 这个过程都可以理解,可是回到我们的Activity代码当中:

[代码]java代码:

?


01

02

03

04

05

06

07

08

09

10

11

12

TestFragment fragmentOne = new TestFragment();

fragmentOne.setText("One");

TestFragment fragmentTwo = new TestFragment();

fragmentTwo.setText("Two");

TestFragment fragmentThree = new TestFragment();

fragmentThree.setText("Three");

fragmentList.add(fragmentOne);

fragmentList.add(fragmentTwo);

fragmentList.add(fragmentThree);

CustomPagerAdapter adapter = new CustomPagerAdapter(getSupportFragmentManager(), fragmentList);

        pager.setAdapter(adapter);

 这段代码,是在onCreate方法中调用的。应用从后台恢复的时候,这段代码是被完整的执行过的。既然这样,三个Fragment都被重新创建过,并设置过对应的Text值,那么为什么Fragment中mText字段仍然为空呢?

 难道说,呈现在屏幕上的Fragment,和我们在onCreate中实例化的Fragment,已然不是同一个实例?

 为了验证这个想法,在OnCreate中加入下面的日志:

[代码]java代码:

?


1

2

3

TestFragment fragmentOne = new TestFragment();

fragmentOne.setText("One");

Log.i("test", "++++fragmentOne++++:" + fragmentOne.toString());

同时在TestFragment的onCreateView方法中也记下日志:

[代码]java代码:

?


1

Log.i("test", "++++current fragment++++:" + this.toString());

 第一次运行,恩,没有问题。创建和运行的都是同一个实例 534ed278

[代码]java代码:

?


1

2

I/test: ++++fragmentOne++++:TestFragment{534ed278}

I/test: ++++current fragment++++:TestFragment{534ed278 #0 id=0x7f0c0066 android:switcher:2131492966:0}

 接下来,我们再次进行杀进程并恢复的过程。日志输出为:

[代码]java代码:

?


1

2

I/test: ++++fragmentOne++++:TestFragment{534c5c30}

I/test: ++++current fragment++++:TestFragment{534d10d4 #0 id=0x7f0c0066 android:switcher:2131492966:0}

 额。。果然,这次我们创建的Fragment,和实际经过onCreateView的Fragment。并不是同一个(534c5c30/534d10d4)。

 看来,还是要从源码中寻求真相,打开FragmentPagerAdapter的源码,在instantiateItem方法中发现了下面这一段:

[代码]java代码:

?


01

02

03

04

05

06

07

08

09

10

11

12

13

// 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);

    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));

}

 makeFragmentName方法如下:

[代码]java代码:

?


1

2

3

private static String makeFragmentName(int viewId, long id) {

    return "android:switcher:" + viewId + ":" + id;

}

原来,在实例化Fragment的时候,FragmentPagerAdapter会先通过makeFragmentName返回的tag,到FragmentManager当中进行查找是否有当前Fragment的缓存,如果有的话,就直接将之前的Fragment恢复回来并使用;反之,才会使用我们传入的新实例。

 而makeFragmentName产生的tag,只受我们重写的getItemId()方法返回值,和当前容器View的Id,container.getId()的影响。

 到这里,问题就清楚了,由于FragmentPagerAdapter会主动的去取缓存当中的Fragment,所以导致恢复回来之后,Fragment的实例不一样的问题。

 至于为什么mText字段为空,以及怎样解决这个情况,我们下一篇再来讨论^_^。

时间: 2024-11-04 09:27:33

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

Android中FragmentPagerAdapter对Fragment的缓存(二)

上一篇我们谈到了,当应用程序恢复时,由于FragmentPagerAdapter对Fragment进行了缓存的读取,导致其并未使用在Activity中新创建的Fragment实例.今天我们来看如何解决这种情况.  根据上篇Blog的描述,我们不难发现,目前需要解决的问题有以下两个:  1. 缓存Fragment内部成员变量缺失的问题.  2. 新Fragment的创建和缓存Fragment使用之间的矛盾.  下面先来解决第一个问题,缓存Fragment内部成员变量缺失.上篇Blog中,Fragm

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中Activity和Fragment与Fragment和Fragment之前互相传值方法

Activity->Fragment: Activity:ft.setXXX(xxx);或者在new Fragment(xxx); Fragment:setXXX(xxx);或者Fragment(xxx); Fragment->Activity: Fragment:接口mylistener.onAttach Activity:继承mylistener Fragment1->Fragment2: Fragment1:setArgument(bundle); Fragment2:getArg

Android中的缓存处理

一.缓存介绍 (一).Android中缓存的必要性: 1.没有缓存的弊端: 流量开销:对于客户端--服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量. 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕. 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存 2.缓存带来的好处: 1. 服务器的压力大大减小: 2. 客户端的响应速度大大变快(用户体验好): 3. 客户端的数据加载出错情况大大较少,大大提高了应有的稳定性(用户体验好): 4.

简单地Android中图片的三级缓存机制

我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存缓存中,缓存到强引用中 也就是LruCache中.如果强引用中空间不足,就会将较早存储的图片对象驱逐到软引用(softReference)中存储,然后将图片缓存到文件(内部存储外部存储)中:读取图片的时候,先读取内存缓存,判断强引用中是否存在图片,如果强引用中存在,则直接读取,如果强引用中不存在,则

android中viewPager+fragment实现的屏幕左右切换(进阶篇)

Fragment支持在不同的Activity中使用并且可以处理自己的输入事件以及生命周期方法等.可以看做是一个子Activity. 先看一下布局: 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match

Android应用开发:Fragment与大型数据缓存

引言 在Android应用开发:Fragment的非中断保存setRetaineInstance一文中已经介绍过了如何让Fragment不随着Activity销毁从而保存数据的方法.在移动应用程序的架构设计中,界面与数据即不可分割又不可混淆.在绝大部分的开发经历中,我们都是使用Fragment来进行界面编程,即使保存数据基本上也只是界面相关控件的数据,很少做其他的数据保存,毕竟这样与开发原则相背,而今天这一篇博客就要来介绍一下Fragment的另类用法,只是用来保存数据而没有任何界面元素. 实现

Android中Fragment和ViewPager那点事儿

在之前的博文<Android中使用ViewPager实现屏幕页面切换和引导页效果实现>和<Android中Fragment的两种创建方式>以及<Android中Fragment与Activity之间的交互(两种实现方式)>中我们介绍了ViewPager以及Fragment各自的使用场景以及不同的实现方式. 那如果将他们两结合起来,会不会擦出点火花呢,答案是肯定的.之前在介绍ViewPager时,我们实现了多个ImageView的切换,并配合更新导航原点的状态.那我们现在

Android 中 EventBus 的使用(2):缓存事件

在上一篇文章中,我曾提到我所选择的是Green Robot提供的EventBus(Android平台),而且这并非只是我一个人的选择.在最近一次查看中,我发现选择它的人数已经是Otto(由Jake Wharton和其他大神们在Square上所提供的版本)的两倍之多了.GR的版本显然比Otto有更多的性能提升,但最令我动心的地方在于它添加了很多新功能.今天我就打算谈谈其中的一项:通过sticky事件进行事件缓存. sticky是什么? sticky事件就是指在EventBus内部被缓存的那些事件.