3.1.1 Activity 详解及其生命周期

本节例程下载地址:WillFlowLifeCycle

一、Activity详解

(1)Activity是什么?

其实在上一章中,我们已经和Activity打过交道了,并且对Activity也有了初步的认识。不过上一章我们的重点是一些常用的UI控件的使用,对Activity的介绍并不多,在本章中我将对Activity进行详细的介绍。

Activity是最容易吸引到用户的地方了,它是一个应用程序的组件,他在屏幕上提供了一个区域,允许用户在上面做一些交互性的操作, 比如打电话,照相,发送邮件,或者显示一个地图。

就拿发送邮件来说吧,电子邮件应用可能具有一个显示新电子邮件列表的 Activity、一个用于撰写电子邮件的 Activity 以及一个用于阅读电子邮件的 Activity。 尽管这些 Activity 通过协作在电子邮件应用中形成了一种紧密结合的用户体验,但每一个 Activity 都独立于其他 Activity 而存在。 因此,其他应用可以启动其中任何一个 Activity(如果电子邮件应用允许的话)。 这就是为什么我们可以在相机应用中启动电子邮件应用内用于撰写新电子邮件的 Activity,以此来实现共享图片的功能。

当然了,我们的应用程序中可以包含零个或多个活动,但包含零个活动的应用程序很少见,谁也不想让自己的应用永远无法被用户看到对吧?

(2)Activity、Window 与 View 的关系

View、Window以及Activity主要是用于显示并与用户交互的,这让我们在初学的时候很容易弄混,而且无法理解他们区别以及联系,下面让我们梳理一下。

View表示屏幕上的一个矩形区域,主要是用于绘制我们想要的结果,是一个最基本的UI组件。它(包括ViewGroup)使用的是组合模式,即:将View组成成树形结构,以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

Window表示一个窗口,它的大小一般来说取值为屏幕大小,但是这不是绝对的,比如对话框、Toast等就不是整个屏幕大小,我们甚至可以自定义指定Window的大小。Window包含一个 View tree 和窗口的 layout 参数,包含一个View tree 和窗口的 layout 参数。View tree的 root View 可以通过 getDecorView 得到,还可以设置Window的Content View。

我们知道,Window 是系统管理的窗口界面,那么为什么还需要Activity呢?我们把Activity所做的事情,全部封装到Window不就可以了吗?本质上讲,我们要显示一个窗口出来的确可以不需要Activity,但是让用户自己直接操作Window来开发自己的应用的话,先不说工作量,光让用户自己去实现任务栈这点,有多少人能写的出来呢?为了让大家能简单、快速的开发应用,Android通过定义Activity,让Activity帮我们管理好,我们只需简单的去重写几个回调函数(即后续的Activity生命周期里面讲到的那些回调方法),而无需直接与Window对象接触。这就使得对于各种事件的处理只需重写Activity里面的回调即可,而无需关注其他细节,因为Activity默认都帮我们写好了,我们只需要针对定制的部分重写我们的业务逻辑就可以了。

(3)Activity、Task 和 Back Stack

我们的APP一般都是由多个Activity构成的,而众多的Activity是可以层叠的,我们每启动一个新的Activity,就会覆盖在原Activity之上,然后点击 Back 键会销毁最上面的Activity,下面的一个活动就会重新显示出来,那么类似这样的活动创建和销毁是如何管理的呢?其实 Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack),那么什么是Task呢?

这个Task只是一个frameworker层的概念,栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈, 这时前一个入栈的活动就会重新处于栈顶的位置。也就是说Task会将多个相关的Activity收集起来,然后进行Activity的跳转与返回,并且系统总是会显示处于栈顶的活动给用户。

Task具有如下特点:

后进先出(LIFO),常用操作入栈(push),出栈(pop),处于最顶部的叫栈顶,最底部叫栈底。

相信大家对于栈这种数据结构并不陌生,而Android中的Stack Stack也具有上述特点,它也正是这样来管理Activity的。

下面是官方文档给出的流程图:
下面的图展示了返回栈是如何管理活动入栈出栈操作的:

二、Activity的生命周期

