Android Activity

Activity

基本概念:Activity(活动)是Android四大组件之一,用于显示用户界面,和用户进行交互。
生命周期:

1
2
3
4
5
Activity 的状态:
1. 运行状态:当其位于栈顶时。
2. 暂停状态:当其不再处于栈顶,但仍然可见。(比如对话框形式的 Activity 并不会完全遮挡住下面的 Activity)
3. 停止状态:当其不再处于栈顶,并且完全不可见。
4. 销毁状态:当其从返回栈中移除。

正常情况下的生命周期

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

onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()

方法 解释
onCreate() 正在被创建。当 activity 启动时,做一些必要的初始化工作。
onStart() 正在被启动,即将开始。状态为可见,但位于后台。
onResume() 进入运行状态,与用户进行交互。并状态为可见,并位于前台。
onPause() 表示 activity 正在停止,此时可以做一些存储数据,停止动画等工作,但不能太耗时,因为这会影响到新 activity 的显示,onPause 必须先执行完,新的 activity 的 onResume 才会执行。状态为可见,但位于后台。
onStop() 即将停止。此时应该释放不再需要的资源。状态为不可见,并位于后台。
onRestart() 表示 activity 正在重新启动 ,一般情况下,当前 activity 从完全不可见重新变成可见状态时,会被调用,这种情形一般是用户行为所导致的,比如用户按 HOME 键切换到桌面然后重新打开 APP 或者按 back 键返回此 activity。
onDestory() 即将被销毁。可做最后的回收和资源释放的工作,避免内存泄漏。

流程:

  • 启动 activity:onCreate()->onStart()->onResume(),activity进入运行状态。
  • activity被其他activity覆盖(DialogActivity,但未完全覆盖),系统会调用onPause()方法,暂停activity的运行。(如果新 Activity 采用透明主题,则不会回调 onStop)
  • 当activity由被覆盖状态回到前台,系统会调用onResume()方法,activity再次进入运行状态。
  • 当activity跳转到新的activity界面并完全覆盖此activity,或按home键回到主屏,自身退居后台:则执行onPause()->onStop(),进入停滞状态。
  • 当activity由后台回到前台,再次回到原 Activity:则执行onRestart()->onStart()->onResume(),重新进入运行状态。(注意只是生命周期方法一样,不代表所有过程都一样)
  • 当activity已被系统杀死,而后用户回退到此activity界面:则重新执行onCreate()->onStart()->onResume(),进入运行状态。
  • 按 back 键回退时:则执行onPause()->onStop()->onDestory(),结束当前activity运行状态。

注:

  • 锁屏:onPause()->onStop(),解锁屏幕:onStart()->onResume()。
  • 旧activity先执行onPause()方法,然后新activity再执行onResume()方法。
  • 关于activity是否执行onPause()方法,只有启动dialog风格的activity时才会调用。当是dialog弹框时(AlertDialog),并不会执行onPause()方法。
  • activity从是否可见来讲,onStart()和onStop()是相对的。从是否在前台来讲,onResume()和onPause()是相对的。也就是说,onStart()可见但位于后台,onResume()可见并位于前台,onPause()可见但位于后台,onStop()不可见并位于后台。

Activity 的启动过程:

  • 启动 Activity 的请求会由 Instrumentation 来处理
  • 然后它通过 Binder 向 AMS(ActivityManagerService) 发请求
  • AMS 内部维护着一个 ActivityStack 并负责栈内的 Activity 的状态同步
  • AMS 通过 ActivityThread 去同步 Activity 的状态从而完成生命周期方法的调用

旧 Activity 先执行 onPause 方法,新 Activity 再执行 onResume 方法。
在 ActivityStack 中的 resumeTopActivityInnerLocked 方法中,栈顶的 Activity 需先 onPause 后,新 Activity 才能启动。
最终,在 ActivityStackSupervisor 中的 realStartActivityLocked 方法会调用 app.thread.scheduleLaunchActivity()。
app.thread 的类型是 IApplication(它的具体实现是 ActivityThread 中的 ApplicationThread),所以,实际上调到了 ApplicationThread 的 scheduleLaunchActivity 方法,这个方法会调用 handleLaunchActivity 方法完成新 Activity 的启动三回调。

异常情况下的生命周期

