《Android开发艺术探索》读书笔记-Android的生命周期和启动模式

Activity的生命周期全面分析

Activity的生命周期可分为两部分,一种是在正常的执行过程中的生命周期,另一种则是在执行过程中发生异常情况的生命周期,这两种情况在实际开发中都有着的极为重要的作用,因此弄清楚这里面的关系就显得十分必要。

典型情况下的生命周期

  • 由于正常情况下的生命周期已经是一个老生常谈的问题,这里也就不再多费口舌,给出一张Google官方的流程图便能看出其调用顺序。

《Android开发艺术探索》读书笔记-Android的生命周期和启动模式_第1张图片

  • 额外补充的说明:

    • 针对一个特定的Activity,第一次启动,回调如下:onCreate -> onStart -> onResume
    • 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause -> onStop特殊情况:新的Activity采用了透明主题,那么当前Activity不会调用onStop
    • 当用户再次回到原Activity时,回调如下:onRestart -> onStart -> onResume
    • 当用户按back键回退时,回调如下:onPause -> onStop -> onDestroy
    • 当Activity被系统回收后再次打开,此时生命周期任然是和第一次启动一样,不过其过程多了参数的保存和恢复,后面会集体讲到
    • 各个生命周期都存在着一一配对的关系,可以看成是对称的,正因为有这个特性,很多时候在对称的回调里做注册和反注册
  • onStart对应onStoponResume对应onPause,使用中,二者极其类似,其之所以这样存在,是应为出发的角度不同。前者从Activity是否可见的角度来回调,后者从Activity是否位于前台来回调。

  • 一个有趣的问题:当前 Activity 为 A,在此时打开 Activity B,那么 B 的onResume先执行还是 A 的onPause先执行?

    回答这个问题并不难,这里就是采用 阅读源码 + 实验验证 的方式,在源码中新的Activity启动之前,栈顶的Activity需要先onPause后,新的 Activity 才能启动。同时在 Activity 的生命周期的回调中输出日志,验证正确性。最后的结论就是 A 的 Activity 先onPause,然后 B 的 Activity 再启动。onPauseonStop中都不可执行耗时操作,尤其是在onPause

  • 另外,在Android的说明文档中还有着这样一张图,看图说话

《Android开发艺术探索》读书笔记-Android的生命周期和启动模式_第2张图片

异常情况下的生命周期

情况1:资源相关的系统配置发生改变导致 Activity 被杀死并重新创建
  • 一个简单的例子,在横竖屏切换的时候,在默认配置的情况下,Activity 就会销毁并重建,那么在这个过程中发生的事情自然就是数据的保存和恢复了。

  • 这里依旧是调用了之前图片中的onSaveInstanceState,用以保存数据,这个调用发生在onStop之前,但其顺序与onPause没有关系,可能在之前也可能在之后。而当 Activity 被重建之时,会调用onRestoreInstanceState来恢复数据,其调用时机在onStart之后,值得注意的是,这个状态值Bundle会同时传递给onCreateonRestoreInstanceState,也就是说在没有数据保存的时候,onCreate的形参Bundlenull,而当有数据保存的时候则不为null

  • 在系统调用onSaveInstanceStateonRestoreInstanceState的时候,会默认为我们保存当前 Activity 的视图结构以及恢复数据。这其中使用的是委托思想,一级一级委托上层保存元素,在一级一级通知下层恢复数据。这种思想在view绘制和事件分发中都有着应用。

情况2:资源内存不足导致低优先级的 Activity 被杀死
  • Activity有着优先级一说,优先级越高,越不容易被系统杀死。按照其高低可分为以下三种

    • 前台 Activity——正在和用户交互的 Activity,这个 Activity 几乎不可能被系统杀死,杀死的话会严重影响用户体验
    • 可见但非前台 Activity——比如 Activity 中弹出一个对话框,导致 Activity 可见但是位于后台无法直接和用户直接交互,这种 Activity 也不容易被杀死
    • 后台 Activity ——已经被暂停的 Activity,比如执行了 onStop,优先级最低,在内存不足的时候会优先回收此类 Activity
  • 另外说明一点,在没有四大组件执行的时候,进程就会很快被杀死。

禁止重建
  • 就针对以上 Activity 的重建来说,能否在某些内容发生改变时不重建 Activity 呢?答案是肯定的,这时候就需要在AndroidManifest.xml中配置android:configChanges了,其目的是告诉系统不要重建在指定的变化时不要重建 Activity,其配置值有很多,但常用的只有screenSize(屏幕尺寸信息发生改变,例如屏幕旋转),smallestScreenSize(物理屏幕尺寸发生改变,例如外接屏幕)和keyboardHidden(键盘的可访问性发生改变,例如调出虚拟键盘)

Activity的启动模式

启动模式的必要性:为了更好的对任务栈进行管理

