Android Activity 攻略

559 阅读40分钟

攻略大全

1. 粘贴攻略

1.1 Activity概述

Activity是与用户交互的接口。

Android系统是通过Activity栈的形式来管理Activity。

1.2 Activity的运行状态

Activity生命周期一般分为四个基本状态,分别是活动状态(running),暂停状态(paused),停止状态(stopped)和死亡状态(killed)。

1.2.1 活动状态(running)

活动状态一般是指该Activity正处于屏幕最显著的位置上显示,即该Activity是在Android活动栈的最顶端。 

一般地当Activity创建后就是处于该状态中。 

期间触发的函数及顺序为: onCreate() ->onStart() -> onResume()。 

其中: 

  • onCreate()只有在该Activity是第一次被创建时才会被调用,主要是负责Activity的一般性的初始化设置,包括视图的创建,数据的绑定等等。需要注意的是若之前有冻结的state(即系统对该Activity调用过onSaveInstanceState()函数),则可以通过其 Bundle 参数进行state恢复。 

  • onStart()是当Activity正在变为可见状态时才会被调用。一般地在此期间可以注册一个广播等等。 

  • onResume()是在该Activity将要和用户进行交互时被调用,此时Activity位于的活动栈顶部。

1.2.2 暂停状态(paused)

暂停状态一般指该Activity已失去了焦点但仍然是可见的状态(包括部分可见)。一个处于暂停状态的Activity只有在系统极度缺乏内存资源的情况下才会被系统强制结束。 

运行状态到暂停状态所触发的函数及顺序为:onResume() -> onPuased()。 

暂停状态恢复至运行状态所触发的函数及顺序为:onPuased() -> onResume()。 

其中: 

  • onPuased()是当一个Activity失去系统焦点后将会被调用,包括界面被部分遮挡,以及设备转入休眠状态等等。一般地在此期间对一些未保存的数据进行持久化并停止其他需要耗费CPU的操作,同时不可进行耗时操作,否则会阻塞系统UI线程。

1.2.3 停止状态(stopped)

停止状态一般指该Activity被另一个Activity完全覆盖的状态,这时它仍然保持所有的状态,但是由于该Activity变得不可见,所以系统经常会由于内存不足而将该Activity强行结束。 

暂停状态到停止状态所触发的函数及顺序为:onPuased() -> onStop()。 

停止状态恢复至运行状态所触发的函数及顺序为:onStop() -> onRestart() -> onStart() -> onResume()。 

其中: 

  • onStop()是当一个Activity变为不可见时将会被调用,此时可能是由于该Activity要被注销或新的Activity完全遮挡了该Activity。在此期间一般可以进行取消注册广播等操作,因为用户不可见。 

  • onRestart()是当一个Activity从停止状态恢复至运行状态时将会被优先调用。

1.2.4 死亡状态

死亡态是指该Activity被系统销毁。当一个Activity处于暂停状态或停止状态时就随处可能进入死亡状态,因为系统可能因内存不足而强行结束该Activity。 

停止状态到死亡状态分为两种情况:

  • (1)由用户操作导致,则执行:onStop() -> onDestroy()。
  • (2)由系统自动强制执行,则该Activity被强行结束。 

其中: 

  • onDestroy()是当一个Activity正在被系统finished期间被调用的。

1.3 Activity的数据通信

1.3.1 Activity与Activity

  • Intent/Bundle
  • 类静态变量
  • 全局变量
  • EventBus之类的框架

1.3.2 Activity与Fragment

  • Bundle
  • 直接在Activity中定义方法
  • 广播
  • EventBus之类的框架

1.3.3 Fragment与Activity

  • 接口回调:在fragment中定义一个内部回调接口,在fragment调用onDetach()方法时,要把传递进来的activity对象释放掉,防止内存泄漏。

  • 广播

  • EventBus之类的框架

1.3.4 Activity与Service

  • 绑定服务,利用ServiceConnection类

  • 简单通信,利用Intent传值

  • 定义一个callback接口来监听服务中的进程的变化,可配合Handler实现特殊需求。

1.4 Android进程层次

在android系统中最重要的进程被称为前台进程,然后依次是任何可见进程、服务进程、后台进程,最后是空进程。

要明确一个问题,当我们谈论进程优先级的时候是以 activity、service 这样的组件来说的,但请注意这些优先级是在进程的级别上的,而非组件级别。只要有一个组件是前台进程,就会将整个进程变为前台进程。同时我们要知道绝大多数应用是单进程的,如果我们有生命周期差异很大的不同部分或者某个部分非常重量型,那么我们强烈建议把它们分为不同的进程,以便让重量级进程尽早被系统回收。