指 Activity 被系统回收或者由于当前设备的 Configuration 发生改变从而导致 Activity 被销毁重建。
Configuration 类专门用来描述 Android 设备的配置情况:

1
2
3
4
5
6
7
8
9
10
11
// 获取实例对象
Configuration config = getResources().getConfiguration();
// config.fontScale:字体的缩放因子
// config.keyboard:键盘类型
// config.mcc:国家码
// config.mnc:网络码
// config.locale:语言
// config.navigation:导航类型
// config.orientation:屏幕方向
// config.touchscreen:触摸方式
// config.keyboardHidden:键盘可用

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

当系统资源配置发生改变时,比如切换横竖屏,则Activity 会被销毁(默认情况下)并重新创建,同时由于是在异常情况下终止的,系统会调用 onSaveInstanceState(调用时机在 onStop 之前,和 onPause 没有既定的时序关系) 来保存当前 Activity 的状态。

当 Activity 被重新创建后,系统会调用 onRestoreInstanceState(调用时机在 onStart 之后),并把 Activity 销毁时 onSaveInstanceState 方法所保存的 Bundle 对象作为参数同时传递给 onRestoreInstanceState(调用就是有值的) 和 onCreate(注意 Bundle 的非空判断)方法。
并且,在这两个方法中,系统会自动做一定的恢复工作。和 Activity 一样,每个 View 都有这两个方法。
注意,系统只在 Activity 异常终止时才会调用它们,其他情况不会触发这个过程。但是,按 Home 键或者启动新 Activity 仍然会单独触发 onSaveInstanceState 的调用。

关于保存和恢复 View 层次结构,系统的工作流程:
一种典型的委托思想,上层委托下层,父容器委托子元素。
数据的恢复过程也是类似的。

  • 当 Activity 被意外终止时,它会调用 onSaveInstanceState 保存数据。
  • 然后 Activity 会委托 Window 保存数据。
  • Window 再委托它上面的顶级容器保存数据。
  • 顶级容器是一个 ViewGroup,一般来说可能是 DecorView。
  • 最后顶级容器再去一一通知它的子元素来保存数据。

当系统配置发生改变后,如果不想重新创建 Activity:
可在 AndroidMenifest.xml 中,给 Activity 指定 configChanges 属性。比如 android : configChanges = “orientation|vertical”。常用的值有:

  • locale。设备本地位置发生改变,一般指切换了系统语言。
  • orientation。屏幕方向发生改变,比如旋转了屏幕。
    当 minSdkVersion 和 targetSdkVersion 有一个不低于 13。为了防止旋转屏幕时 Activity 重启,还要加上 screenSize。
  • keyboardHidden。键盘的可访问性发生改变,比如用户调出了键盘。
