Android Do not keep activities選項分析


Android Do not keep activities選項分析

Developer Options里面有一項:
Do not keep activities -> 不保留Activities.
默認是不開啟的.
當開啟之后,用戶離開后即銷毀每個Activity.
 

相關背景知識: task, back stack和低內存時的系統行為

當用戶開啟一個task,里面的activities都會被保存在這個棧的back stack中.
當前的activity在棧頂並且擁有焦點,之前的activities都被壓入棧中,處於stopped的狀態.
 

Android系統后台是可以保存多個task的.

當用戶開啟另一個新的task或者Home鍵退回到桌面的時候,task將會變為在后台運行.
當task在后台時,其中所有的activities都是停止的(stopped),其back stack保持不變,這個task只是失去了焦點.
task可以回到前台狀態,即頂端的activity會被resume,這樣用戶就可以回到他們離開的現場.
清理back stack:
如果用戶離開一個task很長時間,系統將會清理task中的所有activity, 只保留根activity, 當用戶返回到這個task的時候, 只有根activity會被恢復. 這個行為也可以通過一些activity的屬性來控制.
 

系統可能會kill掉activity.

當activity停止時(stopped), 系統的默認行為是保存它的狀態, 但是當系統的內存不足時, 可能會kill掉activity, 這時候activity的信息會丟失.
即便是kill掉了activity, 系統仍然知道這個activity在back stack中的位置, 當這個activity需要返回到棧頂的時候, 系統會重建(recreate)它, 而不是像之前一樣恢復(resume)它.
重建的時候有一些數據會丟失,比如一些成員變量的值.
為了不丟失用戶的工作, 可以使用回調 onSaveInstanceState()來主動地保存數據, 以防activity被系統銷毀而需要重建.
 

Do not keep activities開關作用

當這個開關被打開,所有被stop的activity都會被立即銷毀.
打開Do not keep activities開關, 可以很方便地模擬出內存不足時,系統kill activity的極端情況.
打開此開關可以用來檢驗應用是否在activity停止時保存了需要的數據, 即是否可以在activity回到前台時復原數據, 甚至可以檢查出一些沒有做好保護的異常.
所以在開發應用的時候打開這個開關做一些測試和保護是很有益的.
 
但是這個開關只處理了Activity, 對於系統在內存不足時殺死Service的情況並不能模擬出來.
 
 

Do not keep activities開關對Activity的生命周期影響

做了一個小實驗對比這個開關打開和關閉的情況:
實驗很簡單,有一個Activity A, 里面有一個button,點擊即打開Activity B, B可以finish自己, 再回到A.
即A -> B -> A.
兩個Activity的launch mode都是standard, 即默認情況.
打印出它們的生命周期回調函數.
正常情況下是這樣的:
可以看到當B打開以后, A只是onStop()了; 當B finish自己, A重新調用了onRestart()->onResume().
 
當Do not keep activities開關打開,同一個程序,輸出如下:
可以看到當B打開以后, A不僅調用了onStop(), 還調用了onDestroy(), 當B finish自己,再返回A的時候, A重新調用了onCreate() -> onResume(), 而並不是onRestart().
這說明activity A已經被重新創建, 不再是之前的那個實例, 所有未在 onSaveInstanceState()回調方法中保存的數據都已經丟失.
 
這里可以參見Activity的生命周期:  http://www.cnblogs.com/mengdd/archive/2012/12/01/2797784.html 
 

Do not keep activities開關對Fragment的生命周期影響

先來復習一下Fragment的生命周期:
關於Fragment的生命周期可以查看:  http://developer.android.com/guide/components/fragments.html#Lifecycle
 
我寫了一個Demo, 一個自定義的Fragment A,一開始就加在Activity的布局里.
Fragment A和Activity的生命周期回調函數調用順序如圖:
可以看到,在創建階段,Activity的回調總是先調用的,Fragment的幾個回調后調用.
而在暫停到停止和銷毀階段,總是先調用Fragment的回調,再調用同階段的Activity回調.
可以總結為: Activity是個早出晚歸的勤勞老大哥.
 
通過點擊Activity中的UI動態添加的另一個Fragment B, 則是在添加之后一股腦調用Fragment的回調直到它在最前面:
 
好啦,復習完畢,現在來說Do not keep activities開關.
 
 
我的操作步驟如下:
1.打開Activity, FragmentA寫在activity的布局里,所以自動添加;
2.點擊toggle添加FragmentB;
3.Home鍵退出;
4.再打開應用,應用應該自動返回到離開前的Activity.
 
正常狀況下Log如下:
 
而當Do not keep activities開關開啟之后,Log的前半部分是一樣的,在此略去(太長了), 后半部分如下:
 
可見一旦Home鍵退出,Fragment被銷毀,再次進來時重建,並且恢復到原來所在的布局中去.
 
只是因為Activity在 onSaveInstanceState()中保存了View和Fragment的狀態, 在 onRestoreInstanceState()恢復了這些狀態.
可以看到log中的android:viewHierarchyState是View的狀態
android:fragments=android.app.FragmentManagerState@42ac6328是Fragment的狀態.
 
所以在override這兩個方法保存其他數據時, 一般都需要調用super的方法.
 
需要處理的問題:
開始的時候Demo里動態添加FragmentB用了一個ToggleButton:
@OnCheckedChanged(R.id.fragment_b_control_toggle)
void toggleFragmentB(CompoundButton toggleButton, boolean checked) {
    Log.d(LOG_TAG, "toggle " + checked);
    //this will have bug when "Do no keep activities" turned on
    //because, when the FragmentB is shown, home exit and enter again, the system auto recover the fragment
    //the checked status is also recovered, this method will invoke once, with the checked is true
    //So, the fragmentB is added twice, and remove button can only remove one of them
    if (checked) {
        addFragmentB();
    } else {
        removeFragmentB();
    }

}

添加方法和移除方法如下:

private void addFragmentB() {
    //You can also add/remove fragment dynamically
    FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentB = new FragmentB();
    fragmentTransaction.add(R.id.fragment_container, fragmentB);
    fragmentTransaction.commit();
}

private void removeFragmentB() {
    FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.remove(fragmentB);
    fragmentTransaction.commit();
}

 

在Do not keep activities開啟的時候就出現了問題,因為返回回來系統恢復了一個FragmentB,為了恢復View的狀態, toggleButton的事件又進入了一次,所以又add了一個新的FragmentB.
 
想了一些辦法,比如:
fragmentB.isAdded()判斷: 不管用,因為fragmentB是新的實例.
把FragmentB寫成單例: Home退出又返回的時候就拋異常了,因為系統在自動恢復的時候無法調用它的構造方法.
 
后來用了tag解決了:
private void addFragmentB() {
    FragmentManager fragmentManager = getFragmentManager();
    Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG);
    if (null == fragment) {
        FragmentB fragmentB = new FragmentB();
        Log.i(LOG_TAG, "do add fragmentB action");
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, fragmentB, FragmentB.TAG);
        fragmentTransaction.commit();
    }
}

private void removeFragmentB() {
    FragmentManager fragmentManager = getFragmentManager();
    Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG);
    if (null != fragment) {
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(fragment);
        fragmentTransaction.commit();
    }
}

 

當然這只是我的小demo的問題解決了,實際項目中要看實際情況了,雖然開始感覺比較復雜,知道原理之后就可以抽絲剝繭了.
 

參考資料:

博文:
 
 
關於狀態恢復:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM