1. 程式人生 > >記錄一次關於Activity與Fragment生命週期引起的異常

記錄一次關於Activity與Fragment生命週期引起的異常

在Activity和Frgment生命週期中對於資料儲存應該是大部分都能儲存的,像按Home返回到後臺,再切換回來後應該不會出大問題的,但一次記憶體過底把儲存的Activity給Destroy後引起了錯誤,具體是Activity裡Fragment A裡有一個Fragment B, Fragment B 按理是在A的onCreate裡進行網路請求後非同步建立,那麼Activity銷燬後走OnCreate應該不會有什麼問題。

但是呢,Fragment B報空指標。這便引出這個文章。

Activity & Fragment

介紹文章直接有官方網站地址如下:

activity_lifecycle

instance_restore

fragment_lifecycle

activity_fragment_lifecycle

上面的圖是初學者應該瞭解的,但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: ");
    }

saveinstance

在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
        } 

    }

參考