Android開發(fā)進階:Activity和進程的回收和狀態(tài)恢復

不管是安卓的官方文檔還是源碼注釋,處處可見“從 Activity A 跳到 Activity B,當系統(tǒng)內(nèi)存不足時 A 可能會被回收……”,而且沒有明確說明 A 和 B 是否屬于同一個 app 或進程。

但是,在官方給的 Activity 生命周期圖中,卻說內(nèi)存不足時低優(yōu)先級的進程將被殺死。


image.png

那么,內(nèi)存不足時,到底是 Activity 被回收了呢,還是進程被殺死了呢,還是二者都出現(xiàn)了呢?

答案是,Activity 被回收了,而且進程被殺死了,而且一般情況下該進程是后臺進程。當內(nèi)存不足時,系統(tǒng)會殺死優(yōu)先級低的后臺進程,進程內(nèi)的 Activity 肯定也就被回收了。

這就引申出另一個話題:app的進程被回收后,當用戶切回app時,我們應該怎樣保證activity的狀態(tài)得到恢復呢?

我們知道,在安卓開發(fā)中,當一個activity要啟動另一個activity時要傳遞數(shù)據(jù)的話,普遍的做法是將數(shù)據(jù)放在intent中:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("data", "Data to Second Activity");
startActivity(intent);

在SecondActivity中可以通過intent.getStringExtra("data")獲得數(shù)據(jù)。如果這個數(shù)據(jù)要往更深層的activity傳遞的話,就要繼續(xù)將其放入啟動后續(xù)activity的intent中

String data = intent.getStringExtra("data");
// do something with data ....
Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
intent.putExtra("data", data);
startActivity(intent);

很多安卓開發(fā)的新手都會問,如果這個數(shù)據(jù)到在多個activity之間傳遞,為什么我們不把他作為成員變量放在一個全局的類(比如現(xiàn)成的Application類,或者一個全局可獲取的單例類)中,這樣這個數(shù)據(jù)就不用每次放在intent中傳來傳去了,豈不是方便很多?

答案很簡單:不能這樣做。如前面所述,當app處于后臺時被回收時,所有的全局單例類(包括Application實例)都會被銷毀,存在里面的數(shù)據(jù)也就跟著丟失了。當app被切回前臺時,依賴于這些數(shù)據(jù)的activity就會取不到數(shù)據(jù)。

為什么用Intent傳數(shù)據(jù)沒有問題呢?因為系統(tǒng)維護的task和activity棧幫我們處理了Intent(以及其中的數(shù)據(jù))的保存和恢復。簡單來說,所有“曾用于啟動activity的intent”和“還沒有被銷毀的activity”都會被系統(tǒng)維護在task棧中,并且當進程被回收時,安卓系統(tǒng)會自動幫我們把這些信息保存起來。

image.png

當用戶嘗試把一個被回收的app切回前臺時,系統(tǒng)會用之前保存的task棧信息來嘗試把app恢復到被回收之前的狀態(tài),而通過intent傳遞的數(shù)據(jù)也在這個過程中得到了還原。下面用一個簡單的例子來說明。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.e("lifecycle", "MainActivity onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnStartSecond = (Button)findViewById(R.id.button_start_second);
        btnStartSecond.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("lifecycle", "Start SecondActivity from MainActivity");
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                intent.putExtra("data", "Data to Second Activity");
                startActivity(intent);
            }
        });
    }
    @Override
    protected void onStart() {
        Log.e("lifecycle", "MainActivity onStart");
        super.onStart();
    }
    @Override
    protected void onRestart() {
        Log.e("lifecycle", "MainActivity onRestart");
        super.onRestart();
    }
    @Override
    protected void onResume() {
        Log.e("lifecycle", "MainActivity onResume");
        super.onResume();
    }
    @Override
    protected void onPause() {
        Log.e("lifecycle", "MainActivity onPause");
        super.onPause();
    }
    @Override
    protected void onStop() {
        Log.e("lifecycle", "MainActivity onStop");
        super.onStop();
    }
}

public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.e("lifecycle", "SecondActivity onCreate");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Intent intent = getIntent();
        if(intent != null) {
            String data = intent.getStringExtra("data");
            Log.e("lifecycle", "data from getIntent(): " + data);
            ((TextView)findViewById(R.id.text_second)).setText(data);
        } else {
            Log.e("lifecycle", "getIntent() returns null");
        }
    }
    @Override
    protected void onStart() {
        Log.e("lifecycle", "SecondActivity onStart");
        super.onStart();
    }
    @Override
    protected void onRestart() {
        Log.e("lifecycle", "SecondActivity onRestart");
        super.onRestart();
    }
    @Override
    protected void onResume() {
        Log.e("lifecycle", "SecondActivity onResume");
        super.onResume();
    }
    @Override
    protected void onPause() {
        Log.e("lifecycle", "SecondActivity onPause");
        super.onPause();
    }
    @Override
    protected void onStop() {
        Log.e("lifecycle", "SecondActivity onStop");
        super.onStop();
    }
}

我們的app lifecycletest的MainActivity啟動了SecondActivity并通過Intent傳遞了一個字符串數(shù)據(jù)。我們在SecondActivity界面把app切回后臺,然后通過啟動其他app占用內(nèi)存來促使系統(tǒng)回收lifecycletest app:


image.png

可以看到lifecycletest的進程已經(jīng)被回收了。當切回app時:


image.png

可以看到系統(tǒng)建立了一個新的進程,SecondActivity的生命周期函數(shù)被依次執(zhí)行,而onCreate函數(shù)中通過getIntent仍然能夠取到之前從MainActivity傳過來的數(shù)據(jù),用戶也正確地回到了SecondActivity界面,看到了正確的數(shù)據(jù)。
細心的讀者可以看到這時MainActivity的生命周期函數(shù)沒有被執(zhí)行。這是因為系統(tǒng)恢復進程被回收的app時,只會執(zhí)行task棧頂?shù)腶ctivity的生命周期函數(shù)。如果用戶點返回的話,會執(zhí)行MainActivity的生命周期函數(shù),正確的退回到MainActivity。
image.png

需要注意的是,一個intent里面能放的數(shù)據(jù)大小是有限制的,最好是不超過500kb(不同的系統(tǒng)版本限制不一,可參考相關(guān)資料)。比如在intent里面放一個byte[ ]數(shù)組的話,如果數(shù)組大小太大,就會導致運行時拋異常。所以如果要在activity之間傳大量數(shù)據(jù)的話,最好把數(shù)據(jù)存為臨時文件,然后在intent中傳文件的路徑。
總結(jié):
1.當app處于后臺被系統(tǒng)回收時,app的進程被殺死了,Activity 也被回收了,而app的task和activity棧以及相應的intent和數(shù)據(jù)會被系統(tǒng)保存起來。當app被切回前臺時,系統(tǒng)會恢復task和activity棧以及相應的intent和數(shù)據(jù)。
2.不要在Application類和全局單例類中存放數(shù)據(jù),會導致app無法正確恢復狀態(tài)。運行時的臨時數(shù)據(jù)應存放在SharedPreference、臨時文件或數(shù)據(jù)庫中
3 Activity之間傳數(shù)據(jù)應該用系統(tǒng)提供的intent機制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容