Activity的创建与销毁、数据的保存和启动模式分析

本文详细介绍了Android中Activity的创建与销毁过程,数据的保存与恢复方法,以及四大启动模式(standard、singleTop、singleTask、singleInstance)的运用。在Activity销毁时,可以通过onSaveInstanceState()保存数据,onRestoreInstanceState()恢复。启动模式中,standard模式每次启动都会创建新实例,singleTop模式会检查实例是否在栈顶,singleTask和singleInstance模式则涉及任务栈管理和单个实例控制。文章还讨论了如何通过配置项控制屏幕旋转时的生命周期以及Activity的Flags用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一直对Activity的各种生命周期方法、创建和销毁时机以及Activity的4种启动模式没有彻底的了解清楚,详细整理记录一下这些知识,以后哪再不清楚时不会再到处查阅资料了。

首先,从网上拷过来一张图片,对这张图片,相信很多人都非常熟悉,它包括了Activity从创建到销毁的全部过程,如下图所示:

这里写图片描述

1、Activity的创建与销毁

  • 从Activity的创建开始说起,当启动一个Activity时,生命周期方法调用:

onCreate()→onStart()→onResume()

此时,Activity显示在前台界面和用户交互,开始运行

从Activity点击Back键回到桌面,生命周期方法调用:

onPause()→onStop()→onDestroy()

此时,Activity被销毁,这是最常见的创建与销毁流程

在当前Activity点击Home键回到桌面,Activity进入后台运行,再次启动该Activity,生命周期方法调用:

onPause()→onStop()→onRestart()→onStart()→onResume()

如果在Activity点击Back键退出Activity,并且在当前Activity的onStop()方法还没有被调用之前,再次启动Acitivity,生命周期方法调用:

onPause()→onResume()

这种情况属于很极端的情况,在正常的用户操作下很难重现这一场景。

  • 从当前Activity启动一个新的Activity:

当前显示的Activity为A,要启动的Activity为B,生命周期方法调用:

Activity A的onPause()→Activity B的onCreate()→Activity B的onStart()→Activity B的onResume()→Activity A的onStop()→Activity A的onDestroy()

此时,Activity A被销毁,Activity B显示到前台。如果Activity B的主题是透明主题,则Activity A不会被销毁,即Activity A的onStop()和onDestroy()不会被执行

可以看到,Activity A的onPause()方法执行完毕后,Activity B才会被创建,所以在onPause()方法中不可以有特别耗时的操作,否则将会影响新Activity的显示

2、Activity重新创建时数据的保存与恢复

由于资源内存不足或者系统配置发生变化导致Activity被销毁时,需要保存Activity中的数据,例如在Activity界面启动新的Activity、在Activity界面接到来电,在回到前一个Activity时,希望可以恢复原来的效果,这时就需要在Activity销毁时保存必要的数据,以便再次启动时恢复。

  • 通过Activity的onSaveInstanceState()和onRestoreInstanceState()来保存和恢复数据

Activity被异常终止时,系统会调用onSaveInstanceState()方法来保存当前Activity的状态,调用时机在onStop()方法之前,和onPause()方法没有既定的时序关系,尽管大多数情况下都是在onPause()方法之后调用。onSaveInstanceState()方法接受一个Bundle参数,用于保存数据。

在这里,就用手机切换横竖屏时的场景进行测试,手机在由横屏切换到竖屏时,Activity会进行销毁并重新创建,如果不进行处理,界面上的数据就不会保存。

重写Activity的生命周期方法和onSaveInstanceState()、onRestoreInstanceState()方法并打印log。

public class MainActivity extends ListActivity {

    public static final String TAG = "MainActivity";

    private List<String> datas = new ArrayList<String>();
    private ArrayAdapter<String> adapter;

    Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            initAdapter();
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate");
        new Thread() {
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                for(int i = 0; i < 10; i ++) {
                    datas.add("This is test item " + i);
                }
                Log.i(TAG, "loadData");
                handler.sendEmptyMessage(0x123);
            };
        }.start();
    }

    public void initAdapter() {
        adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, datas);
        setListAdapter(adapter);
    }

    @Override
    protected void onStart() {
        // TODO Auto-generated method stub
        super.onStart();
        Log.i(TAG, "onStart");
    }

    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        Log.i(TAG, "onResume");
    }

    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        Log.i(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        // TODO Auto-generated method stub
        super.onStop();
        Log.i(TAG, "onStop");
    }

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.i(TAG, "onDestroy");
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // TODO Auto-generated method stub
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onRestoreInstanceState(savedInstanceState);
        Log.i(TAG, "onRestoreInstanceState");
    }

}

在Activity中启动了一个子线程来加载10条数据,运行程序

这里写图片描述

2秒后数据加载完显示,此时将屏幕切换为横屏

这里写图片描述