从上文我们知道在应用中Activity是与用户交互的接口,它提供了一个用户完成相关操作的窗口。当我们在开发中创建Activity后,通过调用setContentView(View)方法来给该Activity指定一个布局界面,而这个界面就是提供给用户交互的接口。Android系统中是通过Activity栈的方式来管理Activity的,而Activity自身则是通过生命周期的方法来管理自己的创建与销毁的,既然如此,现在我们就来看看Activity生命周期是如何运作的。

(1)Activity的生命状态

首先我们来了解下Activity的生命状态,Activity会在以下几种生命状态中相互切换,至于如何切换,这因用户的操作不同而异。

  • 运行状态(Active/Running)
    当一个Activity位于返回栈的栈顶时,这时Activity就处于运行状态。 系统最不愿意回收的就是处于运行状态的Activity,因为这会带来非常差的用户体验。
  • 暂停状态(Paused)
    当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,也就是说此时的Activity仍然是完全存活着的,系统也不愿意去回收这种活动,只有在系统内存紧张的情况下,Activity才有可能被系统回收掉。
  • 停止状态(Stopped)
    当一个Activity不再处于栈顶位置,并且完全不可见的时候,比如当一个Activity被另一个Activity完全覆盖时,就进入了停止状态。 系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的Activity有可能会被系统回收。
  • 销毁状态(Killed)
    当一个Activity从返回栈中移除后就变成了销毁状态,系统会最倾向于回收处于这种状
    态的Activity,从而保证手机的内存充足。
了解了Activity的4种生命状态后,我们就来聊聊Activity的生命周期,下图是生命周期流程图:

(2)Activity的生命周期回调方法

Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一介绍下这七个方法。

1. onCreate()

该方法是在Activity被创建时回调,它是生命周期第一个调用的方法,我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。

2. onStart()

这个方法在活动由不可见变为可见的时候调用,也就是说此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互,可以简单理解为Activity已显示而我们无法看见罢了。

3. onResume()

当此方法回调时说明Activity已在前台可见,此时的Activity一定是位于返回栈的栈顶的,那么这个Activity就可与用户交互了(处于前面所说的Active/Running生命状态)。onResume方法与onStart的相同点是两者都表示Activity可见,只不过onStart回调时Activity还是后台无法与用户交互,而onResume则已显示在前台,可与用户交互。当然从流程图,我们也可以看出当Activity停止后(onPause方法和onStop方法被调用),重新回到前台时也会调用onResume方法,因此我们也可以在onResume方法中初始化一些资源,比如重新初始化在onPause或者onStop方法中释放的资源。

4. onPause()

此方法被回调时则表示Activity正在停止(Paused生命状态),一般情况下onStop方法会紧接着被回调。但通过流程图我们还可以看到一种情况是onPause方法执行后直接执行了onResume方法,这属于比较极端的现象了,这可能是用户操作使当前Activity退居后台后又迅速地再回到到当前的Activity,此时onResume方法就会被回调。当然,在onPause方法中我们可以将一些消耗 CPU 的资源释放掉以及保存一些关键数据,比如做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause方法执行完成后,新Activity的onResume方法才会被执行。

5. onStop()

这个方法在Activity完全不可见的时候调用,此时Activity不可见,仅在后台运行,表示Activity即将停止或者完全被覆盖(Stopped生命状态)。它和 onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop()方法并不会执行。同样地,可以在onStop方法可以做一些资源释放的操作,但是不能太耗时。

6. onDestroy()

这个方法在Activity被销毁之前调用,此时Activity正在被销毁,之后Activity的状态将变为销毁状态。这也是生命周期最后一个执行的方法,一般我们可以在此方法中做一些回收工作和最终的资源释放。

7. onRestart()

这个方法在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。这种情况一般是用户打开了一个新的Activity时,当前的Activity就会被暂停(onPause和onStop被执行了),当接着又回到当前Activity页面时,onRestart方法就会被回调。

以上七个方法中除了 onRestart()方法, 其他都是两两相对的,从而又可以将活动分为如下的三种生存期:
1. 完整生存期

Activity在 onCreate()方法和 onDestroy()方法之间所经历的,就是完整生存期。一般情况下,一个Activity会在 onCreate()方法中完成各种初始化操作,而在 onDestroy()方法中完成释放内存的操作。

