Low Memory狀態下app狀態的恢復

app結構
     activity帶有ViewPager
     ViewPager上帶有4個fragment


這是非常典型的結構


操作過程
     點icon打開app
     點擊home鍵隱藏app
     再次點擊icon喚回app
     點擊home鍵隱藏app
     開啓多個其他app,要很耗費內存那種
     點擊home鍵隱藏這些app
     再次點擊icon喚回app
     app雖然喚回,但是上面的fragment已經不聽招呼,空白,無反應


仔細debug,發現按鍵能夠操作fragment,並且fragment能否反饋,但屏幕上還是顯示一個空白fragment以及一個初始狀態的變量值
所謂初始狀態的變量值,是指靜態代碼設置的值,比如如下的字符串:

     public class FragmentListview extends Fragment {
         String currentPath = "static_ini_code_null";


在靜態代碼之後的初始化過程中這個值應該被改變,但是卻沒有,可見這是一個僅僅靜態代碼初始化的fragment

困擾了很久。在activity和fragment的所有生命週期函數處添加了打印,終於發現端倪

以下是第一次喚回app的流程,打印了activity,以及0和1兩個fragment

lifecycle: DirPlayerActivity.onRestart()
lifecycle: FragmentListview.onStart() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStart() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStart()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c2c018
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c2c018
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: DirPlayerActivity.onResume()
lifecycle: FragmentListview.onResume() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onResume() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onResumeFragments()
lifecycle: FragmentListview.onPause() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onPause() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onPause()
lifecycle: FragmentListview.onStop() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStop() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStop()
lifecycle: FragmentListview.onDestroy() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onDetach() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onDestroy() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onDetach() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onDestroy()


可見喚回時是從onRestart()開始,且沒有重新生成fragment的操作
這是第二次喚回app的流程,仍然打印了activity,以及0和1兩個fragment

lifecycle: FragmentListview.newInstance() 0 FragmentListview{41c40198}
lifecycle: DirPlayerActivity.StaticCodeIniFragment{} 0 FragmentListview{41c40198}
lifecycle: FragmentListview.newInstance() 1 FragmentListview{41c4e3e8}
lifecycle: DirPlayerActivity.StaticCodeIniFragment{} 1 FragmentListview{41c4e3e8}
lifecycle: FragmentListview.onAttach() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onCreate() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onAttach() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onCreate() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onCreate()
lifecycle: FragmentListview.onCreateView() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onActivityCreated() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onCreateView() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onActivityCreated() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onStart() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStart() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStart()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 0:static_ini_code_null FragmentListview{41c40198}
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c40198}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c40198} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c3f260
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 1:static_ini_code_null FragmentListview{41c4e3e8}
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c4e3e8}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c4e3e8} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c3f260
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: DirPlayerActivity.onResume()
lifecycle: FragmentListview.onResume() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onResume() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onResumeFragments()
lifecycle: FragmentListview.onPause() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onPause() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onPause()
lifecycle: FragmentListview.onSaveInstanceState() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onSaveInstanceState() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onSaveInstanceState()
lifecycle: FragmentListview.onStop() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStop() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStop()


可見,app不是從onRestart()開始,而是完全從activity靜態代碼開始初始化activity並且到達onCreate()
並且看到來歷不明的兩個fragment被onAttach()過來,這並不是activity靜態代碼生成的fragment


一個訣竅:可以將一個fragment或其他object當成字符串打印,會打印出該fragment的內存地址(估計是)
通過這個我們可以看出每次操作的是哪個fragment或其他object,儘管它們可能是同一類對象


如果高亮上面被打印出的各個fragment地址,就會發現activity靜態代碼生成的兩個fragment被後續的activity代碼初始化了
比如被setListviewAdapter()設置了adapter,但是這兩個fragment卻走不到onCreateView()和onResume()


被attach過來的fragment未經歷activity代碼的初始化,但是卻走到了onCreateView()和onResume()
也即,app顯示了被attach過來的fragment,僅僅被該fragment的靜態代碼初始化過
這正可以解釋操作時看到的現象,被attach過來的應該是系統幫忙恢復的fragment


在官網上找到了low memory下系統可能的恢復操作,其中包括幫忙恢復fragment
但是,這個恢復的fragment怎樣接收?怎樣關聯回activity?官網上卻找不到具體的處理方法


在stackoverflow上找到若干類似的問題,其中之一和我預想的方法一致
也即,在fragment的onAttach()中將自己關聯給activity,具體關鍵代碼如下

//fragment代碼
public class FragmentListview extends Fragment {
    int tab = -1; // attach時如果爲-1,說明是系統幫忙恢復的。正常初始化後會被設置爲0和1

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);
        
        if (tab == -1){ // 系統幫忙創建的,更新到activity
         tab = ((DirPlayerActivity) getActivity()).sysAttachFragment; // 這裏計數被系統attach了多少個fragment
         fragmentListviewInterface.sysAttachFragmentListviewLowMem(
         tab, 
         this);
         ((DirPlayerActivity) getActivity()).sysAttachFragment++;
        }
    }

}
//activity代碼
public class DirPlayerActivity extends FragmentActivity implements
    FragmentListview.FragmentListviewInterface
    {
        int sysAttachFragment = 0;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_dir_player);
            for (int i = 0; i < LocalConst.tabCount; i++) {
                if(sysAttachFragment == 0){ // 正常啓動,而不是系統幫忙恢復
                    fragmentListview[i] = FragmentListview.newInstance(i);
                }
        }
    }

    public void sysAttachFragmentListviewLowMem(int tab, FragmentListview fragment){
               fragmentListview[tab] = fragment;
    }

}

簡單測試了一下,似乎可行

但是,感覺很不好
因爲fragment也許和activity有很多關聯,有很複雜的初始化過程
系統幫忙恢復的操作卻非常的粗魯
直接生成一個未正常初始化的fragment就強塞過來,並佔領了對應的位置
這時候千頭萬緒,關聯和恢復處理相當麻煩不可靠


在low memory的情況下恢復app的狀態有必要嗎?值得嗎?
至少有一些app不會有這樣的需求
所以,系統應該提供一個選項,讓app自己選擇要不要恢復原來的狀態
有這樣的選項嗎?我沒找到。或有,只是我不知道而已?


在我的這個項目中我就不希望系統幫忙恢復fragment
讓app代碼自己處理,結果會乾淨清爽得多,安心得多

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章