可以看到,切换屏幕方向后,Activity被重新创建了,由于没有在onSaveInstanceState()方法中保存数据,所以在最后又加载了一遍

现在修改onSaveInstanceState方法,把数据保存到Bundle中

@Override
    protected void onSaveInstanceState(Bundle outState) {
        // TODO Auto-generated method stub
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState");
        outState.putSerializable("data", datas);
    }

在Activity再次创建的时候判断是否有数据传入,如果有的话不需要再次加载

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate");
        if(savedInstanceState != null) {
            datas = savedInstanceState.getStringArrayList("data");
        }
        if(datas.size() == 0) {
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    for(int i = 0; i < 10; i ++) {
                        datas.add("This is test item " + i);
                    }
                    Log.i(TAG, "loadData");
                    handler.sendEmptyMessage(0x123);
                };
            }.start();
        } else {
            initAdapter();
        }
    }

再次运行程序

这里写图片描述

第一次启动Activity,执行数据加载,切换横屏

这里写图片描述

可以看到,由于在onSaveInstanceState方法中保存了data,Activity被重新创建后没有打印loadData的log,证明没有重新加载

本程序是在onCreate()中获取保存的数据,onCreate()和onRestoreInstanceState()中都有一个Bundle参数,都可以用来恢复,区别在于如果调用了onRestoreInstanceState()方法,Bundle中一定是有数据的,而onCreate()中却不一定,因为如果是第一次启动Activity,Bundle中是没有数据的,所以在onCreate()中恢复的话需要判断Bundle是否为空

3、通过设置配置项控制屏幕切换时的生命周期

当屏幕发生旋转时,可以通过在AndroidManifest.xml中配置Activity的android:configChanges来控制Activity是否销毁并重新创建

重写Activity的生命周期方法和onConfigurationChanged()方法

  • 不设置android:configChanges项

    启动Activity,从竖屏切换到横屏

这里写图片描述

从横屏再切换为竖屏

这里写图片描述

从log中看到,切换横竖屏时都会销毁并重新创建Activity,各调用一次Activity的生命周期

  • 设置android:configChanges=”orientation|screenSize”,启动Activity,从横屏切换到竖屏

这里写图片描述

从竖屏切换回横屏

这里写图片描述

配置android:configChanges=”orientation|screenSize”后,屏幕在旋转时不会重新创建Activity,只会调用Activity的onConfigurationChanges()方法,所以可以通过选项来控制屏幕旋转时的生命周期

4、Activity的4种启动模式

  • 任务栈

要彻底弄清楚Activity的启动模式,首先要了解Task栈的概念,Android采用Task栈来管理多个Activity,Activity的任务栈是一种“先进先出”的栈结构,先启动的Activity放在任务栈底,后启动的放在任务栈顶

启动一个应用时,Android系统会为之创建一个Task栈,默认为应用的包名,可以通过指定android:taskAffinity来指定任务栈的名字

  • standard模式

标准模式,Activity启动的默认模式。每一次启动一个Activity都会重新创建一个实例,不管这个实例是否已经存在。在这种模式下,被启动的Activity会运行在启动它的Activity所在的栈中。

创建工程,新建两个Activity:MainActivity和SecondActivity

MainActivity.java

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button firstButton = (Button)findViewById(R.id.first);
        firstButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

SecondActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button secondButton = (Button)findViewById(R.id.second);
        secondButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent intent = new Intent(SecondActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

运行程序,启动MainActivity,使用adb shell dumpsys activity命令查看应用中的任务栈情况

这里写图片描述

MainActivity在TaskRecord{410aee30 #3 A com.example.testlaunchmode U 0}这个任务栈中

点击MainActivity中的Button启动SecondActivity,查看任务栈

这里写图片描述

SecondActivity被加入MainActivity所在的栈中,位于栈顶。点击SecondActivity中的Button再次启动SecondActivity,查看任务栈

这里写图片描述

在TaskRecord{410aee30 #3 A com.example.testlaunchmode U 0}中再次加入了一个SecondActivity实例,最先启动的MainActivity在栈底

在开发有时会遇到使用ApplicationContext启动standard模式的Activity会报错,这是因为非Activity类型的Context没有所谓的任务栈

  • singleTop模式

    顶单例模式,如果新Activity位于栈顶,不会重新创建实例,会回调Activity的onNewIntent()方法,通过android:launchMode=”singleTop”指定

设置上例中的SecondActivity的启动模式为singleTop,重写onNewIntent()方法

在MainActivity中启动SecondActivity,查看任务栈

这里写图片描述

在SecondActivity中再次启动SecondActivity

这里写图片描述

这里写图片描述

由于SecondActivity实例已经位于栈顶,再次启动该Activity不会创建新的实例,只会回调onNewIntent()方法。如果启动的Activity实例没有位于栈顶,会重新创建并加入栈

新建ThirdActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.third);
        Button thirdButton = (Button)findViewById(R.id.third);
        thirdButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent intent = new Intent(ThirdActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

在ThirdActivity中点击Button启动SecondActivity,修改SecondActivity,点击Button启动ThirdActivity,运行程序

这里写图片描述

当SecondActivity没有位于栈顶时,再次启动会重新创建实例

  • singleTask

Task内单例模式,只要Activity在一个栈中存在,多次启动Activity不会重新创建实例,会回调onNewIntent()方法

启动Activity时,系统首先会寻找是否存在此Activity所需要的任务栈,如果不存在,创建任务栈,并创建Activity实例放入栈中。若存在任务栈,查看该Activity在栈中是否存在,如果存在,把实例调到栈顶,回调onNewIntent()方法。如果不存在实例,创建实例并压入栈中

这种模式包含的情况较多,一个一个来分析

(1)被启动的Activity和启动它的Activity属于同一个栈

还使用上一个例子说明,使用MainActivity启动SecondActivity时,由于两个Activity属于同一应用,而且都没有指定android:taskAffinity属性,所以这两个Activity属于同一个栈中,所以会创建SecondActivity的实例加入到栈中

这里写图片描述

在SecondActivity中再次启动SecondActivity时,由于SecondActivity已经存在栈中,所以不会创建实例,会回调SecondActivity的onNewIntent()方法

如果在ThirdActivity中启动SecondActivity,这时栈中的情况

这里写图片描述

再次启动SecondActivity时,由于SecondActivity已经存在于栈中,但是没有位于栈顶,所以也不会创建实例,而是将SecondActivity调到栈顶,即让ThirdActivity出栈

这里写图片描述

同样会调用SecondActivity的onNewIntent()方法

(2)被启动的Activity和启动它的Activity不属于同一个栈,这种情况分为同一个应用中两个Activity的taskAffinity属性不用和两个不同应用中的两个Activity,不过性质是相同的

为SecondActivity指定android:taskAffinity属性,和当前包名不一样,这样,MainActivity和SecondActivity就不属于同一个任务栈,在MainActivity中启动SecondActivity

这里写图片描述

SecondActivity没有被加入到MainActivity所在的任务栈中,而是被加入了新创建的任务栈TaskRecord{410c0e48 #8 A com.example.testlaunchmode.secondtask U 0},在SecondActivity再次启动SecondActivity

这里写图片描述

没有再次创建SecondActivity的实例

在SecondActivity中启动ThirdActivity

这里写图片描述

在ThirdActivity中启动SecondActivity

这里写图片描述

没有再次创建SecondActivity的实例,而是将ThirdActivity出栈,并会回调SecondActivity的onNewIntent()方法

  • singleInstance模式:

全局单例模式,这种模式具有singleTask模式的所有特性,而且具有此模式的Activity只能单独的位于一个栈中

(1)如果要启动的Activity不存在实例,会新创建一个全新的栈,并创建Activity的实例加入到栈中,以后再启动此Activity都不会创建新的实例

修改SecondActivity的启动模式为singleInstance,在MainActivity中启动SecondActivity

这里写图片描述

不管MainActivity和SecondActivity所属的栈名是否相同,都会创建一个新的栈来放置singleInstance模式的Activity

(2)如果启动的Activity已经存在于某个栈中,会将此栈调入到前台显示

新建另一个工程,将SecondActivity放置到工程中,这样MainActivity和SecondActivity就不属于同一个应用,在MainActivity中启动SecondActivity,点击Home键将SecondActivity置于后台

这里写图片描述

此时MainActivity所在的任务栈位于前台,SecondActivity所在的任务栈位于后台,在MainActivity中再次启动SecondActivity

这里写图片描述

并没有再次创建SecondActivity的实例,而是将SecondActivity所属的任务栈调到了前台显示

5、Activity的Flags

可以在AndroidManifest.xml中为Activity设置启动模式,也可以在程序中通过指定Activity的Flags来设置

  • FLAG_ACTIVITY_NEW_TASK

为Activity指定“singleTask”启动模式

  • FLAG_ACTIVITY_SINGLE_TOP

为Activity指定“singleTop”启动模式

  • FLAG_ACTIVITY_CLEAR_TOP

具有此标志位的Activity启动时,位于它上面的Activity都要出栈,根据前面的例子可以得知singleTask模式具有此效果

  • FLAG_ACTIVITY_EXCLUED_FROM_RECENT

标记的Activity不会出现在历史列表中,当用户不希望通过历史列表回到Activity时使用这个标记,相当在指定android:excludeFromRecent=”true”

当然,Activity的Flags不仅限于以上几种,只是列出了比较常用的几种,还有其他的标志位,不在此一一总结了

以上就是所了解的关于Activity生命周期及数据保存,还有启动模式的知识内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值