该系列文章总纲链接:专题总纲目录 Android Framework 总纲
本章关键点总结 & 说明:
说明:本章节主要解读AMS通过startActivity启动Activity的整个流程的第一阶段:从am启动到ActivityThread启动。
第二阶段文章链接为:
Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)
AMS的startActivity方法目的是确保Activity 的正确启动和正确显示。分析Activity的启动流程方法有很多,我们选择从 adb shell am start 命令开始分析 Activity 的启动流程,那么为什么要选择从这个视角来分析呢?
这是因为这个命令提供了一个标准化和可控的方式来启动 Activity,它模拟了从系统层面发起的启动请求,这种方式可以绕过应用内部的逻辑,直接请求 AMS 进行处理。可以让开发者从系统层面更好地理解 Android 系统的工作原理,以及 AMS 在其中扮演的角色。这种方式有助于揭示 Activity 启动过程中的系统行为,包括进程创建、任务栈管理、Activity 生命周期管理等。
接下来开始我们的分析。
1 从am命令到AMS的startActivty调用
这里我们以启动setting界面为例。在命令行中,我们开始执行:
$adb shell am start -a android.intent.action.MAIN -n com.android.settings/.Settings
这里的命令解释如下:
- adb shell: 启动一个 Android shell 命令行。
- am: 命令行工具,用于与 Activity Manager 服务交互。
- start: 指示 am 工具启动一个新的 Activity。
- -a android.intent.action.MAIN: 指定启动的 Activity 应该处理 MAIN 动作,这是启动 Activity 的常用动作。
- -n com.android.settings/.Settings: 指定包名和 Activity 名称,这里 com.android.settings 是设置应用的包名,Settings 是设置应用的主 Activity。
这个命令会打开设备的设置主界面(注意:不同的设备可能有细微的差别,特别是在定制过的 Android 系统上,Activity 名称或者包名可能有所不同,如果上述命令不起作用,你可能需要查看设备的特定设置应用的包名和 Activity 名称)。
接下来我们看看am命令是如何编译和运行的?
1.1 am命令的编译和运行
am命令对应的Am.java代码,我们先来看Am.java是如何被编译成jar文件,以及如何被使用的。相关文件均在AOSP的frameworks/base/cmds/am目录下。am命令对应的Android.mk文件内容如下所示:
# Copyright 2008 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := am
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := am
LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
这里编译Am.java后生成一个am.jar的文件。这里我们再看am命令,编辑器打开后,如下所示:
#!/system/bin/sh
#
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
#CLASSPATH 用于指定 Java 程序运行时查找类定义的路径。这里,它被设置为
#/system/framework/am.jar,意味着 am.jar 文件包含了执行 am 命令所需的类。
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"
这里详细解读下最后一句脚本的含义:
- exec:这个 shell 内置命令用于执行指定的命令,并且替换当前 shell 进程。
- app_process:这是一个用于启动 Android 应用程序和系统服务的底层 C++ 程序。
- $base/bin:这是 app_process 的路径,指向 /system/bin 目录。
- com.android.commands.am.Am:这是启动的 Java 主类,定义在 am.jar 中。
- "$@":这是传递给脚本的所有参数,它们将被传递给 com.android.commands.am.Am。
总的来说,这个脚本设置了必要的环境变量,并使用 app_process 来启动 am 命令行工具。am 命令行工具实际上是通过 Java 编写的,并且被打包在 am.jar 文件中。这个脚本使得用户可以通过 shell 界面以命令行的方式与 Activity Manager 服务进行交互。
接下来我们来分析 Am.java文件的内容。
1.2 Am.java解读
Am.java 是 Android 系统源代码中的一个文件,当调用 am start时,进入到Am.java的main函数中,代码内容如下:
//Am
public static void main(String[] args) {
(new Am()).run(args);
}
Am类是继承BaseCommand,因此这里的run实际上是调用父类的run方法,BaseCommand的run方法实现如下:
//BaseCommand
public void run(String[] args) {
if (args.length < 1) {
onShowUsage(System.out);
return;
}
mArgs = args;
mNextArg = 0;
mCurArgData = null;
try {
//这里调用的是子类Am的onRun方法
onRun();
} catch (IllegalArgumentException e) {
onShowUsage(System.err);
System.err.println();
System.err.println("Error: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace(System.err);
System.exit(1);
}
}
接着,再回到Am中的OnRun方法实现,代码如下所示:
//Am
@Override
public void onRun() throws Exception {
mAm = ActivityManagerNative.getDefault();
//...
String op = nextArgRequired();
if (op.equals("start")) {
runStart();
} else if (op.equals("startservice")) {
runStartService();
} else if (op.equals("stopservice")) {
runStopService();
} else if (op.equals("force-stop")) {
runForceStop();
//...
} else {
showError("Error: unknown command '" + op + "'");
}
}
这里开始,真正的处理命令参数了,关于am start,我们这里只需要关注runStart方法即可,对应的代码实现如下所示:
//Am
private void runStart() throws Exception {
//创建一个 Intent 对象,用于指定要启动的 Activity
Intent intent = makeIntent(UserHandle.USER_CURRENT);
//...
String mimeType = intent.getType();
if (mimeType == null && intent.getData() != null
&& "content".equals(intent.getData().getScheme())) {
mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
}
do {
//...
System.out.println("Starting: " + intent);
//Activity 将在新的任务栈中启动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//性能分析相关
ParcelFileDescriptor fd = null;
ProfilerInfo profilerInfo = null;
if (mProfileFile != null) {
try {
fd = ParcelFileDescriptor.open(
new File(mProfileFile),
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE |
ParcelFileDescriptor.MODE_READ_WRITE);
} catch (FileNotFoundException e) {
System.err.println("Error: Unable to open file: " + mProfileFile);
return;
}
profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
}
IActivityManager.WaitResult result = null;
int res;
final long startTime = SystemClock.uptimeMillis();
//启动activity,关键代码,同时记录启动前后的时间,用于性能分析
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
}
final long endTime = SystemClock.uptimeMillis();
PrintStream out = mWaitOption ? System.out : System.err;
boolean launched = false;
//activity启动后的结果处理
switch (res) {
case ActivityManager.START_SUCCESS:
launched = true;
break;
//...
default:
out.println(
"Error: Activity not started, unknown error code " + res);
break;
}
//...
//重复启动Activity处理
mRepeat--;
if (mRepeat > 1) {
mAm.unhandledBack();
}
} while (mRepeat > 1);
}
这个方法展示了 adb shell am start 命令背后的逻辑,包括 Intent 的创建、性能分析、Activity 的启动以及结果处理。
1.3 Am.java 额外的逻辑解读
1.3.1 mWaitOption解读
mWaitOption 表示是否应该等待目标 Activity 启动完成后再继续执行。当 mWaitOption 设置为 true 时,am start 命令会等待 ActivityManager 启动指定的 Activity 并返回结果。如果 Activity 成功启动,命令会输出启动结果;如果启动失败,命令会输出错误信息和失败原因。在代码中,mWaitOption 可能通过命令行参数设置,例如,如果用户输入了
$adb shell am start -W
命令,其中的 -W 参数就会使得 mWaitOption 设置为 true。
1.3.2 mRepeat解读
在 Am.java 的 runStart() 方法中,处理重复启动的代码片段提取如下:
do {
//...
mRepeat--;
if (mRepeat > 1) {
mAm.unhandledBack();
}
} while (mRepeat > 1);
mRepeat 的初始值是由用户通过命令行参数(如 --repeat 或 -r)指定的,表示 Activity 需要被启动的次数。在循环体内部,每次启动 Activity 后,mRepeat 的值会递减(mRepeat --
)。如果 mRepeat 大于 1,意味着还需要再次启动 Activity。
在每次成功启动 Activity 并且设置了重复启动之后,if 语句检查 mRepeat 的值。如果还需要再次启动(mRepeat 大于 1),它会调用 mAm.unhandledBack() 方法。这个方法的作用是发送一个“返回”事件(相当于用户按下设备上的返回键)到系统,使得当前 Activity 退到后台,并且将其所在的任务栈上一个 Activity 带到前台。
假设用户执行了如下命令:
#这里通过-r参数设置mRepeat的值
$adb shell am start -r 3 com.example/myActivity
这个命令会启动 com.example/myActivity Activity 三次。以下是预期的行为:
- 第一次启动 Activity。
- 发送返回操作,使得 Activity 退到后台。
- 第二次启动 Activity。
- 再次发送返回操作。
- 第三次启动 Activity。
在第三次启动之后,mRepeat的值会递减到 1,循环结束。
总的来说,mRepeat相关的这段代码实现了通过命令行控制 Activity 重复启动和模拟用户返回操作的功能,有助于进行更复杂的测试和性能评估。
接下来开始进入到AMS中的调用。
2 AMS的startActivityXXX方法分析
2.1 进入到AMS的startActivityMayWait方法
从1.2的分析中,关键代码如下:
//Am
//runStart
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
}
这里开始进入到AMS的代理接口调用,最终是调用到了AMS的接口startActivityAndWait 和 startActivityAsUser方法。解读如下:
//ActivityManagerService
//...
@Override
public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
// 确保调用者不是隔离进程
enforceNotIsolatedCaller("startActivityAndWait");
// 处理用户ID,确保调用者有权启动目标用户ID的Activity
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
// 创建WaitResult对象,用于存储启动结果
WaitResult res = new WaitResult();
// 请求启动Activity并等待结果
// 调用ActivityStackSupervisor的相关方法来实际启动Activity
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null,
options, userId, null, null);
// 返回启动结果
return res;
}
//...
@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
// 确保调用者不是隔离进程
enforceNotIsolatedCaller("startActivity");
// 处理用户ID,确保调用者有权启动目标用户ID的Activity
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, ALLOW_FULL_ONLY, "startActivity", null);
// 请求启动Activity但不等待结果,这里传递的res实际上是null
// 调用ActivityStackSupervisor的相关方法来实际启动Activity
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, options, userId, null, null);
}
两个方法都调用了 ActivityStackSupervisor 的 startActivityMayWait 方法来实际启动 Activity。其中:
- startActivityAndWait 方法返回一个 WaitResult 对象,包含启动结果和性能数据。
- startActivityAsUser 方法立即返回一个整数结果,表示启动操作的结果。
最后总结下,这两个方法是 AMS 中处理 Activity 启动请求的核心方法,分别用于同步和异步启动 Activity。最后也都调用了ActivityStackSupervisor的方法startActivityMayWait。该方法较长,这里分成3个阶段进行解读。3个阶段代码的定位分别是:
- 为启动Activity做相关函数的参数初始化。
- 启动Activity,也就是最关键操作。
- 启动Activity后的返回值处理。
接下来分别进行解读。
2.1.1 startActivityMayWait第一阶段代码分析
ActivityStackSupervisor的方法startActivityMayWait第一阶段代码解读如