3DFCDC46-991A-4FCD-B026-2E0B1C2EFB68.png

1.4.1 Foreground Processes(前台进程)

  系统中前台进程的数量很少(这点从图中也是可以看出来的), 前台进程几乎不会被杀死. 只有当内存低到无法保证所有的前台进程同时运行时才会选择杀死某个前台进程.以下几种都属于前台进程: 

  • a. 处于前台正与用户交互的activity 

  • b. 与前台activity绑定的service 

  • c. 调用了startForeground()方法的service 

  • d. 正在执行onCreate(), onStart(), 或onDestroy()方法的service 

  • e. 正在执行onReceive()方法的BroadcastReceiver. 

凡是包含以上任意一种情况的进程都是前台进程。当然一些activity在依靠他们成为前台进程的同时,也可能依赖bindService。任何进程,如果它持有一个绑定到前台activity的服务,那么它也被赋予了同样的前台优先级。

1.4.2 Visible Processes(可见进程)

  此时如果一个Activity可见但并非处于前台时,如在Activity中弹出了一个对话框,从而导致Activity可见但位于后台无法与用户交互,这个进程就可以被视为可见进程。

同时我们也必须明白可见 activity 的 bindService 和 content provider 也处于可见进程状态。这同样是为了保证使用中的 activity 所依赖的进程不会被过早地杀掉。

但还是需要注意的是,只是可见并不意味着不能被杀掉。如果来自前台进程的内存压力过大,可见进程仍有可能被杀掉。从用户的角度看,这意味着当前 activity 背后的可见 activity 会被黑屏代替。当然,倘若我们正确地重建 activity ,在前台 activity 关闭之后,我们的进程和 activity 会立刻恢复而没有数据损失。

1.4.3 Service Processes(服务进程)

  如果我们通过startService()启动一个service服务,那么它被看作是一个服务进程。对于许多在后台做处理(如异步加载数据,获取耗时资源等)而没有立即成为前台服务的app都属于这种情况。这是比较常见也是最合理的后台处理方式,这种进程只有在可见进程和前台进程内存不足时才有可能被杀掉。

1.4.4 Background Processes(后台进程)

  假如我们的Activity目前是前台进程,但是这时候,我们点Home键,将导致onPause,onStop方法被调用,我们的进程也就变成了后台进程。

我们的后台进程并不会被立马杀死,这些进程会保留一段时间,直到更高优先级进程需要内存的时候才被回收,并且是按照最近最少使用顺序(最少使用的会被优先回收)。

很多时候我们会发现手机的内存大都是被后台App进程占用了大部分空间,而android系统这样做的好处是可以使用我们在下次重新打开此进程的app时无需重新分配和加载资源,从而拥有更好的用户体验。 

然而内存不足的时候,他们仍然会被杀掉,所以我们应该和可见activity处理情况一样,应该尽量能够在不丢失用户状态的情况下重建这些activity,以便达到更佳的用户体验。

1.4.5 Empty Processes(空进程)

  在任何层次中,空进程都是最低优先级的。如果我们的进程不属于以上类别,那它就是空进程。空进程是没有活跃的组件,只是出于缓存的目的而被保留(为了更加有效地使用内存而不是完全释放掉),只要 Android 系统内存需要可以随时杀掉它们。

1.5 Activity的过渡动画

1.5.1 overridePendingTransition

overridePendingTransition的方式比较老旧、生硬。

startActivity(intent);
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_in_left);

overridePendingTransition在startActivity或者是finish方法后立刻执行才有效。

1.5.2 windowAnimationStyle

windowAnimationStyle中存在四种动画:

  • activityOpenEnterAnimation,用于设置打开新的Activity并进入新的Activity展示的动画。

  • activityOpenExitAnimation,用于设置打开新的Activity并销毁之前的Activity展示的动画。

  • activityCloseEnterAnimation,用于设置关闭当前Activity进入上一个Activity展示的动画。

  • activityCloseExitAnimation,用于设置关闭当前Activity时展示的动画。

1.5.3 ActivityOptions

MD风格之下推出的一种新颖方式。

  • 1.在跳转的Activity中设置contentFeature
@Override 
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置contentFeature,可使用切换动画
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
Transition explode = TransitionInflater.from(this).inflateTransition(Android.R.transition.explode);
getWindow().setEnterTransition(explode);
setContentView(R.layout.activity_three); }
  • 2.在startActivity执行跳转逻辑的时候调用startActivity的重写方法,执行ActivityOptions.makeSceneTransitionAnimation方法
Intent intent = new Intent(MainActivity.this, ThreeActivity.class);
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle()); 
  • 3.以style的方式使用ActivityOptions内置的动画效果

  • 4.通过transitionName属性,使用ActivityOptions动画共享组件的方式实现跳转Activity动画