2. 可见生存期

Activity在 onStart()方法和 onStop()方法之间所经历的,就是可见生存期。在可见生存期内,Activity对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在 onStart()方法中对资源进行加载,而在 onStop()方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多内存。

3. 前台生存期

Activity在 onResume()方法和 onPause()方法之间所经历的,就是前台生存期。在前台生存期内,Activity总是处于运行状态的,此时的Activity是可以和用户进行相互的,我们平时看到和接触最多的也这个状态下的Activity。

为了帮助你能够更好的理解,我们这里再贴一张Android 官方提供的一张活动生命周期的示意图:

三、Activity中的其它问题

(1)如何应对Activity被回收?

正如前面所说,当一个Activity进入到了停止状态,是有可能被系统回收的。那么设想一下这样的场景:应用中有一个Activity A,用户在Activity A 的基础上启动了Activity B,Activity A 就进入了停止状态,这个时候由于系统内存不足,将Activity A 回收掉了,然后用户按下 Back 键返回Activity A,会出现什么情况呢?其实还是会正常显示Activity A的,只不过这时并不会执行 onRestart()方法,而是会执行Activity A 的 onCreate()方法,因为Activity A 在这种情况下会被重新创建一次。

这样看上去好像一切正常,可是千万不要忽略一个重要问题,那就是Activity A 中是可能存在临时数据和状态的。比如 MainActivity 中有一个文本输入框,现在我们输入了一段文字,然后启动 NormalActivity,这时 MainActivity 由于系统内存不足被回收掉,过了一会你又点击了Back 键回到 MainActivity,你会发现刚刚输入的文字全部都没了,因为 MainActivity 被重新创建了。

如果我们的APP出现了这种情况,是会严重影响用户体验的,所以必须要解决这个问题。查阅文档可以看出,Activity 中还提供了一个 onSaveInstanceState() 回调方法,这个方法会保证一定在 Activity 被回收之前调用,因此我们可以通过这个方法来解决 Activity 被回收时临时数据得不到保存的问题。

onSaveInstanceState() 方法会携带一个 Bundle 类型的参数, Bundle 提供了一系列的方法用于保存数据,比如可以使用 putString()方法保存字符串,使用 putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle 中取值,第二个参数是真正要保存的内容。

在 MainActivity 中添加如下代码就可以将临时数据进行保存:
@Override
protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      String tempData = "Something you just typed";
      outState.putString("data_key", tempData);
}

数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我们一直使用的 onCreate() 方法其实也有一个 Bundle 类型的参数。这个参数在一般情况下都是 null,但是当活动被系统回收之前有通过 onSaveInstanceState() 方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。

修改 MainActivity 的 onCreate()方法,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      Log.d(TAG, "onCreate");
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      setContentView(R.layout.activity_main);
      if (savedInstanceState != null) {
            String tempData = savedInstanceState.getString("data_key");
            Log.d(TAG, tempData);
      }
      ……
}

取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框
上,这里我们只是简单地打印一下。

你可能会问 Intent 和 Bundle 是什么鬼?这等我们讲到Intent的时候你就知道了.

(2)onCreate()一个参数和两个参数有什么区别?

我们都知道正常情况下onCreate()只有一个参数:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
}

但是当我们在配置文件中为我们的Activity设置了这样一个属性之后:

android:persistableMode="persistAcrossReboots"

我们在重写Activity的onCreate()方法时就会发现它出现了两个参数:

protected void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
}

那么这个时候我们的Activity就拥有了更强的数据保存的能力,通常我们会配合以下的方法来使用:

public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState)
我们首先说一下前一个方法的调用情形:
  • 点击home键回到主页或长按后选择运行其他程序
  • 按下电源键关闭屏幕
  • 启动新的Activity
  • 横竖屏切换时肯定会执行,因为横竖屏切换的时候会先销毁Activity,然后再重新创建。
    这很好理解,因为系统"未经我们的许可"销毁了我们的Activity,那么onSaveInstanceState就会被系统调用,这是系统的责任,因为它必须要提供一个机会让我们保存数据,当然我们可以保存也可以不保存。
然后我们说一下第二个方法:

