美文网首页AndroidAndroid知识Android技术知识
Android开发进阶:Activity和进程的回收和状态恢复

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

作者: 码农向前冲 | 来源:发表于2017-06-02 16:56 被阅读128次

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/qbyufxtx.html