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字段为空,以及怎样解决这个情况,我们下一篇再来讨论^_^。

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

  1. Android中FragmentPagerAdapter对Fragment的缓存(二)

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

  2. android 中的 ViewPager+ Fragment

    android的Viewpager 的各种经常的用法,朋友问我要过,所以就稍微总结一下, ViewPager + Fragment 经常用到  代码是从   actionbarsherlock 中提取 ...

  3. Android中Activity加入Fragment使用注意事项及常用技巧

    Fragment中AlertDialog弹出窗口的使用 Fragment默认不具有Content的一些方法和属性,因此在Activity中可以使用的一些方法在Fragment中使用就需要一些小技巧了 ...

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

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存 ...

  5. Android中Fragment和ViewPager那点事儿(仿微信APP)

    在之前的博文<Android中使用ViewPager实现屏幕页面切换和引导页效果实现>和<Android中Fragment的两种创建方式>以及<Android中Fragm ...

  6. Android中Fragment+ViewPager的配合使用

    官方推荐 ViewPager与Fragment一起使用,可以更加方便的管理每个Page的生命周期,这里有标准的适配器实现用于ViewPager和Fragment,涵盖最常见的用例.FragmentPa ...

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

    Fragment支持在不同的Activity中使用并且可以处理自己的输入事件以及生命周期方法等.可以看做是一个子Activity. 先看一下布局: 1 <LinearLayout xmlns:a ...

  8. [Android] Android ViewPager 中加载 Fragment的两种方式 方式(二)

    接上文: https://www.cnblogs.com/wukong1688/p/10693338.html Android ViewPager 中加载 Fragmenet的两种方式 方式(一) 二 ...

  9. Android中ViewPager实现滑动条及与Fragment结合的实例教程

    ViewPager类主要被用来实现可滑动的视图功能,这里我们就来共同学习Android中ViewPager实现滑动条及与Fragment结合的实例教程,需要的朋友可以参考下 自主实现滑动指示条先上一个 ...

随机推荐

  1. POJ 1504 Adding Reversed Numbers (水题,高精度整数加法)

    题意:给两个整数,求这两个数的反向数的和的反向数,和的末尾若为0,反向后则舍去即可.即若1200,反向数为21.题目给出的数据的末尾不会出现0,但是他们的和的末尾可能会出现0. #include &l ...

  2. javascript中创建插入元素

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  3. javascript分享到的源码

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  4. APT攻击

    http://netsecurity.51cto.com/art/201211/363040.htm

  5. 《架构探险——从零开始写Java Web框架》这书不错,能看懂的入门书

    这书适合我. 哈哈,结合 以前的知识点,勉强能看懂. 讲得细,还可以参照着弄出来. 希望能坚持 完成啦... 原来,JSTL就类似于DJANGO中的模板. 而servlet类中的res,req,玩了D ...

  6. hdu 1517 A Multiplication Game 博弈论

    思路:求必胜区间和必败区间! 1-9 先手胜 10-2*9后手胜 19-2*9*9先手胜 163-2*2*9*9后手胜 …… 易知右区间按9,2交替出现的,所以每次除以18,直到小于18时就可以直接判 ...

  7. 序列dp笔记√

    例题: http://www.cnblogs.com/gc812/p/5792181.html http://www.cnblogs.com/gc812/p/5792217.html

  8. Java开发--操作MongoDB

    http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html介绍到了在MongoDB的控制台完成MongoDB的数据操作,通过前一篇文章我们 ...

  9. 【Spring】Redis的两个典型应用场景--good

    原创 BOOT Redis简介 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化. ...

  10. csh与bash比较

    csh与bash比较:一.csh的while循环控制结构及if then:#!/bin/csh -fwhile ($#argv >= 1)  if ("$1" == &quo ...