1.5.4 过渡动画总结

  • overridePendingTransition方法从Android2.0开始,基本上能够覆盖我们activity跳转动画的需求;
  • ActivityOptions API是在Android5.0开始的,可以实现一些炫酷的动画效果,更加符合MD风格;ActivityOptions还可以实现两个Activity组件之间的过度动画;

2.造火箭攻略

2.1 Activity的生命周期全面分析

典型情况下的生命周期,是指在有用户参与的情况下,Activity所经过的生命周期的改变;

而异常情况下的生命周期是指Activity被系统回收或者由于当前设备的Configuration发生改变从而导致Activity被销毁重建

2.1.1 典型情况下的生命周期分析

  • (1)onCreate:表示Activity正在被创建。这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。

  • (2)onRestart:表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。

  • (3)onStart:表示Activity正在被启动,即将开始。这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为Activity已经显示出来了,但是我们还看不到。

  • (4)onResume:表示Activity已经可见了,并且出现在前台并开始活动。要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。

  • (5)onPause:表示Activity正在停止。正常情况下紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onResume会被调用。笔者的理解是,这种情况属于极端情况,用户操作很难重现这一场景。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行。

  • (6)onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。

  • (7)onDestroy:表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。

郑重提示:Activity弹出普通的dialog时,会失去焦点无法交互,但是不会回调onPause()即生命周期并没有发生改变。

只有当弹出的是Dialog主题的Activity时,原Activity的生命周期才会发生改变进而回调onPause()。

因为生命周期的回调都是AMS通过Binder通知应用进程调用的,而Dialog、Toast、PopupWindow本质上都是直接通过 WindowManager.addView() 显示的(没有经过 AMS),所以不会对生命周期有任何影响。

生命周期示意图如下: image.png

onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度来回调的。 除了这种区别,在实际使用中没有其他明显区别。

当新启动一个Activity的时候,旧Activity的onPause会先执行,然后才会启动新的Activity。

Android官方文档对onPause的解释有这么一句:不能在onPause中做重量级的操作,因为必须onPause执行完成以后新Activity才能Resume,从这一点也能间接证明我们的结论。

通过分析这个问题,我们知道onPause和onStop都不能执行耗时的操作,尤其是onPause,这也意味着,我们应当尽量在onStop中做操作,从而使得新Activity尽快显示出来并切换到前台。

2.1.2 异常情况下的生命周期分析

1.情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

image.png

当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。

从时序上来说,onSaveInstanceState的调用时机是在onStop之前,它和onPause没有既定的时序关系,它既可能在onPause之前调用,也可能在onPause之后调用。

当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。

因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复。从时序上来说,onRestoreInstanceState的调用时机在onStart之后。

在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作。 当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等,这些View相关的状态系统都能够默认为我们恢复。

具体针对某一个特定的View系统能为我们恢复哪些数据,我们可以查看View的源码。 和Activity一样,每个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,看一下它们的具体实现,就能知道系统能够自动为每个View恢复哪些数据。

关于保存和恢复View层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶级容器去保存数据。顶层容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情,这种思想在Android中有很多应用,比如View的绘制过程、事件分发等都是采用类似的思想。

可以在onSaveInstanceState中存储数据,然后当Activity被销毁并重新创建后,再去获取之前存储的数据。 接收的位置可以选择onRestoreInstanceState或者onCreate,二者的区别是:

  • onRestoreInstanceState一旦被调用,其参数BundlesavedInstanceState一定是有值的,我们不用额外地判断是否为空;
  • 但是onCreate不行,onCreate如果是正常启动的话,其参数Bundle savedInstanceState为null,所以必须要额外判断。

这两个方法我们选择任意一个都可以进行数据恢复,但是官方文档的建议是采用onRestoreInstanceState去恢复数据。

针对onSaveInstanceState方法还有一点需要说明,那就是系统只会在Activity即将被销毁并且有机会重新显示的情况下才会去调用它。

考虑这么一种情况,当Activity正常销毁的时候,系统不会调用onSaveInstanceState,因为被销毁的Activity不可能再次被显示。这句话不好理解,但是我们可以对比一下旋转屏幕所造成的Activity异常销毁,这个过程和正常停止Activity是不一样的,因为旋转屏幕后,Activity被销毁的同时会立刻创建新的Activity实例,这个时候Activity有机会再次立刻展示,所以系统要进行数据存储。

可以简单地这么理解,系统只在Activity异常终止的时候才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程。

2.情况2:资源内存不足导致低优先级的Activity被杀死

Activity按照优先级从高到低,可以分为如下三种:

  • (1)前台Activity:正在和用户交互的Activity,优先级最高。
  • (2)可见但非前台Activity:比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
  • (3)后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低。

