自從在Android 3.0引入Fragment以來,它被使用的頻率也隨之增多。Fragment帶來的好處不言而喻,解決了不同屏幕分辨率的動態和靈活UI設計。但是在Activity管理多個Fragment中,通常會遇到這些問題:
1、Fragment的狀態保存
2、Fragment的重影
當然,這些問題也一直出現我的開發過程中,雖然有時候通過各種手段也能解決一些問題,但是總是同時完美解決這兩個問題。近來因為項目需要,查閱了很多官方資料(Android官方資料也慢慢有中文資料了,我大Google果然是Don't be evil,扯遠了~~),終於徹底解決了這些問題。
設備:nexus 5
條件:
1、打開“不保留活動”(開發者選項里,主要用於模擬Activity被及時回收)
2、關閉“不保留活動”(正常狀態下)
結果:目前沒發現問題,由於設備有限,大家如果發現在其他設備上有問題,請在下方回帖!
首先我先來解釋下上面問題出現的原因:
1、有時候,我們需要在多個Fragment間切換,並且保存每個Fragment的狀態。官方的方法是使用replace()來替換Fragment,但是replace()的調用會導致Fragment的onCreteView()被調用,所以切換界面時會無法保存當前的狀態。因此一般采用add()、hide()與show()配合,來達到保存Fragment的狀態。以下為代碼片段:
private void setTabSelection(int position) { //記錄position this.position = position; //更改底部導航欄按鈕狀態 changeButtonStatus(position); FragmentTransaction transaction = fragmentManager.beginTransaction(); // 先隱藏掉所有的Fragment,以防止有多個Fragment顯示在界面上的情況 hideFragments(transaction); switch (position) { case TAB_HOME: btnHomePager.setSelected(true); btnShoppingCart.setSelected(false); btnMine.setSelected(false); if (homeFragment == null) { homeFragment = new HomePagerFragment(); transaction.add(R.id.fragment_container, homeFragment); } else { transaction.show(homeFragment); } break; case TAB_SHOP: btnHomePager.setSelected(false); btnShoppingCart.setSelected(true); btnMine.setSelected(false); if (shoppingFragment == null) { shoppingFragment = new ShoppingCartFragment(); transaction.add(R.id.fragment_container, shoppingFragment); } else { transaction.show(shoppingFragment); } break; case TAB_MINE: btnHomePager.setSelected(false); btnShoppingCart.setSelected(false); btnMine.setSelected(true); if (mineFragment == null) { mineFragment = new MineFragment(); transaction.add(R.id.fragment_container, mineFragment); } else { transaction.show(mineFragment); } break; } transaction.commitAllowingStateLoss(); }
2、第二個問題的出現正是因為使用了Fragment的狀態保存,當系統內存不足,Fragment的宿主Activity回收的時候,Fragment的實例並沒有隨之被回收。Activity被系統回收時,會主動調用onSaveInstance()方法來保存視圖層(View Hierarchy),所以當Activity通過導航再次被重建時,之前被實例化過的Fragment依然會出現在Activity中,然而從上述代碼中可以明顯看出,再次重建了新的Fragment,綜上這些因素導致了多個Fragment重疊在一起。
我嘗試了很多種方法去解決這個問題,比如:
在onSaveInstance()里面去remove()所有非空的Fragment,然后在onRestoreInstanceState()中去再次按照問題一的方式創建Activity。當我處於打開“不保留活動”的時候,效果非常令人滿意,然而當我關閉“不保留活動”的時候,問題卻出現了。當轉跳到其他Activity、打開多任務窗口、使用Home回到主屏幕再返回時,發現根本沒有Fragment了,一篇空白。
於是跟蹤下去,我調查了onSaveInstanceState()與onRestoreInstanceState()這兩個方法。原本以為只有在系統因為內存回收Activity時才會調用的onSaveInstanceState(),居然在轉跳到其他Activity、打開多任務窗口、使用Home回到主屏幕這些操作中也被調用,然而onRestoreInstanceState()並沒有在再次回到Activity時被調用。而且我在onResume()發現之前的Fragment只是被移除,並不是空,所以就算你在onResume()中執行問題一中創建的Fragment的方法,同樣無濟於事。所以通過remove()宣告失敗。
接着通過調查資料發現Activity中的onSaveInstanceState()里面有一句super.onRestoreInstanceState(savedInstanceState),Google對於這句話的解釋是“Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執行這句代碼來調用父類去保存視圖層的狀態”。其實到這里大家也就明白了,就是因為這句話導致了重影的出現,於是我刪除了這句話,然后onCreate()與onRestoreInstanceState()中同時使用問題一中的創建Fragment方法,然后再通過保存切換的狀態,發現結果非常完美。代碼如下:
//記錄Fragment的位置 private int position = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_index); setTabSelection(position); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { position = savedInstanceState.getInt("position"); setTabSelection(position); super.onRestoreInstanceState(savedInstanceState); } @Override protected void onSaveInstanceState(Bundle outState) { //記錄當前的position outState.putInt("position", position); }
記錄於此,希望能幫助到一些正遇到這種問題的朋友!