Android开发进阶:Activity和进程的回收和状态恢复

不管是安卓的官方文档还是源码注释,处处可见“从 Activity A 跳到 Activity B,当系统内存不足时 A 可能会被回收……”,而且没有明确说明 A 和 B 是否属于同一个 app 或进程。

但是,在官方给的 Activity 生命周期图中,却说内存不足时低优先级的进程将被杀死。


image.png

那么,内存不足时,到底是 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栈中,并且当进程被回收时,安卓系统会自动帮我们把这些信息保存起来。

image.png

当用户尝试把一个被回收的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:


image.png

可以看到lifecycletest的进程已经被回收了。当切回app时:


image.png

可以看到系统建立了一个新的进程,SecondActivity的生命周期函数被依次执行,而onCreate函数中通过getIntent仍然能够取到之前从MainActivity传过来的数据,用户也正确地回到了SecondActivity界面,看到了正确的数据。
细心的读者可以看到这时MainActivity的生命周期函数没有被执行。这是因为系统恢复进程被回收的app时,只会执行task栈顶的activity的生命周期函数。如果用户点返回的话,会执行MainActivity的生命周期函数,正确的退回到MainActivity。
image.png

需要注意的是,一个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机制。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容