上一篇咱們談到了,當應用程序恢復時,因爲FragmentPagerAdapter對Fragment進行了緩存的讀取,致使其並未使用在Activity中新建立的Fragment實例。今天咱們來看如何解決這種狀況。php
根據上篇Blog的描述,咱們不難發現,目前須要解決的問題有如下兩個:java
1. 緩存Fragment內部成員變量缺失的問題。android
2. 新Fragment的建立和緩存Fragment使用之間的矛盾。git
下面先來解決第一個問題,緩存Fragment內部成員變量缺失。上篇Blog中,Fragment當中,有一個成員變量mText,是經過setter的方式在建立Fragment之初設置進去的。可是在經歷了一系列的存儲和恢復操做事後,其值在最終卻爲空,致使了程序展現的異常。那麼能不能讓mText也在Fragment中同步緩存和恢復呢?github
最早能想到的方法,就是經過Fragment的onSaveInstanceState方法在進程被殺掉時存儲,當恢復時經過onCreateView的savedInstanceState參數取出;代碼以下:數組
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或者重寫默認構造器的方式來傳遞參數:ide
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().ui
緣由是,當Fragment從新被恢復時,不會去從新調用這些setter/有參構造方法; 而是會調用onCreateView,咱們卻能夠在其中從新調用getArguments去獲取這些參數。這就保證了在恢復事後,咱們須要傳入的參數能夠從新被設置。一番改造以後以下:this
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裏面去手動保存了。
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代碼以下:
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方法,以下:
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的代碼以下:
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緩存的分析就是這麼多了,歡迎指正。