Activity的LaunchMode

  • standard:标准模式,系统默认的启动模式,每次伴随 Activity 的启动而创建一个新的实例,不管这个实例是否已经存在。在这种启动模式下,注意两点,第一:每次 Activity 的启动都会调用完整的 Activity 生命周期流程。第二:谁启动了这个 Activity,那么这个 Activity 就会运行在启动它的那个 Activity 所在的栈中,用一个例子来说明,Activity A 启动了 Activity B(标准模式),那么 B 就会进入 A 所在的栈中。这里引申出一个问题:用 ApplicationContext 启动标准模式的 Activity 会报错,这是因为非 Activity 类型的 Context 是不存在任务栈一说的,所以就会报错,当添加了 FLAG_ACTIVITY_NEW_TASK 时,便不会有问题,这是因为当存在这个Flag的时候,启动时就会创建出一个新的任务栈,而这时候启动的 Activity 实际上是以 singleTask 模式启动的。
  • singleTop:栈顶复用模式。这种模式下,新的 Activity 已经位于栈顶,那么此 Activity 实例将不再创建,并且回调onNewIntent方法。但如果新的 Activity 不在栈顶,不管其是否存在都会新建 Activity 的实例,并将新建的实例置于栈顶。
  • singleTask:栈内复用模式。简单来说,这就是 Activity 实例的单例模式,只要栈中存在 Activity 实例,就不会创建这个 Activity 的新实例,同时会回调onNewIntent。对于Activity A,当栈中不存在 A 需要的任务栈,那么便会创建一个新的任务栈,同时将 A 放入新的任务栈中;如果存在 A 需要的任务栈,此时若 A 的实例存在,那么 A 会被调到栈顶(会清空 A 之上的实例),并且执行onNewIntent,如果 A 的实例不存在,那么便会创建 A 的实例,并将其放入栈中。
  • singleInstance:单实例模式。加强的singleTask模式,拥有其全部特性。具有此模式的 Activity 只能单独位于一个任务栈,并且只有这么一个实例存在。

默认情况下所有 Activity 所需的任务栈的名字为应用的包名,而任务栈可以通过TaskAffinity标识,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。任务栈分为前台任务栈与后天任务栈,后台任务栈中的 Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。

TaskAffinitysingleTask启动模式配对使用时,他是具有该模式的 Activity 的目前任务栈的名字,待启动的 Activity 就会运行在名字和TaskAffinity相同的任务栈中。

TaskAffinityallowTaskReparenting结合使用的时候,当一个应用 A 启动了应用 B 的某个 Activity 后,如果这个 Activity 的allowTaskReparenting属性为true的话,那么应用 B 被启动后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中。

指定 Activity 的启动模式主要有两种方式,一种是在AndroidManifest.xml中指定,另外一种是通过Intent的标志位来指定。

<activity
	android:name="com.cj5785.demo.MainActivity"
	android:configChanges="screenLayout"
	android:launchMode="singleTask"
	android:label="@string/app_name" />
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

二者的区别在于:

1. 优先级不同,代码添加的优先级高于XML中添加,同时存在时,以代码为准
2. 范围不同,XML方式无法设置`FLAG_ACTIVITY_CLEAR_TOP`,代码无法设置`singleInstance`

查看任务栈信息:adb shell dumpsys activity

关于任务栈,当前台任务栈都出栈时,回将后台任务栈调至前台。

Activity的Flags

Activity 的 Flags 有很多,除了几个常用的,其余可以在用到的时候去文档查询

FLAG_ACTIVITY_NEW_TASK

为 Activity 指定singleTask启动模式,效果和在XML中指定该启动模式相同

FLAG_ACTIVITY_SINGLE_TOP

为 Activity 指定singleTop启动模式,效果和在XML中指定该启动模式相同

FLAG_ACTIVITY_CLEAR_TOP

具有此标记的的 Activity,当其启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

具有这个标记的 Activity 不会出现在历史 Activity 列表中,等同于XML中的android:excludeFromRecents="true"

IntentFilter的匹配规则

Activity 的启动分为两种,隐式调用和显式调用,而 IntentFilter 正是用来匹配隐式调用的。IntentFilter 中的过滤信息有action,category,data。只有一个 Intent 同时匹配一组action,category,data才算完全匹配,也只有完全匹配才能启动目标 Activity,一个 Activity 可以包含多个intent-filter,而intent-filter中正是包含了三个信息的组合。

action的匹配规则

action 是一个字符串,系统预定义了一些 action,也可以自定义 action。action的匹配要求字符串完全一致(区分大小写),一个intent-filter可以存在多个action,只要能够与其中任意一个完全一致则视为匹配成功。也就是说,action 的匹配要求 Intent 中的 action 存在且必须和过滤规则中的其中一个 action 相同。

category的匹配规则

category 是一个字符串,系统预定义了一些 category,也可以自定义 category。category 匹配要求 Intent 中如果有 category,那么所有的 category 都必须和过滤规则中的其中一个 category 相同。如果不写,那么在startActivity的时候会默认加上android.intent.category.DEFAULT,为了能够接受隐式调用,也必须加上android.intent.category.DEFAULT

data的匹配规则

data 的匹配规则与 action 类似,如果过滤规则中定义了 data,那么 Intent 中也必须要定义可匹配的 data。

data 的语法:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

data 由两部分组成,mimeType 和 URI。

URI的默认值为content和file,也就是说如果指定了,那么就必须是content或者file

<data android:mimeType="image/*" />
intent.setDataAndType(Uri.parse("file://abc"), "image/*");

intent的setDatasetType会使得后调用的清除先调用的值

隐式调用启动 Activity,如果没有能够匹配的项,就会报错ActivityNotFoundException,这种情况一般使用PackageManagerresolveActivity或者IntentresolveActivity方法先做判断,找不到匹配项则会返回null,其参数第二位为标志为,一般设置为MATCH_DEFAULT_ONLY,以防止匹配到不含 default category 的 Activity,而那些 Activity 是无法通过隐式调用启动的。

你可能感兴趣的:(读书笔记,Android开发艺术探索)