当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数数据。

如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。 比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。

1.1.3 Activity的configChanges

image.png

2.2 Activity的启动模式

2.2.1 Activity的LaunchMode

  • (1)standard:标准模式,这也是系统的默认模式。

每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期,这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。

比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的栈中。

而当我们用ApplicationContext去启动standard模式的Activity的时候会报错, 这是因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以这就有问题了。

解决这个问题的方法是为待启动Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动Activity实际上是以singleTask模式启动的。

  • (2)singleTop:栈顶复用模式。

在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。

需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。此时的生命周期时序为:onCreate -> onStart -> onResume -> onPause -> onNewIntent -> onResume。

如果新Activity的实例已存在但不是位于栈顶,那么新Activity仍然会重新重建。 举个例子,假设目前栈内的情况为ABCD,其中ABCD为四个Activity, A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。

  • (3)singleTask:栈内复用模式。

这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent。

具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中。

  • (4)singleInstance:单实例模式。

这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中,换句话说,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。

2.2.2 TaskAffinity

TaskAffinity,可以翻译为任务相关性。

这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的包名。

当然,我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。

TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。比如说目标activity指定了taskAffinity但是启动模式是standard,那么被启动的目标activity会进入启动它的activity所在的栈,也就不会创建出目标activity所需的任务栈,更不会存在切换任务栈的情况。

任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。具体的任务栈切换可以通过startActivity将目标Acitivity切换到前台,而这其实就是依赖于singleTask的特性。

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

当TaskAffinity和allowTaskReparenting结合的时候,这种情况比较复杂,会产生特殊的效果。

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

这还是很抽象,再具体点,比如现在有2个应用A和B, A启动了B的一个Activity C,然后按Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动了B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中。可以这么理解,由于A启动了C,这个时候C只能运行在A的任务栈中,但是C属于B应用,正常情况下,它的TaskAffinity值肯定不可能和A的任务栈相同(因为包名不同)。

所以,当B被启动后,B会创建自己的任务栈,这个时候系统发现C原本所想要的任务栈已经被创建了,所以就把C从A的任务栈中转移过来了。

2.2.3 指定Activity的启动模式

给Activity指定启动模式有两种方法:

  • 通过AndroidMenifest为Activity指定启动模式
  • 通过在Intent中设置标志位来为Activity指定启动模式

这两种方式都可以为Activity指定启动模式,但是二者还是有区别的:

  • 首先,优先级上,第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;
  • 其次,上述两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式。

2.2.4 Activity的Flags

  • FLAG_ACTIVITY_NEW_TASK

    这个标记位的作用是为Activity指定“singleTask”启动模式,其效果和在XML中指定该启动模式相同。

  • FLAG_ACTIVITY_SINGLE_TOP

    这个标记位的作用是为Activity指定“singleTop”启动模式,其效果和在XML中指定该启动模式相同。

  • FLAG_ACTIVITY_CLEAR_TOP

    具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶,singleTask启动模式默认就具有此标记位的效果。

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

    具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents="true"。注意:由该 Activity 启动的后续同属一个 “Task” 的一系列 Activity 都不会出现在 Recent screens。也就是说该属性是对 Task 起作用的,而不仅仅是某个 Activity。所以想要后续的 Activity 能够出现在 Recent screens 中,就必须让后续 Activity 在新的 Task 中。

2.3 IntentFilter的匹配规则

启动Activity分为两种,显式调用和隐式调用。

显式调用需要明确地指定被启动对象的组件信息,包括包名和类名,而隐式调用则不需要明确指定组件信息。

原则上一个Intent不应该既是显式调用又是隐式调用,如果二者共存的话以显式调用为主。

隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action、category、data。

一个过滤列表中的action、category和data可以有多个,所有的action、category、data分别构成不同类别,同一类别的信息共同约束当前类别的匹配过程。只有一个Intent同时匹配action类别、category类别、data类别才算完全匹配,只有完全匹配才能成功启动目标Activity。另外一点,一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

1.action的匹配规则

action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符串值完全一样。一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。

需要注意的是,Intent中如果没有指定action,那么匹配失败。

总结一下,action的匹配要求Intent中的action必须存在且和过滤规则中的其中一个action相同,这里需要注意它和category匹配规则的不同。另外,action区分大小写,大小写不同字符串相同的action会匹配失败。

2.category的匹配规则

category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。

category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。

这里要注意下它和action匹配过程的不同,action是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同,而category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能够和过滤规则中的任何一个category相同。

3.data的匹配规则

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

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

mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式。

