# 四大组件之 Activity ## 什么是 Activity? Activity 是 Android 的四大组件之一,是用户操作的可视化界面,它为用户提供了一个完成操作指令的窗口。 当我们创建完 Activity 之后,需要调用 `setContentView(view)` 方法来完成界面的显示,以此来为用户提供交互的入口。在 Android App 中只要能看见的几乎都要依托于 Activity,所以 Activity 是在开发中使用最频繁的一种组件。 ## 生命周期 生命周期就是 Activity 从开始到结束所经历的各个状态,从一个状态到另一个状态的转变,从无到有再到无,这样一个过程中所经历的状态就叫做生命周期。 Acitivity 本质上有四种状态: - 运行:如果一个活动被移到了前台(活动栈顶部)。 - 暂停:如果一个活动被另一个非全屏的活动所覆盖(比如一个 Dialog),那么该活动就失去了焦点,它将会暂停(但它仍然保留所有的状态和成员信息,并且仍然是依附在 WindowsManager 上),在系统内存积极缺乏的时候会将它杀死。 - 停止:如果一个活动被另一个全屏活动完全覆盖,那么该活动处于停止状态(状态和成员信息会保留,但是 Activity 已经不再依附于 WindowManager 了)。同时,在系统缺乏资源的时候会将它杀死(它会比暂停状态的活动先杀死)。 - 重启:如果一个活动在处于停止或者暂停的状态下,系统内存缺乏时会将其结束(finish)或者杀死(kill)。这种非正常情况下,系统在杀死或者结束之前会调用 `onSaveInstanceState()` 方法来保存信息,同时,当 Activity 被移动到前台时,重新启动该Activity并调用 `onRestoreInstanceState()` 方法加载保留的信息,以保持原有的状态。 在上面的四中常有的状态之间,还有着其他的生命周期来作为不同状态之间的过度,用于在不同的状态之间进行转换。 ![Activity 生命周期](https://raw.githubusercontent.com/jeanboydev/Android-ReadTheFuckingSourceCode/master/resources/images/android/basic/01_activity/activity_lifecycle.png) 正常情况下的生命周期: - onCreate():与 onDestroy() 配对,表示 Activity 正在被创建,这是生命周期的第一个方法。 在这个方法中可以做一些初始化的工作(加载布局资源、初始化 Activity 所需要的数据等),耗时的工作在异步线程上完成。 - onRestart():表示 Activity 正在重新启动。 一般情况下,在当前 Activity 从不可见重新变为可见的状态时 onRestart() 就会被调用。这种情形一般是由于用户的行为所导致的,比如用户按下 Home 键切换到桌面或者打开了一个新的 Activity(这时当前 Activity 会暂停,也就是 onPause() 和 onStop() 被执行),接着用户有回到了这个 Activity,就会出现这种情况。 - onStart():与 onStop() 配对,表示 Activity 正在被启动,并且即将开始。 但是这个时候要注意它与 onResume() 的区别。两者都表示 Activity 可见,但是 onStart() 时 Activity 还正在加载其他内容,正在向我们展示,用户还无法看到,即无法交互。 - onResume():与 onPause() 配对,表示 Activity 已经创建完成,并且可以开始活动了,这个时候用户已经可以看到界面了,并且即将与用户交互(完成该周期之后便可以响应用户的交互事件了)。 - onPause():与 onResume() 配对,表示 Activity 正在暂停,正常情况下,onStop() 接着就会被调用。 在特殊情况下,如果这个时候用户快速地再回到当前的 Activity,那么 onResume() 会被调用(极端情况)。一般来说,在这个生命周期状态下,可以做一些存储数据、停止动画的工作,但是不能太耗时,如果是由于启动新的 Activity 而唤醒的该状态,那会影响到新 Activity 的显示,原因是 onPause() 必须执行完,新的 Activity的 onResume() 才会执行。 - onStop():与 onStart() 配对,表示 Activity 即将停止,可以做一些稍微重量级的回收工作,同样也不能太耗时(可以比 onPause 稍微好一点)。 - onDestroy():与 onCreate() 配对,表示 Activity 即将被销毁,这是 Activity 生命周期的最后一个回调,我们可以做一些回收工作和最终的资源释放(如 Service、BroadReceiver、Map 等)。 ## 启动模式 Activity 的启动模式有4种,分别是 Standard、SingleTop、SingleTask、SingleInstance。可以在 `AndroidMainifest.xml` 文件中指定每一个 Activity 的启动模式。 一个 Android 应用一般都会有多个 Activity,系统会通过任务栈来管理这些 Activity,栈是一种后进先出的集合,当前的 Activity 就在栈顶,按返回键,栈顶 Activity 就会退出。Activity 启动模式不同,系统通过任务栈管理 Activity 的方式也会不同,以下将分别介绍。 - Standard Standard 模式是 Android 的默认启动模式,你不在配置文件中做任何设置,那么这个 Activity 就是 Standard 模式。这种模式下,Activity 可以有多个实例,每次启动 Activity,无论任务栈中是否已经有这个 Activity 的实例,系统都会创建一个新的 Activity 实例。 - SingleTop SingleTop 模式和 Standard 模式非常相似,主要区别就是当一个 SingleTop 模式的 Activity 已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例。如果不位于栈顶,就会创建新的实例。 - SingleTask SingleTask 模式的 Activity 在同一个 Task 内只有一个实例。如果 Activity 已经位于栈顶,系统不会创建新的 Activity 实例,和 SingleTop 模式一样。但 Activity 已经存在但不位于栈顶时,系统就会把该 Activity 移到栈顶,并把它上面的 Activity 出栈。 - SingleInstance SingleInstance 模式也是单例的,但和 SingleTask 不同,SingleTask 只是任务栈内单例,系统里是可以有多个 SingleTask Activity 实例的,而 SingleInstance Activity 在整个系统里只有一个实例,启动一个SingleInstance 的 Activity 时,系统会创建一个新的任务栈,并且这个任务栈只有他一个 Activity。 SingleInstance 模式并不常用,如果我们把一个 Activity 设置为 SingleInstance 模式,你会发现它启动时会慢一些,切换效果不好,影响用户体验。它往往用于多个应用之间,例如一个电视 Launcher 里的 Activity,通过遥控器某个键在任何情况可以启动,这个 Activity 就可以设置为 SingleInstance 模式,当在某应用中按键启动这个 Activity,处理完后按返回键,就会回到之前启动它的应用,不影响用户体验。 ## 任务与返回栈 一个应用程序当中通常会包含多个 Activity,每个 Activity 都应该设计成可以执行用户特定的操作,并且能够启动其他 Activity。比如,电子邮件应用可能有一个 Activity 显示新邮件列表。用户选择某个邮件时,会打开一个新的 Activity 来查看该邮件。 一个 Activity 甚至可以启动设备上其他应用中的 Activity。比如,如果当前应用想要发送电子邮件,就可以发送一个 Intent 添加一些数据(如邮箱和邮件内容等)。然后,系统将打开其他应用中已经声明可以处理该 Intent 的 Activity,如果有多个系统则让用户选择要使用的 Activity。即使这两个 Activity 可能来自不同的应用,但是 Android 仍会将 Activity 保留在相同的*任务*中,以维护这种无缝的用户体验。 任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即*返回栈*)中。 设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主” Activity 将作为堆栈中的根 Activity 打开。 当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,并获取焦点。前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按「返回」按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用「返回」按钮退出时弹出堆栈。 因此,返回栈以「后进先出」对象结构运行。 如下图: ![Activity 返回栈](https://raw.githubusercontent.com/jeanboydev/Android-ReadTheFuckingSourceCode/master/resources/images/android/basic/01_activity/diagram_backstack.png) 如果用户继续按「返回」,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。 任务是一个有机整体,当用户开始新任务或通过「主页」按钮转到主屏幕时,可以移动到「后台」。 尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如下图所示。然后,任务可以返回到「前台」,用户就能够回到离开时的状态。 ![Activity 多任务栈](https://raw.githubusercontent.com/jeanboydev/Android-ReadTheFuckingSourceCode/master/resources/images/android/basic/01_activity/diagram_multitasking.png) 例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。 用户先按「主页」按钮,然后从应用启动器启动新应用。 显示主屏幕时,任务 A 进入后台。新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务 B)。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。 此时,用户还可以通过转到主屏幕并选择启动该任务的应用图标(或者,通过从「屏幕预览」择该应用的任务)切换回任务 B。这就是 Android 系统中的多任务的场景。 无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按「返回」按钮始终会转到前一个 Activity。 但是,如果启动指定 `singleTask` 启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity,如下图: ![Activity SingleTask 任务栈](https://raw.githubusercontent.com/jeanboydev/Android-ReadTheFuckingSourceCode/master/resources/images/android/basic/01_activity/diagram_backstack_singletask_multiactivity.png) ## 保存与恢复 Activity 为我们提供了两个回调方法 onSaveInstanceState() 和 onRestoreInstanceState() 用于当 Activity 在不是用户主动意识关闭的情况下来进行页面数据的保存和恢复。 ![Activity 保存与恢复](https://raw.githubusercontent.com/jeanboydev/Android-ReadTheFuckingSourceCode/master/resources/images/android/basic/01_activity/basic-lifecycle-savestate.png) 那么那些情况下 onSaveInstanceState() 会被调用呢?分别有以下几种情况: - 当用户按下 Home 键 App 处于后台,此时会调用 onSaveInstanceState() 方法。 - 当用户按下电源键时,会调用 onSaveInstanceState() 方法。 - 当 Activity 进行横竖屏切换的时候也会调用 onSaveInstanceState() 方法。 - 从 AActivity 跳转到 BActivity 的时候 AActivity 也会调用 onSaveInstanceState() 方法。 虽然以上四种情况会执行 onSaveInstanceState() 方法 但是并不是都会执行 onRestoreInstanceState() 方法,只有第三种情况会调用 onRestoreInstanceState(),因为当 Activity 横竖屏切换的时候会重新走一遍生命周期,所以 Activity 会被销毁创建,由此会执行 onRestoreInstanceState() 方法。 也就是说 onSaveInstanceState 和 onRestoreInstanceState 并不是一定成双出现的,终于当 Activity 真正的被销毁的时候才会执行 onRestoreInstanceState()。 而其他情况 Activity 只是暂居后台,并没有被销毁,所以系统不会调用 onRestoreInstanceState()。 保存数据: ```java @Override public void onSaveInstanceState(Bundle savedInstanceState) { // 保存数据 savedInstanceState.putString("data", "这是保存的数据"); super.onSaveInstanceState(savedInstanceState); } ``` 恢复数据: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 恢复数据 if (savedInstanceState != null) { String data = savedInstanceState.getInt("data"); } } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 也可以在 onRestoreInstanceState() 方法中恢复数据 if (savedInstanceState != null) { String data = savedInstanceState.getInt("data"); } } ``` ## Intent Intent 分两种,显式 Intent 和隐式 Intent。如果一个 Intent 明确指定了要启动的组件的完整类名,那么这个 Intent 就是显式 Intent,否则就是隐式 Intent。 当我们用一个显式 Intent 去启动组件时,Android 会根据 Intent 对象所提供的 component name 直接找到要启动的组件,当我们用一个隐式的 Intent 去启动组件时,Android 系统就无法直接知道要启动的组件名称了。 ### 显式 Intent ```java Intent intent = new Intent(this, xxx.class); startActivity(intent); ``` ### 隐式 Intent 使用隐式 Intent 之前需要在 AndroidManifest.xml 中对标签增加设置。 ```xml ``` 使用隐式 Intent 跳转 Activity。 ```java Intent intent = new Intent("com.jeanboy.action.TEST"); startActivity(intent); ``` ### Intent Filter 如果 Intent 中的存在 category 那么所有的 category 都必须和 Activity 过滤规则中的 category 相同。才能和这个 Activity 匹配。Intent 中的 category 数量可能少于 Activity 中配置的 category 数量,但是 Intent 中的这 category 必须和 Activity 中配置的 category 相同才能匹配。 ```xml ``` 运行以下代码可以匹配到 IntentActivity: ```java Intent intent = new Intent("com.jeanboy.action.TEST"); intent.addCategory("aaa.bb.cc"); startActivity(intent); ``` 只通过 category 匹配是无法匹配到 IntentActivity 的,因为 category 属性是一个执行 Action 的附加信息。 ### URL Scheme Android 中的 Scheme 是一种页面内跳转协议,是一种非常好的实现机制。通过定义自己的 Scheme 协议,可以非常方便跳转 App 中的各个页面。 使用场景: - 通过小程序,利用 Scheme 协议打开原生 App。 - H5 页面点击锚点,根据锚点具体跳转路径 App 端跳转具体的页面。 - App 端收到服务器端下发的 Push 通知栏消息,根据消息的点击跳转路径跳转相关页面。 - App 根据URL跳转到另外一个 App 指定页面。 - 通过短信息中的 URL 打开原生 App。 Scheme 路径的规则: > \ :// \ : \ [\|\|\] 设置 Scheme 在 AndroidManifest.xml 中对标签增加设置 Scheme。 ```xml ``` 原生调用: ```java Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); ``` 网页调用: ```html 打开 App ``` 在 SchemeActivity 中可以处理 Scheme 跳转的参数: ```java public class SchemeActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri uri = getIntent().getData(); if (uri != null) { //获取指定参数值 String type = uri.getQueryParameter("type"); Log.e("SchemeActivity", "type:" + type); if(type.equals("jeanboy")){ ActivityUtils.startActivity(XXXActivity.class); }else if(type.equals("main")){ ActivityUtils.startActivity(MainActivity.class); } } finish(); } } ``` 如何判断一个 Scheme 是否有效: ```java PackageManager packageManager = getPackageManager(); Uri uri = Uri.parse("aa://bb:1024/from?type=jeanboy"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); List activities = packageManager.queryIntentActivities(intent, 0); boolean isValid = !activities.isEmpty(); if (isValid) { startActivity(intent); } ``` ## startActivityForResult() 如果想在 Activity 中得到新打开 Activity 关闭后返回的数据,需要使用系统提供的 `startActivityForResult()` 方法打开新的 Activity,新的 Activity 关闭后会向前面的 Activity 传回数据。 ```java @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //... int resultCode = 1; startActivityForResult(new Intent(this, OtherActivity.class), resultCode); } /** * 接收 OtherActivity 返回的数据 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { String result = data.getExtras().getString("result"); } ``` 为了得到传回的数据,必须在前面的 Activity 中重写 `onActivityResult()` 方法。 ```java Intent intent = new Intent(); // 把返回数据存入 Intent intent.putExtra("result", "我是返回数据"); // 设置返回数据 setResult(RESULT_OK, intent); // 关闭当前 Activity finish(); ``` ## 常见面试题 - 启动一个 Activity 的生命周期? 例如:A 启动 B,生命周期如下 ```json A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause() B: ==> onCreate() B: ==> onStart() B: ==> onResume() A: ==> onStop() ``` - 下拉通知栏对生命周期的影响? 没有影响! - AlertDialog(对话框)对生命周期的影响? 没有影响! - Toast 对生命周期的影响? 没有影响! - 透明主题的 Activity 对生命周期的影响? ```json A: ==> onCreate() A: ==> onStart() A: ==> onResume() 如果弹出透明 Activity A: ==> onPause() ``` - 屏幕旋转对生命周期的影响? 没有配置 configChanges: ```json A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onPause() A: ==> onSaveInstanceState() A: ==> onStop() A: ==> onDestroy() 屏幕旋转后 A: ==> onCreate() A: ==> onStart() A: ==> onRestoreInstanceState() A: ==> onResume() ``` 配置 configChanges 后: ```json A: ==> onCreate() A: ==> onStart() A: ==> onResume() A: ==> onConfigurationChanged() ``` ## 参考资料 - [Android 官方文档 - Activity 生命周期](https://developer.android.com/guide/components/activities/activity-lifecycle?hl=zh-cn) - [Android 官方文档 - 任务和返回栈](https://developer.android.com/guide/components/activities/tasks-and-back-stack?hl=zh-cn)