1
2
3
4
5
6
7
8
/**
* 会调用这个方法
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e("TAG","onConfigurationChanged,newOrientation:"+newConfig.orientation);
}

情况 2:资源内存不足导致低优先级的 Activity 被杀死
其数据存储和恢复过程和情况 1 完全一致。

Activity 的优先级,从高到低:

  • 前台 Activity。正在和用户交互的 Activity。
  • 可见但非前台 Activity。比如 activity 中弹出了一个对话框,导致 activity 可见但是位于后台无法和用户直接交互。
  • 后台 Activity。已经被暂停的 Activity,比如执行了 onStop。

注意:一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是放入 Service 中从而保证进程有一定的优先级。


Activity与Fragment生命周期关系

创建过程 销毁过程
Fragment–onAttach() Fragment–onPause()
Fragment–onCreate() Activity–onPause()
Fragment–onCreateView() Fragment–onStop()
Activity–onCreate() Activity–onStop()
Fragment–onActivityCreated Fragment–onDestoryView()
Activity–onStart() Fragment–onDestory
Fragment–onStart() Fragment–onDetach()
Activity–onResume() Activity–onDestory()
Fragment–onResume()

Activity与menu创建先后顺序

在activity创建完回调onResume后创建menu,回调onCreateOptionsMenu。

Activity的启动模式

LaunchMode

用于设定 Activity 的启动方式,无论是哪种启动方式,所启动的 Activity 都会位于 Activity 栈的栈顶。

首先了解,任务栈是一种”后进先出”的栈结构,当栈中无任何 Activity 时,系统会回收这个任务栈。
任务栈分为前台任务栈和后台任务栈(此栈中的 Activity 位于暂停状态),用户可通过切换将后台任务栈再次调到前台。

Low Memory Killer:App 的启动和销毁

  • App 的启动销毁管理机制:Activity 栈,先进先出,保护机制(当内存不足时,先入栈的先杀掉。) -> LRU Cache(缓存淘汰机制)

启动模式要解决的问题:

  • 默认情况下,当我们每次启动一个activity时,系统都会创建一个新的实例,并放入任务栈中,位于栈顶。
  • 当按back键返回时,基于后进先出的原则,位于栈顶的activity会被移除,重复下去,当任务栈为空时,会被系统回收。
  • 这种情况下,在使用时,系统会大量的并重复的创建实例,而当要退出应用时,需多次按返回,只有任务栈被销毁时,程序才会退出。所以需要不同的启动模式,来面对不同的情况。

四种启动模式:standard,singleTop,singleTask,singleInstance,

  • standard模式:标准模式。默认的启动模式,每次启动一个activity,都会创建一个新的实例,activity默认会进入启动它的activity所属的任务栈中(taskId值相同,hashcode值不同)。当我们不指定启动模式时,系统也会默认使用该模式。

    注:在非activity类型的context(如ApplicationContext)并没有所谓的任务栈,所以不能通过ApplicationContext去启动standard模式的activity,用它启动 standard 模式的 Activity 时,可为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记。

  • singleTop模式:栈顶复用模式。如果activity位于任务栈的栈顶,则不会被重新创建,而是复用栈顶的实例,并将Intent对象传入,同时回调它的onNewIntent方法。如果不在栈顶或不存在,则行为同standard模式。

    注:执行onNewIntent()—->onResart()——>onStart()—–>onResume()。

  • singleTask模式:栈内复用模式。一种单实例模式,如果栈内存在此activity(根据taskAffinity匹配),则会清除之上的activity,复用此activity,并调用onNewIntent()方法。(onPause–>onNewIntent–>onResume)
    当启动此模式 Activity 时,系统会先寻找是否存在它想要的任务栈:

  • 如不存在,就重新创建一个任务栈,然后创建它的实例后把它放到栈中。

  • 如存在它需要的任务栈,并且栈中有它的实例存在,那么系统会把它调到栈顶(同时,默认具有 clearTop 效果)并调用它的 onNewIntent 方法,如果实例不存在,就创建它的实例并把它压入栈中。

  • singleInstance模式:单实例模式。除了具有 singleTask 模式的所有特性外,此模式的 Activity 只能单独位于一个任务栈中,当它启动时,系统会创建一个新的任务栈给它。由于站内复用特性,后续的请求均不会创建新的activity实例。

为 Activity 指定启动模式

  • 第一种方式:在 AndroidMenifest 中为 Activity 指定 launchMode 属性。
    <activity android:name=".standard.StandardActivity" android:launchMode="standard|singleTop|singleTask|singleInstance" >
  • 第二种方式:动态代码,通过 Intent 设置标志位。(intent.addFlags())
  • 两种方式的区别:
  • 第二种方式优先于第一种方式。
  • 限定范围不同。

任务相关性:TaskAffinity 参数

通过设置不同的启动模式可以实现调配不同的 Task。但是 taskAffinity 在一定程度上也会影响任务栈的调配流程。此参数标识了一个 Activity 所需要的任务栈的名字,这个属性值不能和包名相同(每一个 Activity 都有一个 Affinity 属性,如果不在清单文件中指定,默认情况下,所需任务栈的名字为包名,比如初始的MainActivity,它的Task的名字就是包名)。在默认情况下,同一应用程序的所有的 Activity 都有着相同的 taskAffinity。Android 使用任务(task)来管理 Activity,一个任务就是一组存放在栈里的 Activity 的集合,这个栈也被称作返回栈(back task)。栈是一种后进先出的数据结构。

通过命令:adb shell dumpsys activity activities 可将系统中所有存活中的 Activity 信息打印到控制台(其中 TaskRecord 代表一个任务栈)。

TaskAffinity 的值为字符串,且中间必须含有包名分隔符”.”。可以在 AndroidManifest.xml 中通过给 activity 指定 TaskAffinity 属性来指定任务栈。指定方式:
<activity android:name=".ActivitySingleTop" android:launchMode="singleTop" android:taskAffinity="com.castiel.demo.singletop"/>

TaskAffinity 属性主要和 singleTask、singleInstance 模式或者 allowTaskReparenting 属性配对使用。不对 standard 和 singleTop 模式有任何影响,即使指定了该属性为其他不同的值,这两种启动模式下不会创建新的 task,它们都被创建在一个任务栈中。taskAffinity 在下面两种情况时会产生效果:

1、taskAffinity 与 FLAG_ACTIVITY_NEW_TASK 或者 singleTask 模式配合使用时

它具有该模式 Activity 目前任务栈的名字,待启动 Activity 会运行在名字和 TaskAffinity 相同的任务栈中。(如果新启动 Activity 的 taskAffinity 和 栈 的 taskAffinity 相同则加入到该栈中,如果不同,就会创建新栈。)

2、taskAffinity 与 allowTaskReparenting 配合时

例子一:如果 allowTaskReparenting 属性为 true,A 应用启动 B 应用的一个 Activity C,C 会从 A 的任务栈转移到 B 的任务栈中,也就是说明 Activity 具有转移的能力。

比如使用一款社交应用,这个应用的联系人详情界面提供了联系人的邮箱,当点击邮箱时会跳到发送邮件的界面。会把发送邮件界面放到社交应用中详情界面所在栈的栈顶。
当社交应用启动了发送邮件的 Activity,此时发送邮件的 Activity 是和社交应用处于同一个栈中的,并且这个栈位于前台。如果发送邮件的 Activity 的 allowTaskReparenting 设置为 true,此后 E-mail 应用所在的栈位于前台时,发送邮件的 Activity 会由社交应用的栈中转移到与它更亲近的邮件应用(taskAffinity 相同)所在的栈中。

例子二:allowTaskReparenting 赋予 Activity 在各个 Task 中间转移的特性。一个在后台任务栈中的 Activity A,当有其他任务进入前台,并且 taskAffinity 与 A 相同,则会自动将 A 添加到当前启动的任务栈中。举一个生活中的场景:

在某外卖 App 中下好订单后,跳转到支付宝进行支付。当在支付宝中支付成功之后,页面停留在支付宝支付成功页面。
按 Home 键,在主页面重新打开支付宝,页面上显示的并不是支付宝主页面,而是之前的支付成功页面。
再次进入外卖 App,可以发现支付宝成功页面已经消失。

例子三:假设有两个项目

  • 项目一:其中有 3 个 Activity:FirstA、FirstB、FirstC。打开顺序依次是 FirstA -> FirstB -> FirstC。其中 FirstC 的 taskAffinity 为”lagou.affinity“,且 allowTaskReparenting 属性设置为true。FirstA 和 FirstB 为默认值;
  • 项目二:其只有一个 Activity–ReparentActivity,并且其 TaskAffinity 也等于”lagou.affinity“。
  • 将这两个项目分别安装到手机上之后,打开项目一,并从 FirstA 开始跳转到 FirstB,再进入 FirstC 页面。然后按 Home 键,使其进入后台任务。此时系统中的 Activity 信息:
    • TaskRecord -> FirstC、FirstB、FirstA。
  • 接下来,打开项目二,屏幕上本应显示 ReparentActivity 的页面内容,但是实际上显示的却是 FirstC 中的页面内容,并且系统中 Activity 信息如下:
    • TaskRecord -> FirstC、ReparentActivity。
    • TaskRecord -> FirstB、FirstA。
  • 可以看出,FirstC 被移动到与 ReparentActivity 处在一个任务栈中。此时 FirstC 位于栈顶位置,再次点击返回键,才会显示 ReparentActivity 页面。

接下来通过系统源码来查看 taskAffinity 的应用(基于 Android 8.0 版本)。ActivityStackSupervisor 的 findTaskLocked 方法用于找到 Activity 最匹配的栈,最终会调用 ActivityStack 的 findTaskLocked 方法,这里只截取了和 taskAffinity 相关的部分:ActivityStack.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void findTaskLocked(ActivityRecord target, FindTaskResult result) {
...
// 遍历 mTaskHistory 列表,列表的元素为 TaskRecord,它用于存储没有被销毁的栈。
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
// 得到某一个栈的信息。
final TaskRecord task = mTaskHistory.get(taskNdx);
...
else if (!isDocument && !taskIsDocument
&& result.r == null && task.rootAffinity != null) {
// 将栈的 rootAffinity(初始的 taskAffinity)和目标 Activity 的 taskAffinity 做对比,如果相同,则将 FindTaskResult 的 matchedByRootAffinity 属性设置为 true,说明找到了匹配的栈。
if (task.rootAffinity.equals(target.taskAffinity)) {
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity candidate!");

result.r = r;
result.matchedByRootAffinity = true;
}
} else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
}
}

Intent 的 Flags

在 Intent 中定义了很多 FLAG,其中有几个 FLAG 也可以设定 Activity 的启动方式,如果 Launch Mode 和 FLAG 设定的 Activity 的启动方式有冲突,则以 FLAG 设定的为准。

所以,Flags 并不能简单地等同于启动模式,它的作用也很多,比如可以影响 Activity 的运行状态。一般不需要指定标记位,理解即可。几个常见的标记位如下:

  • FLAG_ACTIVITY_NEW_TASK:和 Launch Mode 中的 singleTask 效果是一样的。
  • FLAG_ACTIVITY_SINGLE_TOP:和 Launch Mode 中的 singleTop 效果是一样的。
  • FLAG_ACTIVITY_CLEAR_TOP。在 Launch Mode 中没有与此对应的模式,如果要启动的 Activity 已经存在与栈中,则将所有位于它上面的 Activity 出栈。singleTask 默认具有此标记位的效果。

还有一些 FLAG 对分析栈管理有些帮助:

  • FLAG_ACTIVITY_NO_HISTORY:Activity 一旦退出,就不会存在于栈中。同样地,也可以在 AndroidManifest 文件中设置 android:noHistory。
  • FLAG_ACTIVITY_MULTIPLE_TASK:需要和 FLAG_ACTIVITY_NEW_TASK 一同使用才有效果,系统会启动一个新的栈来容纳新启动的 Activity。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:Activity 不会被放入到 “最近启动的 Activity” 列表中。也就是说此 Activity 不会出现在历史 Activity 列表中。
  • FLAG_ACTIVITY_BROUGHT_TO_FROM:这个标志位通常不是由应用程序中的代码设置的,而是 Launch Mode 为 singleTask 时,由系统自动加上的。
  • FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY:这个标志位通常不是由应用程序中的代码设置的,而是从历史列表中启动的(长按 Home 键调出)。
  • FLAG_ACTIVITY_CLEAR_TASK:需要和 FLAG_ACTIVITY_NEW_TASK 一同使用才有效果,用于清除与启动的 Activity 相关栈的所有其他 Activity。

接下来通过系统源码来查看 FLAG 的应用(基于 Android 8.0 版本)。首先,根 Activity 启动时会调用 AMS 的 startActivity 方法,经过层层调用会调用 ActivityStarter 的 startActivityUnchecked 方法:ActivityStarter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
// 初始化启动 Activity 的各种配置,在初始化前会重置各种配置再进行配置,这些配置包括 ActivityRecord、Intent、TaskRecord 和 LaunchFlags(Activity 启动的 FlAG)等。
setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,voiceInteractor);
// 计算出 Activity 启动的 FLAG,并将计算的值赋值给 mLaunchFlags。
computeLaunchingTaskFlags();
computeSourceStack();
// 将计算出的 mLaunchFlags 设置给 Intent,达到设定 Activity 的启动方式的目的。
mIntent.setFlags(mLaunchFlags);
...
}

接下来查看 computeLaunchingTaskFlags 方法,,计算启动的 FLAG 的逻辑比较复杂,这里只截取了一小部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void computeLaunchingTaskFlags() {
...
// 当 TaskRecord 类型的 mInTask 为 null 时,说明 Activity 要加入的栈不存在,因此,这一小段代码主要解决的问题就是 Activity 要加入的栈不存在时如何计算出启动的 FLAG。
if (mInTask == null) {
// ActivityRecord 类型的 mSourceRecord 用于描述 “初始 Activity”。
// 初始 Activity 是指:假设 ActivityA 启动了 ActivityB,那么 ActivityA 就是初始 Activity。
if (mSourceRecord == null) {
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
// 创建一个新栈
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
// 如果 “初始 Activity” 所在的栈只允许有一个 Activity 实例,则也需要创建一个新栈。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (mLaunchSingleInstance || mLaunchSingleTask) {
// 如果 Launch Mode 设置了 singleTask 或 singleInstance,则也要创建一个新栈。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
}

IntentFiler 的匹配规则

启动 Activity 分为两种:

  • 显式调用
    需明确指定被启动对象的组件信息,包括包名和类名。优先级更高。
  • 隐式调用
    不需要明确指定组件信息。需要 Intent 能匹配目标组件的 IntentFilter(可以有多个,匹配其一即可) 中所设置的过滤信息。
    过滤信息有 action、category、data,它们都可以有多个,同时匹配这三个类别才能启动目标 Activity。
    注意,不含有 DEFAULT 这个 category 的 Activity 是无法接收隐式 Intent 的。
    可通过 PackageManager 的 resolveActivity,queryIntentActivities(这两方法的标记位使用 MATCH_DEFAULT_ONLY,仅匹配声明了 DEFAULT 这个 category 的 Activity),或者是 Intent 的 resolveActivity 。来判断是否 null,避免 startActivity 失败。

各属性匹配规则

  • action
    要求 Intent 中的 action 必须有一个且必须和过滤规则中的其中一个 action 相同,并且区分大小写。
  • category
    可以不存在(会默认加上标记位),但如果有,每一个都要能够和过滤规则中一个 actegory 相同。
  • data
    如果过滤规则中定义了 data,那么 Intent 中也必须定义可匹配的 data。如果要指定完整的data,必须调用 setDataAndType(Uri.prase(file://abc),”text/plain”) 方法。
    它由两部分组成:
  • mimeTyp。指媒体类型
  • URI。默认值为 content 和 file。

后台启动 Activity

从 Android10(API 29)开始,Android 系统对后台进程启动 Activity 做了一定的限制。主要目的就是尽可能的避免当前前台用户的交互被打断,保证当前屏幕上展示的内容不受影响。解决办法:Android 官方建议我们使用通知来替代直接启动 Activity 操作:也就是当后台执行的任务执行完毕之后,并不会直接调用 startActivity 来启动新的界面,而是通过 NotificationManager 来发送 Notification 到状态栏。这样既不会影响当前使用的交互操作,用户也能及时获取后台任务的进展情况,后续的操作由用户自己决定。

示例(Kotlin)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 第一行代码第三版
*/
class MainActivity : BaseActivity(), View.OnClickListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