URI中包含的数据就比较多了,结构为:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|pathPattern]
content://com.example.project:200/folder/subfolder/etc
https://www.baidu.com:80/search/info
  • Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,这也意味着URI是无效的。

  • Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。

  • Port:URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。

  • Path、pathPattern和pathPrefix:这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“”, “”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“”要写成“\”, “\”要写成“\\”;pathPrefix表示路径的前缀信息。

data的匹配规则和action类似,它也要求Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。这里的完全匹配是指过滤规则中出现的data部分也出现在了Intent中的data中。

如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值。

当我们通过隐式方式启动一个Activity的时候,可以做一下判断,看是否有Activity能够匹配我们的隐式Intent,如果不做判断就有可能出现错误。

判断方法有两种:采用PackageManager的resolveActivity方法或者Intent的resolveActivity方法,如果它们找不到匹配的Activity就会返回null,我们通过判断返回值就可以规避上述错误了。

另外,PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity方法不同的是:它不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息。

2.4 onCreate()/根Activity的启动过程/应用程序的启动过程

Activity的启动过程分为两种,一种是根Activity的启动过程,另一种是普通Activity的启动过程。

根Activity指的是应用程序启动的第一个Activity,因此根Activity的启动过程一般情况下也可以理解为应用程序的启动过程。

普通Activity指的是除应用程序启动的第一个Activity之外的其他Activity。

根Activity的启动过程,它和普通Activity的启动过程是有重叠部分的,只不过根Activity的启动过程一般情况下指的就是应用程序的启动过程,更具有指导性意义。

2.4.1 流程简述

2.4.1.1 Launcher请求AMS过程

当我们点击某个应用程序的快捷图标时,就会通过Launcher请求AMS来启动该应用程序。

Launcher的startActivitySafely()中,会构建一个Intent,并添加Intent.FALG_ACTIVITY_NEW_TASK的flag用于标识需创建一个新的任务栈,而后会调用Activity的startActivity()。

Activity的startActivity会调用startActivityForResult并传入值为-1的requestCode参数,标识Launcher不需要知道Activity启动的结果。

Activity的startActivityForResult()中会有一个根Activity是否为null的判断。既然是根Activity的启动,则判断条件下一定为null,此时会去调用Instrumentation.execStartActivity()。

Instrumentation.execStartActivity()中会通过ActivityManager.getService获取到AMS的代理,紧接着调用代理的startActivity(),并传入该方法所需要的一系列参数。

至此完成了Launcher到AMS的一个方法调用,代码逻辑进入了AMS中。

2.4.1.2 AMS到ApplicationThread的调用过程

AMS.startActivity()会返回statrActivityAsUser()。

statrActivityAsUser()中会判断调用者进程是否被隔离,如果被隔离会抛出SecurityException,会检查调用者是否有权限,如果没有权限也会抛出SecurityException异常,若皆无异常会紧接着调用ActivityStarter.startActivityMayWait()。

ActivityStater是Android 7.0中新加入的类,它是加载Activity的控制类,会收集所有的逻辑来决定如何将Intent和Flags转换为Activity,并将Activity和Task以及Stack相关联。

ActivityStarter.startActivityMayWait会紧接着调用startActivityLocked()。

startActivityLocked()中会判断启动理由参数是否为null,如果为null则抛出llegalArgumentException异常,否则会紧接着调用startActviity()。

ActivityStarter.startActviity()中会判断IApplicationThread类型的caller是否为null,这个caller是方法调用一路传过来的,指向的是Launcher所在的应用的程序进程的ApplicationtThrad对象。然后会通过AMS获取到代表Launcher进程的callerApp对象,它是ProcessRecord类型,该类型用于描述一个应用程序进程。紧接着创建一个ActivityRecord,用于描述将要启动的Activity。最后调用startActivityUnChecked()。

ActivityStarter.startActivityUnChecked()中主要处理与栈管理相关的逻辑,最后调用ActivityStackSupervisor.resumeFocusedStackTopActivityLocked()。

resumeFocusedStackTopActivityLocked()中会判断如果ActivityRecord不为null或者要启动的Activity的状态不是RESUME状态时会调用ActivityStack.resumeTopActivityUncheckedLocked()。

ActivityStack.resumeTopActivityUncheckedLocked()中会紧接着调用ActivityStack.resumeTopActivityInnerLocked(),方法中会紧接着调用ActivityStackSupervisor.startSpecificActivityLocked()。

