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字段爲空,以及怎樣解決這個狀況,咱們下一篇再來討論^_^。