ViewPager + FragmentPagerAdapter,时咱们常常使用的一对搭档,其实际应用的代码也很是简单,可是也有一些容易被忽略的地方,此次咱们就来讨论下FragmentPagerAdapter对Fragment的缓存应用。java
咱们能够先看看最简单的实现,自定义Adapter以下:android
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:git
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最基本的应用。如今,看上去一切都如咱们所愿,可是真的没有任何问题了吗?github
接下来,咱们来模拟一下程序运行在后台时,Android系统因为内存紧张,杀掉咱们程序进程的状况:缓存
- 首先运行程序至前台
- 接下来,点击Home键,返回桌面,同时咱们的程序退回至后台运行。
- 进入Android Studio中,点击Android Monitor这个tab,并选择当前Device,并选择咱们程序的进程名。
- 点击Terminal Application这个小红叉按钮,以下图:
- 这个时候后台进程已经被杀掉了,可是应用程序历史里咱们的应用还在,因此长按Home键,并选择咱们的程序,让其恢复到前台。
- 这时会看到,程序的确恢复到以前的页面。但奇怪的是,页面上却只有Hello,咱们以前传入的Two到哪里去了?
Fragment代码也比较简单,经过日志,咱们发现恢复时,mText字段为空。因此页面上对应的TextView没法显示。安全
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安全管家等应用,选择清理内存时,也会触发以上状况。ide
那么当上面的流程发生时,Activity的onSaveInstanceState会被调用,以便咱们能够保存当前的用户数据/页面状态等。当恢复时,在onCreate时,咱们经过savedInstanceState参数,能够取到以前存储的数据,而后从新绑定到View上。this
这个过程均可以理解,但是回到咱们的Activity代码当中:spa
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字段仍然为空呢?3d
难道说,呈如今屏幕上的Fragment,和咱们在onCreate中实例化的Fragment,已然不是同一个实例?
为了验证这个想法,在OnCreate中加入下面的日志:
1
2
3
|
TestFragment fragmentOne =
new
TestFragment();
fragmentOne.setText(
"One"
);
Log.i(
"test"
,
"++++fragmentOne++++:"
+ fragmentOne.toString());
|
同时在TestFragment的onCreateView方法中也记下日志:
1
|
Log.i(
"test"
,
"++++current fragment++++:"
+
this
.toString());
|
第一次运行,恩,没有问题。建立和运行的都是同一个实例 534ed278
1
2
|
I/test: ++++fragmentOne++++:TestFragment{534ed278}
I/test: ++++current fragment++++:TestFragment{534ed278 #
0
id=
0x7f0c0066
android:switcher:
2131492966
:
0
}
|
接下来,咱们再次进行杀进程并恢复的过程。日志输出为:
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方法中发现了下面这一段:
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方法以下:
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字段为空,以及怎样解决这个状况,咱们下一篇再来讨论^_^。