void startSpecificActivityLocked(ActivityRecord r,
        boolean andResume, boolean checkConfig) {
    // Is this activity's application already running?
    ProcessRecord app = mService.getProcessRecordLocked(r.processName,
            r.info.applicationInfo.uid, true);

    getLaunchTimeTracker().setLaunchTime(r);

    if (app != null && app.thread != null) {
        try {
            if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                    || !"android".equals(r.info.packageName)) {
                // Don't add this if it is a platform component that is marked
                // to run in multiple processes, because this is actually
                // part of the framework so doesn't make sense to track as a
                // separate apk in the process.
                app.addPackage(r.info.packageName, r.info.applicationInfo.longVersionCode,
                        mService.mProcessStats);
            }
            realStartActivityLocked(r, app, andResume, checkConfig);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting activity "
                    + r.intent.getComponent().flattenToShortString(), e);
        }

        // If a dead object exception was thrown -- fall through to
        // restart the application.
    }

    mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
            "activity", r.intent.getComponent(), false, false, true);
}

在ActivityStackSupervisor.startSpecificActivityLocked()中获取即将启动的Activity所在的应用程序进程,并判断该应用程序进程是否已在运行。如果已经运行的话,会紧接着调用realStartActivityLocked();如果没有,则调用AMS.startProcessLocked()。

而ActivityStackSupervisor.realStartActivityLocked()中会调用app.thread.scheduleLaunchActivity()。

app.thread指的就是IApplicationThread,它的实现是ActivityThread的内部类ApplicationThread,其中ApplicationThread继承了IApplicationThread.Stub。

app指的是传入的要启动的Activity所在的应用程序进程。

因此,这段代码指的就是要在目标应用程序进程启动Activity。

当前代码逻辑运行在AMS 所在的进程(SystemServer 进程)中,通过ApplicationThread来与应用程序进程进行Binder通信。换句话说,ApplicationThread是AMS所在进程(SystemServer进程)和应用程序进程的通信桥梁。

image.png

至此,代码逻辑从AMS进入了应用程序进程中。

2.4.1.3 ActivityThread启动Activity过程

在scheduleLaunchActivity()中会将启动Activity的参数封装成ActivityClientRecord,并通过sendMessage()方法向H类发送类型为LAUNCH_ACTIVITY的消息,并将ActivityClientRecord传递过去。H是ActivityTread的内部类并继承自Handler,是应用程序进程中主线程的消息管理类。因为ApplicationThread是一个Binder,它的调用运行在Binder线程池中,所以这里需要通过H将代码的逻辑切换到主线程中。

在H的handleMessage方法中收到该消息后,会进一步封装ActivtyClientRecord所需的相关信息,而后调用handleLaunchActivity()。

handleLaunchActivity()中会调用perforLaunchActivity()来启动Activity,如果activity不为null则置其状态为Resume,否则通知AMS停止启动Activity。

在perforLaunchActivity()中通过传入的ActivityClicentRecord获取所需的信息(如activity的theme和launchMode、包名类名、Apk文件的描述类等)后:

  • 先是创建要启动Activity的上下文环境,并通过上下文环境获取类加载器进而创建该Activity的实例,
  • 然后通过makeApplication()创建Application,该方法内部会调用Application的onAttach、onCreate()方法,
  • 而后调用activity的attach()方法进行相关初始化(会创建Window树对象(PhoneWindow)并与Activity自身进行关联),
  • 最后调用Instrumentation的callActivityOnCreate()来启动Activity,Instrumentation的callActivityOnCreate()中会调用activity.performCreate(),该方法中会紧接着调用activity.onCreate()方法。

至此,根Activity就启动了,即应用程序启动了。

2.4.2.4 根Activity启动过程中涉及的进程

根Activity启动过程中涉及的进程之间的关系: image.png

根Activity启动过程中进程调用时序图: image.png

2.4.2 流程图示

image.png

  • 根Activity的启动流程 Android 根Activity的启动流程.png

  • 根Activity启动过程中涉及的进程之间的关系: image.png

  • 根Activity启动过程中进程调用时序图: image.png

2.5 setContentView()

2.6 onStart()

2.7 onResume()

2.8 onPause()

2.9 onStop()

2.10 onDestroy()

3. 拧螺丝攻略

3.1 onCreate()/根Activity的启动过程/应用程序的启动过程

3.1.1 Launcher请求AMS过程

Launcher进程启动后,会将已安装应用程序的快捷图标显示到桌面上。

这些应用程序的快捷图标就是启动根Activity的入口,当我们点击某个应用程序的快捷图标时,就会通过Launcher请求AMS来启动该应用程序。

Launcher请求AMS的时序图如图所示: image.png

Launcher的startActivitySafely方法,如下所示: image.png

在注释1处将Flag设置为Intent.FLAG_ACTIVITY_NEW_TASK①,这样根Activity会在新的任务栈中启动。

在注释2处会调用startActivity方法,这个startActivity方法在Activity中实现,如下所示:

image.png

