Android中FragmentPagerAdapter對Fragment的緩存(一)

ViewPager + FragmentPagerAdapter,時咱們常常使用的一對搭檔,其實際應用的代碼也很是簡單,可是也有一些容易被忽略的地方,此次咱們就來討論下FragmentPagerAdapter對Fragment的緩存應用。java

 咱們能夠先看看最簡單的實現,自定義Adapter以下:android

[代碼]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:git

[代碼]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最基本的應用。如今,看上去一切都如咱們所願,可是真的沒有任何問題了嗎?github

 接下來,咱們來模擬一下程序運行在後臺時,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安全管家等應用,選擇清理內存時,也會觸發以上狀況。ide

 那麼當上面的流程發生時,Activity的onSaveInstanceState會被調用,以便咱們能夠保存當前的用戶數據/頁面狀態等。當恢復時,在onCreate時,咱們經過savedInstanceState參數,能夠取到以前存儲的數據,而後從新綁定到View上。this

 這個過程均可以理解,但是回到咱們的Activity代碼當中:spa

[代碼]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字段仍然爲空呢?3d

 難道說,呈如今屏幕上的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字段爲空,以及怎樣解決這個狀況,咱們下一篇再來討論^_^。

相關文章
相關標籤/搜索