1. 程式人生 > >Android開發進階:Activity和程序的回收和狀態恢復

Android開發進階:Activity和程序的回收和狀態恢復

不管是安卓的官方文件還是原始碼註釋,處處可見“從 Activity A 跳到 Activity B,當系統記憶體不足時 A 可能會被回收……”,而且沒有明確說明 A 和 B 是否屬於同一個 app 或程序。

但是,在官方給的 Activity 生命週期圖中,卻說記憶體不足時低優先順序的程序將被殺死。

這裡寫圖片描述
那麼,記憶體不足時,到底是 Activity 被回收了呢,還是程序被殺死了呢,還是二者都出現了呢?

答案是,Activity 被回收了,而且程序被殺死了,而且一般情況下該程序是後臺程序。當記憶體不足時,系統會殺死優先順序低的後臺程序,程序內的 Activity 肯定也就被回收了。

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

我們知道,在安卓開發中,當一個activity要啟動另一個activity時要傳遞資料的話,普遍的做法是將資料放在intent中:

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

在SecondActivity中可以通過intent.getStringExtra(“data”)獲得資料。如果這個資料要往更深層的activity傳遞的話,就要繼續將其放入啟動後續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);

很多安卓開發的新手都會問,如果這個資料到在多個activity之間傳遞,為什麼我們不把他作為成員變數放在一個全域性的類(比如現成的Application類,或者一個全域性可獲取的單例類)中,這樣這個資料就不用每次放在intent中傳來傳去了,豈不是方便很多?

答案很簡單:不能這樣做。如前面所述,當app處於後臺時被回收時,所有的全域性單例類(包括Application例項)都會被銷燬,存在裡面的資料也就跟著丟失了。當app被切回前臺時,依賴於這些資料的activity就會取不到資料。

為什麼用Intent傳資料沒有問題呢?因為系統維護的task和activity棧幫我們處理了Intent(以及其中的資料)的儲存和恢復。簡單來說,所有“曾用於啟動activity的intent”和“還沒有被銷燬的activity”都會被系統維護在task棧中,並且當程序被回收時,安卓系統會自動幫我們把這些資訊儲存起來。

這裡寫圖片描述
當用戶嘗試把一個被回收的app切回前臺時,系統會用之前儲存的task棧資訊來嘗試把app恢復到被回收之前的狀態,而通過intent傳遞的資料也在這個過程中得到了還原。下面用一個簡單的例子來說明。

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傳遞了一個字串資料。我們在SecondActivity介面把app切回後臺,然後通過啟動其他app佔用記憶體來促使系統回收lifecycletest app:

這裡寫圖片描述
可以看到lifecycletest的程序已經被回收了。當切回app時:

這裡寫圖片描述
可以看到系統建立了一個新的程序,SecondActivity的生命週期函式被依次執行,而onCreate函式中通過getIntent仍然能夠取到之前從MainActivity傳過來的資料,使用者也正確地回到了SecondActivity介面,看到了正確的資料。
細心的讀者可以看到這時MainActivity的生命週期函式沒有被執行。這是因為系統恢復程序被回收的app時,只會執行task棧頂的activity的生命週期函式。如果使用者點返回的話,會執行MainActivity的生命週期函式,正確的退回到MainActivity。

這裡寫圖片描述

需要注意的是,一個intent裡面能放的資料大小是有限制的,最好是不超過500kb(不同的系統版本限制不一,可參考相關資料)。比如在intent裡面放一個byte[ ]陣列的話,如果陣列大小太大,就會導致執行時拋異常。所以如果要在activity之間傳大量資料的話,最好把資料存為臨時檔案,然後在intent中傳檔案的路徑。
總結:
1.當app處於後臺被系統回收時,app的程序被殺死了,Activity 也被回收了,而app的task和activity棧以及相應的intent和資料會被系統儲存起來。當app被切回前臺時,系統會恢復task和activity棧以及相應的intent和資料。
2.不要在Application類和全域性單例類中存放資料,會導致app無法正確恢復狀態。執行時的臨時資料應存放在SharedPreference、臨時檔案或資料庫中
3 Activity之間傳資料應該用系統提供的intent機制。

相關推薦

Android開發:Activity程序回收狀態恢復

不管是安卓的官方文件還是原始碼註釋,處處可見“從 Activity A 跳到 Activity B,當系統記憶體不足時 A 可能會被回收……”,而且沒有明確說明 A 和 B 是否屬於同一個 app 或程序。 但是,在官方給的 Activity 生命週期圖中

Android開發——使用Dagger2

前言 關於Dagger2的學習,首先看的官方文件,確實看不懂。然後搜尋網路上的介紹博文,不乏一些講得比較好的,如這個。但終究不夠透徹,還是得回頭研究官方文件。本文不僅僅是翻譯,而是記錄了自己對官方文件的理解。 提供依賴的兩種方式 使用@Inject註解構造器 class

Android開發——測試

導言 每次使用Android Studio建立一個新的工程,都會看到類似如下的目錄結構: 我們編寫的Java程式碼全部放在最上面的包中,下面兩個使用紅線圈中的包總是沒有用過,僅僅知道他們是用於放置測試程式碼的。標註為androidTest的包放置UI相關的測試,標註為test

mysql 開發篇系列 43 邏輯備份與恢復(基於時間位置的不完全恢復)

一. 概述          在上篇講到了邏輯備份,使用mysqldump工具來備份一個庫,並使用完全恢復還原了資料庫。在結尾也講到了誤操作是不能用完全恢復的。解決辦法是:我們需要恢復到誤操作之前的狀態,然後跳過誤操作語句。再恢復後面執行的語句,完成我們的恢復,這種恢復叫“不完全恢復”。在mysql 中,不完