和onCreate一样,同样可以取出前者保存的数据,它一般是在onStart()和onResume()之间执行,之所以有两个可以获取到保存数据的方法,是为了避免Activity跳转而没有关闭时不走onCreate()方法,而我们又想取出保存的数据这种情况。也就是说我们这个Activity拥有了更强的持久化的能力,增加的这个PersistableBundle参数令这些方法拥有了系统关机后重启的数据恢复能力,而且不影响我们其他的序列化操作。

四、生命周期演示

先给出如下的案例代码:
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    /**
     * Activity创建时被调用
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate(1)被调用");

        initView();
    }

    /**
     * Activity从后台重新回到前台时被调用
     */
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.i(TAG, "onRestart(1)被调用");
    }

    /**
     * Activity创建或者从后台重新回到前台时被调用
     */
    @Override
    protected void onStart() {
        super.onStart();
        Log.w(TAG, "onStart(1)被调用");
    }

    /**
     * Activity创建或者从被覆盖、后台重新回到前台时被调用
     */
    @Override
    protected void onResume() {
        super.onResume();
        Log.i(TAG, "onResume(1)被调用");
    }

    /**
     *  Activity被覆盖到下面或者锁屏时被调用
     */
    @Override
    protected void onPause() {
        super.onPause();
        Log.i(TAG, "onPause(1)被调用");
    }

    /**
     * 退出当前Activity或者跳转到新Activity时被调用
     */
    @Override
    protected void onStop() {
        super.onStop();
        Log.w(TAG, "onStop(1)被调用");
    }

    /**
     * 退出当前Activity时被调用,调用之后Activity就结束了
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy(1)被调用");
    }

    private void initView() {
        Button button = (Button) findViewById(R.id.buttonPanel);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
                Log.d(TAG, "开启:SecondActivity!");
            }
        });
    }
}

下面我们俩综合分析几种生命周期方法的调用情况:

  • 我们先来分析Activity启动过程中所调用的生命周期方法,运行程序截图如下:


    从Log中我们可以看出Activity启动后,先调用了onCreate方法,然后是onStart方法,最后是onResume方法,进入运行状态,此时Activity已在前台显示。即 Activity启动–>onCreate()–>onStart()–>onResume() 依次被调用。

  • 当前Activity创建完成后,按Home键回到主屏。按如上操作运行截图:


    我们在Activity创建完成后,点击Home回调主界面时,可以发现此时onPause方法和onStop方法被执行,也就是点击Home键回到主界面 (Activity不可见)–>onPause()–>onStop() 依次被调用。

  • 当我们点击Home键回到主界面后,再次点击App回到Activity时,调用结果如下:


    我们可以发现重新回到Activity时,调用了onRestart方法,onStart方法,onResume方法。即 Activity时–>onRestart()–>onStart()–>onResume() 依次被调用。

  • 当我们在原有的Activity的基础上打新的Activity时,调用结果如下:


    我们可看到原来的Activity调用的了onPause方法和onStop方法,也就是说在原Activity的基础上开启新的Activity,原Activity生命周期执行方法顺序为:onPause()–>onStop()。事实上跟点击home键是一样的,但是这里有点要注意的是如果新的Activity使用了透明主题,那么当前Activity不会回调onStop方法,同时我们发现新Activity(SecondActivity)生命周期方法是在原Activity的onPause方法执行完成后才可以被回调,这也就是前面我们为什么说在onPause方法不能操作耗时任务的原因了。

  • 当我们点击Back键回退时,回调结果如下:


    从Log我们可以看出,当点击Back键回退时,相当于退出了当前Activity,Activity将被销毁,因此退出当前Activity时:onPause()–>onStop()–>onDestroy() 依次被调用。

  • 最后,当我们再次点击返回键回到桌面的时候,回调结果如下:


    可以看出,这就是一个Activity正常的退出顺序。

结语:

本篇的理论知识就介绍到这里,下一篇会介绍Activity的各种使用方法,如果你觉得本篇的知识比较难懂,那么相信你经过之后的代码实践之后回来再看的话会有新的认识并更加印象深刻。

点此进入:GitHub开源项目“爱阅”

感谢优秀的你跋山涉水看到了这里,欢迎关注下让我们永远在一起!

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

推荐阅读更多精彩内容