initView()
}

private fun initView() {
// 'kotlin-android-extensions' 插件的作用,不必再 findViewById()。
// 这个插件会根据布局文件中定义的控件 id 自动生成一个具有相同名称的变量。
// android:textAllCaps="false" 取消默认的字母全部大写
btn_main.setOnClickListener {

// 这里使用了 Kotlin 的语法糖,实际上调用的是 editText.getText()
// 不必特意记忆,直接输入 getText() 后,Android Studio 会自动给提示转换。
// val inputext = editText.text.toString()
// Toast.makeText(this,inputext,Toast.LENGTH_SHORT).show()

// 界面跳转,显式 Intent
val data = "Hello"
val intent = Intent(this, Main2Activity::class.java)
// 传递参数
intent.putExtra("extra_data",data)
// startActivity(intent)
// 带返回值的跳转
startActivityForResult(intent,1)

// 隐式启动
// 只有<action>和<category>同时匹配才行
// 但这里的<category>因为设置的为DEFAULT,所以只需匹配<action>即可,<category>会自动添加到Intent中。
// val intent = Intent("com.example.activitytest.ACTION_START")
// intent.addCategory("com.example.activitytest.MY_CATEGORY")
// startActivity(intent)

// 更多隐式 Intent 的用法
// val intent = Intent(Intent.ACTION_VIEW)
// // 通过 Uri.parse() 将网址字符串解析成一个 Uri 对象,再调用 Intent 的 setData() 将这个 Uri 对象传递进去。
// intent.data = Uri.parse("https://www.baidu.com")
// startActivity(intent)

// val intent = Intent(Intent.ACTION_DIAL)
// intent.data = Uri.parse("tel:1008611")
// startActivity(intent)
}

initListener()
}

private fun initListener() {
btn.setOnClickListener(this)
}

override fun onClick(v: View?) {
// 不要忘记添加监听事件
when(v?.id){
R.id.btn -> {
// Main2Activity.actionStart(this,data)
Main2Activity.actionStart(this)
}
}
}

/**
* requestCode:启动 Activity 时传入的请求码
* resultCode:返回数据时传入的处理结果
* data:携带着返回数据的 Intent
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// 判断数据来源
when(requestCode){
// 判断处理结果是否成功
1 -> if (resultCode == Activity.RESULT_OK){
val returnedData = data?.getStringExtra("data_return")
Log.d("TAG_MainActivity","return data is $returnedData")
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Main2Activity : BaseActivity(), View.OnClickListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)

initView()
initData()
}

private fun initData() {
// intent 实际上调用的是父类的 getIntent()
val extraData = intent.getStringExtra("extra_data")
Log.d("TAG_Main2Activity","extra data is $extraData")
}

private fun initView() {
btn_main2.setOnClickListener(this)
}

override fun onClick(v: View?) {
when (v?.id){
R.id.btn_main2 ->{
val intent = Intent()
intent.putExtra("data_return","Hello MainActivity")
setResult(Activity.RESULT_OK,intent)
finish()
}
}
}

/**
* 启动 Activity 的最佳写法
* companion object 语法结构:
* Kotlin 规定,所有定义在此结构中的方法都可以使用类似于 Java 静态方法的形式调用。
*/
companion object{
fun actionStart(context: Context,data1:String,data2:String){
// val intent = Intent(context,Main2Activity::class.java)
// intent.putExtra("param1",data1)
// intent.putExtra("param2",data2)
// context.startActivity(intent)

val intent = Intent(context, Main2Activity::class.java).apply {
putExtra("param1",data1)
putExtra("param2",data2)
}
context.startActivity(intent)

}
}

override fun onBackPressed() {
val intent = Intent()
intent.putExtra("data_return","Hello MainActivity")
setResult(Activity.RESULT_OK,intent)
finish()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Main3Activity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main3)

btn_main3.setOnClickListener{

// 退出程序
ActivityCollector.finishAll()
// 还可以在销毁所以 Activity 的代码后面再加上杀掉当前进程的代码,以保证程序完全退出
// killProcess() 用于杀掉一个进程,它接收一个进程 id 参数,并且它只能用于杀掉当前进程,不能用于杀掉其它进程。
// myPid() 获得当前进程的进程 id。
android.os.Process.killProcess(android.os.Process.myPid())
}
}
}

System.exit(0) 、onDestory()、Activity.finish()三者的区别

  • System.exit(0) 是正常结束程序,kill 掉当前进程,针对的是整个Application。
  • onDestory() 方法是 Activity 生命周期的最后一步,资源空间等就被回收了。当重新进入此 Activity 的时候,必须重新创建,执行 onCreate() 方法。
  • Activity.finish() 当调用此方法的时候,系统只是将最上面的Activity移出了栈,并没有及时的调用onDestory() 方法,也就是占用的资源没有被及时释放。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 随时随地退出程序
* 单例类
*/
object ActivityCollector{

private val activities = ArrayList<Activity>()

fun addActivity(activity:Activity){
activities.add(activity)
}

fun removeActivity(activity: Activity){
activities.remove(activity)
}

fun finishAll(){
for (activity in activities){
if (!activity.isFinishing){
activity.finish()
}
}
activities.clear()
}
}
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- theme 对话框形式主题 -->
<activity
android:name=".activity.Main3Activity"
android:theme="@style/Theme.AppCompat.Dialog">
<intent-filter tools:ignore="AppLinkUrlError">

<!-- tools:ignore="AppLinkUrlError" 忽略警告 -->
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />

</intent-filter>
</activity>
<activity android:name=".activity.Main2Activity">
<intent-filter>

<!-- 每个 Intent 中只能指定一个 action,但能指定多个 category。 -->
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.activitytest.MY_CATEGORY" />

</intent-filter>
</activity>
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

备注

参考资料:
Android 开发艺术探索