Android中FragmentPagerAdapter對Fragment的緩存(一)


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM