記錄一次關於Activity與Fragment生命週期引起的異常
在Activity和Frgment生命週期中對於資料儲存應該是大部分都能儲存的,像按Home返回到後臺,再切換回來後應該不會出大問題的,但一次記憶體過底把儲存的Activity給Destroy後引起了錯誤,具體是Activity裡Fragment A裡有一個Fragment B, Fragment B 按理是在A的onCreate裡進行網路請求後非同步建立,那麼Activity銷燬後走OnCreate應該不會有什麼問題。
但是呢,Fragment B報空指標。這便引出這個文章。
Activity & Fragment
介紹文章直接有官方網站地址如下:
上面的圖是初學者應該瞭解的,但Android開發一段時間後也應該複習一下。具體情景是DetailActivity裡replace一個framelayout後新增的fragment A, 在fragment A裡再使用相同方式新增 fragment B, 但使用的是fragmentManager 是呼叫 A的getChildFragmentManager()
正常情況下看列印的資料
05-24 17:24:18.584 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onCreate:
05-24 17:24:18.586 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onAttach:
05-24 17:24:18.586 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onCreate:
05-24 17:24:18.587 22965-22965/com.yorkyu .fragmentlifedemo D/AFragment: onCreateView:
05-24 17:24:18.622 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onViewCreated:
05-24 17:24:18.622 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onActivityCreated:
05-24 17:24:18.622 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onViewStateRestored:
05 -24 17:24:18.622 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onStart:
05-24 17:24:18.622 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onStart:
05-24 17:24:18.623 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onResume:
05-24 17:24:23.593 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onAttach:
05-24 17:24:23.594 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreate:
05-24 17:24:23.595 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreateView:
05-24 17:24:23.612 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewCreated:
05-24 17:24:23.612 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onActivityCreated:
05-24 17:24:23.613 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewStateRestored:
05-24 17:24:23.613 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onStart:
05-24 17:24:23.613 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onResume:
這裡按home鍵後
05-24 17:26:50.408 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onPause:
05-24 17:26:50.409 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onPause:
05-24 17:26:50.409 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onPause:
05-24 17:26:50.726 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onSaveInstanceState:
05-24 17:26:50.730 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onSaveInstanceState:
05-24 17:26:50.730 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onSaveInstanceState:
05-24 17:26:50.730 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onStop:
05-24 17:26:50.730 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onStop:
05-24 17:26:50.730 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onStop:
也很好對應上面的圖片了。相同之處是在onPause後呼叫了onSaveInstanceState
。官方也介紹瞭如下,
不過,即使您什麼都不做,也不實現 onSaveInstanceState(),Activity 類的 onSaveInstanceState() 預設實現也會恢復部分 Activity 狀態。具體地講,預設實現會為佈局中的每個 View 呼叫相應的 onSaveInstanceState() 方法,讓每個檢視都能提供有關自身的應儲存資訊。Android 框架中幾乎每個小部件都會根據需要實現此方法,以便在重建 Activity 時自動儲存和恢復對 UI 所做的任何可見更改。例如,EditText 小部件儲存使用者輸入的任何文字,CheckBox 小部件儲存複選框的選中或未選中狀態。您只需為想要儲存其狀態的每個小部件提供一個唯一的 ID(通過 android:id 屬性)。如果小部件沒有 ID,則系統無法儲存其狀態。
儘管 onSaveInstanceState() 的預設實現會儲存有關您的Activity UI 的有用資訊,您可能仍需替換它以儲存更多資訊。例如,您可能需要儲存在 Activity 生命週期內發生了變化的成員值(它們可能與 UI 中恢復的值有關聯,但預設情況下系統不會恢復儲存這些 UI 值的成員)。
由於 onSaveInstanceState() 的預設實現有助於儲存 UI 的狀態,因此如果您為了儲存更多狀態資訊而替換該方法,應始終先呼叫 onSaveInstanceState() 的超類實現,然後再執行任何操作。 同樣,如果您替換 onRestoreInstanceState() 方法,也應呼叫它的超類實現,以便預設實現能夠恢復檢視狀態。
注:由於無法保證系統會呼叫 onSaveInstanceState(),因此您只應利用它來記錄 Activity 的瞬態(UI 的狀態)— 切勿使用它來儲存永續性資料,而應使用 onPause() 在使用者離開 Activity 後儲存永續性資料(例如應儲存到資料庫的資料)。
再看下按回的列印
05-24 17:31:42.592 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onRestart:
05-24 17:31:42.593 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onStart:
05-24 17:31:42.593 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onStart:
05-24 17:31:42.593 22965-22965/com.yorkyu.fragmentlifedemo D/DetailActivity: onStart:
05-24 17:31:42.594 22965-22965/com.yorkyu.fragmentlifedemo D/AFragment: onResume:
05-24 17:31:42.595 22965-22965/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onResume:
執行的生命週期不多,不會走onCreate,而且都沒有銷燬,所以看不是問題。這裡如果開啟開發者選項中的不保留活動, 那麼問題就來了,Activity會Destory
05-24 11:13:03.969 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onPause:
05-24 11:13:03.970 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onPause:
05-24 11:13:03.970 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onPause:
05-24 11:13:04.911 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onSaveInstanceState:
05-24 11:13:04.912 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onSaveInstanceState:
05-24 11:13:04.928 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onSaveInstanceState:
05-24 11:13:04.929 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onStop:
05-24 11:13:04.929 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onStop:
05-24 11:13:04.929 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onStop:
05-24 11:13:04.950 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onDestroyView:
05-24 11:13:04.951 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onDestroyView:
05-24 11:13:04.952 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onDestroy:
05-24 11:13:04.952 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onDetach:
05-24 11:13:04.953 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onDestroy:
05-24 11:13:04.953 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onDetach:
05-24 11:13:04.953 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onDestroy:
返回列印結果
05-24 11:13:31.331 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onAttach:
05-24 11:13:31.331 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onAttach:
05-24 11:13:31.332 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreate:
05-24 11:13:31.332 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onCreate:
05-24 11:13:31.358 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onCreate:
05-24 11:13:31.360 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onCreateView:
05-24 11:13:31.392 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onViewCreated:
05-24 11:13:31.392 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onActivityCreated:
05-24 11:13:31.392 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreateView:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewCreated:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onActivityCreated:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewStateRestored:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onViewStateRestored:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onAttach:
05-24 11:13:31.396 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onCreate:
05-24 11:13:31.397 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onDestroyView:
05-24 11:13:31.397 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onDestroyView:
05-24 11:13:31.397 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onCreateView:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onViewCreated:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onActivityCreated:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onViewStateRestored:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onStart:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onStart:
05-24 11:13:31.400 12289-12289/com.yorkyu.fragmentlifedemo D/DetailActivity: onRestoreInstanceState:
05-24 11:13:31.401 12289-12289/com.yorkyu.fragmentlifedemo D/AFragment: onResume:
05-24 11:13:31.405 12289-12289/com.yorkyu.fragmentlifedemo D/ActivityThreadInjector: clearCachedDrawables.
05-24 11:13:36.367 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onAttach:
05-24 11:13:36.367 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreate:
05-24 11:13:36.399 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onAttach:
05-24 11:13:36.399 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreate:
05-24 11:13:36.400 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onCreateView:
05-24 11:13:36.413 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewCreated:
05-24 11:13:36.413 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onActivityCreated:
05-24 11:13:36.413 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onViewStateRestored:
05-24 11:13:36.414 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onStart:
05-24 11:13:36.414 12289-12289/com.yorkyu.fragmentlifedemo D/BFragment - main - 1: onResume:
看到fragment B會先onCreate再destoryview,然後非同步方法再new instance 一個新的, 因此產生多個例項,造成記憶體洩露。其實fragment A也同的情況。
解決辦法
onSaveInstanceState裡處理
必須在回撥前通過fragmentManager移除當前頁面的fragment,這樣再走onCreate都會new Instance,這樣也有一個問題就是每次跳轉頁面返回這個頁面後一樣會new Instance,效能不是太好。但解決了非同步動態新增Fragment和Activity及Fragment自動恢復機制引起重複建立。我觀察過這種情況大概在我這個應用裡會造成每次1MB左右的記憶體洩露。
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
int backStackEntryCount = mChildFragmentManager.getBackStackEntryCount();
for (int i = 0; i < backStackEntryCount; i++) {
mChildFragmentManager.popBackStackImmediate();
}
Log.d(TAG, "onSaveInstanceState: ");
}
在OnCreate()或者onRestart()裡判斷
既然無法很好在onDestory去remove fragment,那麼就配合activity 與fragment的自動儲存機制,在onSaveInstanceState()裡保留自動保留外的資料。大意程式碼如下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: ");
if (savedInstanceState != null) {
// do something
} else {
// do anothering
}
}