在startActivity 方法中会调用startActivityForResult 方法,它的第二个参数为-1,表示Launcher不需要知道Activity启动的结果,startActivityForResult方法的代码如下所示:

image.png 注释1处的mParent是Activity类型的,表示当前Activity的父类。因为目前根Activity还没有创建出来,因此,mParent==null成立。接着调用Instrumentation的execStartActivity方法,Instrumentation 主要用来监控应用程序和系统的交互,execStartActivity 方法的代码如下所示:

image.png 首先调用ActivityManager的getService方法来获取AMS的代理对象,接着调用它的startActivity方法。首先我们来查看ActivityManager的getService方法做了什么:

image.png getService 方法调用了IActivityManagerSingleton的get方法,我们接着往下看。

IActivityManagerSingleton是一个Singleton类。

在注释1处得到名为“activity”的Service引用,也就是IBinder类型的AMS的引用。

接着在注释2处将它转换成IActivityManager类型的对象,这段代码采用的是AIDL,IActivityManager.java 类是由AIDL 工具在编译时自动生成的,IActivityManager.aidl 的文件路径为frameworks/base/core/java/android/app/IActivityManager.aidl。

要实现进程间通信,服务器端也就是AMS只需要继承IActivityManager.Stub 类并实现相应的方法就可以了。

回到Instrumentation类的execStartActivity方法中,从上面得知execStartActivity方法最终调用的是AMS的startActivity方法。

3.1.2 AMS到ApplicationThread的调用过程

Launcher请求AMS后,代码逻辑已经进入AMS中,接着是AMS到ApplicationThread的调用流程,时序图如图所示。

image.png

AMS的startActivity()如下: image.png 在AMS的startActivity方法中返回了startActivityAsUser方法,可以发现startActivityAsUser方法比startActivity方法多了一个参数UserHandle.getCallingUserId(),这个方法会获得调用者的UserId,AMS根据这个UserId来确定调用者的权限。

image.png image.png 在注释1处判断调用者进程是否被隔离,如果被隔离则抛出SecurityException异常。 在注释2处检查调用者是否有权限,如果没有权限也会抛出SecurityException异常。 最后调用了ActivityStarter的startActivityLocked 方法。 startActivityLocked 方法的参数要比startActivityAsUser多几个,需要注意的是倒数第二个参数类型为TaskRecord,代表启动的Activity所在的栈。最后一个参数"startActivityAsUser"代表启动的理由。

ActivityStarter是Android 7.0中新加入的类,它是加载Activity的控制类,会收集所有的逻辑来决定如何将Intent和Flags转换为Activity,并将Activity和Task以及Stack相关联。 ActivityStarter的startActivityMayWait方法调用了startActivityLocked方法,如下所示: image.png 在注释1处判断启动的理由不为空,如果为空则抛出IllegalArgumentException异常。紧接着又调用了startActivity方法,如下所示:

image.png

image.png

ActivityStarter的startActivity方法逻辑比较多,这里列出部分我们需要关心的代码。在注释1处判断IApplicationThread类型的caller是否为null,这个caller是方法调用一路传过来的,指向的是Launcher所在的应用程序进程的ApplicationThread对象,在注释2处调用AMS的getRecordForAppLocked方法得到的是代表Launcher进程的callerApp对象,它是ProcessRecord类型的,ProcessRecord用于描述一个应用程序进程。同样地,ActivityRecord用于描述一个Activity,用来记录一个Activity 的所有信息。接下来创建ActivityRecord,用于描述将要启动的Activity,并在注释3处将创建的ActivityRecord赋值给ActivityRecord[]类型的outActivity,这个outActivity会作为注释4处的startActivity方法的参数传递下去。

image.png startActivity方法紧接着调用了startActivityUnchecked方法: image.png

image.png startActivityUnchecked 方法主要处理与栈管理相关的逻辑。在标注①处我们得知,启动根Activity时会将Intent的Flag设置为FLAG_ACTIVITY_NEW_TASK,这样注释1处的条件判断就会满足,接着执行注释2处的setTaskFromReuseOrCreateNewTask方法,其内部会创建一个新的TaskRecord,用来描述一个Activity任务栈,也就是说setTaskFromReuseOrCreateNewTask方法内部会创建一个新的Activity任务栈。Activity任务栈其实是一个假想的模型,并不真实存在。在注释3处会调用ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,如下所示:

image.png 在注释1处调用ActivityStack的topRunningActivityLocked方法获取要启动的Activity所在栈的栈顶的不是处于停止状态的ActivityRecord。在注释2处,如果ActivityRecord不为null,或者要启动的Activity的状态不是RESUMED状态,就会调用注释3处的ActivityStack的resumeTopActivityUncheckedLocked方法,对于即将启动的Activity,注释2处的条件判断是肯定满足的,我们来查看ActivityStack的resumeTopActivityUncheckedLocked方法,如下所示:

image.png

紧接着查看注释1处ActivityStack的resumeTopActivityInnerLocked方法:

image.png resumeTopActivityInnerLocked 方法代码非常多,我们只需要关注调用了ActivityStackSupervisor的startSpecificActivityLocked方法即可,代码如下所示:

image.png

image.png

在注释1处获取即将启动的Activity所在的应用程序进程,在注释2处判断要启动的Activity所在的应用程序进程如果已经运行的话,就会调用注释3处的realStartActivityLocked方法,这个方法的第二个参数是代表要启动的Activity所在的应用程序进程的ProcessRecord。

image.png

这里的app.thread指的是IApplicationThread,它的实现是ActivityThread 的内部类ApplicationThread,其中ApplicationThread继承了IApplicationThread.Stub。app指的是传入的要启动的Activity所在的应用程序进程,因此,这段代码指的就是要在目标应用程序进程启动Activity。当前代码逻辑运行在AMS 所在的进程(SystemServer 进程)中,通过ApplicationThread来与应用程序进程进行Binder通信,换句话说,ApplicationThread是AMS所在进程(SystemServer进程)和应用程序进程的通信桥梁,如图4-3所示。

image.png

3.1.3 ActivityThread启动Activity的过程

ActivityThread启动Activity过程的时序图,如图所示。

image.png

接着查看ApplicationThread的scheduleLaunchActivity方法,其中ApplicationThread是ActivityThread 的内部类,在#Android App Process启动流程攻略中讲过应用程序进程创建后会运行代表主线程的实例ActivityThread,它管理着当前应用程序进程的主线程。ApplicationThread 的scheduleLaunchActivity方法如下所示: image.png scheduleLaunchActivity方法将启动Activity的参数封装成ActivityClientRecord,sendMessage方法向H类发送类型为LAUNCH_ACTIVITY的消息,并将ActivityClientRecord传递过去,sendMessage方法有多个重载方法,最终调用的sendMessage方法如下所示:

image.png

image.png

这里mH指的是H,它是ActivityThread的内部类并继承自Handler,是应用程序进程中主线程的消息管理类。因为ApplicationThread是一个Binder,它的调用逻辑运行在Binder线程池中,所以这里需要用H将代码的逻辑切换到主线程中。H的代码如下所示:

image.png

查看H的handleMessage方法中对LAUNCH_ACTIVITY的处理,在注释1处将传过来的msg的成员变量obj转换为ActivityClientRecord。在注释2处通过getPackageInfoNoCheck方法获得LoadedApk类型的对象并赋值给ActivityClientRecord 的成员变量packageInfo。应用程序进程要启动Activity时需要将该Activity所属的APK加载进来,而LoadedApk就是用来描述已加载的APK文件的。在注释3处调用handleLaunchActivity方法,代码如下所示:

image.png

image.png

注释1处的performLaunchActivity方法用来启动Activity,注释2处的代码用来将Activity的状态设置为Resume。如果该Activity为null则会通知AMS停止启动Activity。下面来查看performLaunchActivity方法做了什么:

image.png

image.png

注释1处用来获取ActivityInfo,用于存储代码以及AndroidManifes设置的Activity和Receiver节点信息,比如Activity的theme和launchMode。在注释2处获取APK文件的描述类LoadedApk。在注释3处获取要启动的Activity的ComponentName类,在ComponentName 类中保存了该Activity的包名和类名。注释4处用来创建要启动Activity的上下文环境。注释5处根据ComponentName中存储的Activity类名,用类加载器来创建该Activity的实例。注释6处用来创建Application,makeApplication 方法内部会调用Application的onCreate方法。注释7处调用Activity的attach方法初始化Activity,在attach方法中会创建Window对象(PhoneWindow)并与Activity自身进行关联。在注释8处调用Instrumentation的callActivityOnCreate方法来启动Activity,如下所示:

image.png 注释1处调用了Activity的performCreate方法,代码如下所示:

image.png 在performCreate方法中会调用Activity的onCreate方法,讲到这里,根Activity就启动了,即应用程序就启动了。

3.1.4 根Activity启动过程中涉及的进程

根Activity启动过程中会涉及4个进程,分别是Zygote进程、Launcher进程、AMS所在进程(SystemServer进程)、应用程序进程。它们之间的关系如图所示。

image.png 首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程,柔则AMS启动根Activity。

这4个进程调用的时序图,如图所示。 image.png

4. 复制攻略

4.1 《Android开发艺术探索》

4.2 《Andriod进阶解密》