HenCoder Android 開發: 自定義 View 1-1 繪製基礎

自定義繪製概述 二話不說,我反手就是一個視訊:(視訊掛了,先直接點到優酷去看吧:優酷連結) 首先總結一下視訊中的關鍵點: 自定義繪製的方式是重寫繪製方法,其中最常用的是 onDraw() 繪製的關鍵是 Canvas 的使用 Canvas 的繪製類方法: drawXX

Android開發之NIO非阻塞包(二)

有關Android NIO我們主要分為三大類,ByteBuffer、FileChannel和SocketChannel。由於篇幅原因今天Android123只對前兩個做說明。NIO和傳統的I/O比較大的區別在於傳輸方式非阻塞,一種基於事件驅動的模式,將會使方法執行完後立即返回

Android開發之NIO非阻塞包(三)

有關Android NIO的精髓主要用於高負載的Socket網路傳輸,相對於傳統I/O模型的Socket傳輸方式的優勢,我們已經在 Android開發進階之NIO非阻塞包(一) 中講到了,這裡不再贅述,一起來看看Android NIO有關Socket操作提供的類吧: 一、S

iOS開發-:被誤解的MVC被神化的MVVM(作者:唐巧)

作者 唐巧 釋出於 2015年11月1日 | 被誤解的 MVC MVC 的歷史 MVC,全稱是 Model View Controller,是模型 (model)-檢視 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟

Android開發-:Json字串轉換為java物件的各種實現方法[json_lib框架、Gson、org.json]

JSON (JavaScript Object Notation) 是一種輕量級的資料交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition

Android開發-:實現微信自動搶紅包的程式

簡單實現了微信自動搶紅包的服務,原理就是根據關鍵字找到相應的View, 然後自動點選。主要是用到AccessibilityService這個輔助服務,基本可以滿足自動搶紅包的功能,但是有些邏輯需要優化,比如,拆完一個紅包後,必須手動點選返回鍵,才能進行下一次自動搶紅包。

Android開發——自定義View的使用及其原理探索

  在Android開發中,系統提供給我們的UI控制元件是有限的,當我們需要使用一些特殊的控制元件的時候,只靠系統提供的控制元件,可能無法達到我們想要的效果,這時,就需要我們自定義一些控制元件,來完成我們想要的效果了。下面,我就來講講自定義控制元件的那些事。   首先,我來講講Android的控制元件架構。

Android開發 -- 通用介面卡 CommonAdapter

      在Android開發中,我們經常會用到ListView 這個元件,為了將ListView 的內容展示出來,我們會去實現一個Adapter來適配,將Layout中的佈局以列表的形式展現到元件中。     比如,像 GGTalk 安卓版的查

mysql 開發篇系列 42 邏輯備份與恢復

一.概述          在作何資料庫裡,備份與恢復都是非常重要的。好的備份方法和備份策略將會使得資料庫中的資料更加高效和安全。對於DBA來說,進行備份或恢復操作時要考慮的因素大概有如下: (1) 確定要備份的表的儲存引擎是事務型(innodb)還是非事務型。兩種不同的儲存引擎備份方式在處理資料一致性方面

mysql 開發篇系列 44 物理備份與恢復( 熱備份xtrabackup 工具介紹)

一.概述   物理備份和恢復又分為冷備份和熱備份。與邏輯備份相比,它最大優點是備份和恢復的速度更快。因為物理備份的原理都是基於檔案的cp。   1.1 冷備份    冷備份就是停掉資料庫服務。這種物理備份一般很少使用,因為很多應用是不允許長時間停機的。恢復操作大概是:首先停掉mysql服務, 在作業系統級別恢

python3開發-Django框架中的ORM的常用操作的補充(F查詢Q查詢,事務)

這樣的 env atomic 實例 In git 必須 TE setup 閱讀目錄 F查詢和Q查詢 事務 一、F查詢和Q查詢 1、F查詢 查詢前的準備 class Product(models.Model): name = mod

python3開發-Django框架的ORM常用字段參數

情況 friend 間隔 .class SM 讀取文件 int 如果 字符串類型 閱讀目錄 常用字段 字段合集 自定義字段 字段參數 關系參數 多對多的關聯關系的三種方式 一、常用字段 AutoField int自增列,必須填入參數 prima

python3開發-Django框架的中間件的五種用法邏輯過程

meta alc 多個 img eth ble 方法 get action 閱讀目錄 什麽是中間件 中間件的執行流程 中間件的邏輯過程 一、什麽是中間件? 官方的說法:中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、

mysql 開發篇系列 45 xtrabackup 安裝與使用者許可權說明(系統使用者mysql使用者)

一. 安裝說明   安裝XtraBackup 2.4 版本有三種方式:            (1) 儲存庫安裝Percona XtraBackup(推薦)            (2 )下載的rpm或apt包安裝Percona XtraBackup。            (3) 原始碼編譯和安裝。  Pe

Android 9:程序通訊之 AIDL 解析

讀完本文你將瞭解: 在 Android 進階7:程序通訊之 AIDL 的使用 中我們使用 AIDL 實現了跨程序的通訊,但是不清楚 AIDL 幫我們做了什麼。 AIDL 的本質是簡化我們 IPC 開發,它使用的是 Binder 機制,於是在上篇文章

Android 7:程序通訊之 AIDL 的使用

讀完本文你將瞭解: 記得 2015 年實習面試,筆試題裡就有這道題:請介紹下 AIDL。 當時的我是懵逼的,只好老老實實空著。沒想到後來面試時面試官大哥嘿嘿一笑說他也沒用過這玩意,真是夠實誠的。 筆試完查了這個知識點,似懂非懂也沒深究。去年看《安卓開