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

转自:https://www.jianshu.com/p/72ccb08e7f34

不管是安卓的官方文档还是源码注释,处处可见“从 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机制。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值