Fragment引起的血案
前幾天遇到一個很奇怪的問題,主頁Fragment的onResume中的接口,存在偶現的頻繁刷接口的現象。
看上去代碼一切正常,我們也重現不出來,但是線上就是存在刷接口的現象,被我們運維頻頻吐槽。
在百思不得其解之際,終於找到突破口 : 自己對於Fragment的使用一直存在誤區。
Fragment的使用誤區
Fragment在Activity旋轉屏幕或重建後,並不會銷燬,而是存儲在saveInstanceState中。
先附上我們平時對於Fragment的用法,( 這是錯誤的 )
如果Activity發生了重建,這會導致內存中,存在兩份Fragment,
甚至於,該Activity多次重建後,就會有多個Fragment同時存在,
而這些Fragment都會重新走生命週期,導致生命週期中的接口被多次頻繁調用。
getSupportFragmentManager().beginTransaction()
.add(R.id.layout_container, SecondFragment.newInstance(), "SecondFragment")
.commit();
方案一
Fragment的show和add方法應該結合使用,如果能夠找到對應的tag的fragment,應該要show Fragment,而不是新創建一個fragment來add
正確用法:
Fragment fragment = getSupportFragmentManager().findFragmentByTag("SecondFragment");
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.show(fragment)
.commit();
}else{
getSupportFragmentManager().beginTransaction()
.add(R.id.layout_container, SecondFragment.newInstance(), "SecondFragment")
.commit();
}
方案二
在所有fragment的使用中,都判斷show和add,相對比較繁瑣,如果對於fragment重用沒有要求,那麼我們完全可以禁止fragment的緩存,從而一勞永逸,解決這個問題。
在Activity的onSaveInstanceState,加入如下代碼
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 只要發生onSaveInstanceState就remove all Fragment
if (outState != null) {
Log.i(TAG, "outState.remove");
outState.remove("android:support:fragments");
}
}
方案三
如果我們對大廠的App們進行測試,會發現一個驚人的祕密,大廠的App完全禁止了App的SaveInstanceState,即不用再考慮App銷燬重建時,數據的存儲恢復情況,這不僅僅可以避免fragment的問題,還可以避免因遺漏對某些數據進行存儲/恢復處理,導致的異常情況的發生。
當然這也有代價,那就是系統回收了App後,無法保存現場頁面狀態和數據。
@Override
public void onSaveInstanceState(Bundle outState) {
//註釋掉這行
//super.onSaveInstanceState(outState);
}
爲什麼我們的Activity會發生重建
我們的app的旋轉方向限定爲豎屏,理論上不會發生Activity重建的情況。
那是什麼場景導致Activity重建呢 ? 搜尋資料後發現
在 Android 的 API 21 ( Android 5.0 ) 以下,Crash 會直接退出應用,但是在 API 21 ( Android 5.0 ) 以上,系統會遵循以下原則進行重啓:
- 包含 Service,如果應用 Crash 的時候,運行着Service,那麼系統會重新啓動 Service。
- 不包含 Service,只有一個 Activity,那麼系統不會重新啓動該 Activity。
- 不包含 Service,但當前堆棧中存在兩個 Activity:Act1 -> Act2,如果 Act2 發生了 Crash ,那麼系統會重啓 Act1。
- 不包含 Service,但是當前堆棧中存在三個 Activity:Act1 -> Act2 -> Act3,如果 Act3 崩潰,那麼系統會重啓 Act2,並且 Act1 依然存在,即可以從重啓的 Act2 回到 Act1。
國內有些廠商可能會去除這個功能
所以,我們有時候出現崩潰,會發現App並沒有閃退,而是會恢復到上一個頁面 (上一個頁面進行了重建)。
小結
至此,我們知道了Fragment的使用中,add/show的誤區,並給出瞭解決方案。
並說明了Activity重建的情況,擴展了自己的認知邊界。
在日後的工作中,我們應該要避免Fragment錯誤使用的情況。
最後,附上用來重現該問題的Demo