参考资料:PMS 第 7 篇 - 通过 pm 指令分析 Install 过程
所谓包,其实是一种文件格式,比如apk包、jar包等。包管理者一个职能就是识别不同的包,维护这些包的信息。当有一个新的包进入或离开Android世界,都需要向包管理者申报一下,其他管理部分要获取包的具体信息,也都需要向包管理者申请。包管理机制的核心是PackageManagerService(下文称PMS),它负责对包进行管理。
Android P 上采用类似 socket 的方式与 server 端通信完成安装,其中Session是重点
其中有如下几个点是我们要重点关注的如下:
通过 IO 流的方式向 Session 内输送 apk 数据。具体代码可以看下文。需要注意的是,PackageInsatller 对于安装结果回调没有采用普通的函数回调,而是采用 Intent 的方式完成回调,比如 广播等(可以参考下面两个实例)。
apk的安装方法
adb inatsll ***.apk 命令对应的代码位置:system/core/adb/commandline.cpp
int adb_commandline(int argc, const char** argv) {
//...各种参数输入的处理
/* adb_connect() commands */
if (!strcmp(argv[0], "devices")) {
//...
}
else if (!strcmp(argv[0], "connect")) {
//..
}
else if (!strcmp(argv[0], "disconnect")) {
//...
}
else if (!strcmp(argv[0], "emu")) {
//...
}
else if (!strcmp(argv[0], "shell")) {
//...
}
else if (!strcmp(argv[0], "exec-in") || !strcmp(argv[0], "exec-out")) {
//...
}
else if (!strcmp(argv[0], "kill-server")) {
return adb_kill_server() ? 0 : 1;
}
else if (!strcmp(argv[0], "sideload")) {
//...
} else if (!strcmp(argv[0], "tcpip")) {
//...
}
else if (!strcmp(argv[0], "remount") ||
//...
} else if (!strcmp(argv[0], "root") || !strcmp(argv[0], "unroot")) {
return adb_root(argv[0]) ? 0 : 1;
} else if (!strcmp(argv[0], "bugreport")) {
//...
} else if (!strcmp(argv[0], "forward") || !strcmp(argv[0], "reverse")) {
//...
}
/* do_sync_*() commands */
else if (!strcmp(argv[0], "ls")) {
//...
}
else if (!strcmp(argv[0], "push")) {
//...
}
else if (!strcmp(argv[0], "pull")) {
//...
}
else if (!strcmp(argv[0], "install")) {
if (argc < 2) return syntax_error("install requires an argument");
if (_use_legacy_install()) { //android 9使用旧的pm install
return install_app_legacy(argc, argv);
} //如果支持FeatureCmd,那就调用install_app
return install_app(argc, argv);
}
else if (!strcmp(argv[0], "install-multiple")) {
//...
} //...
else {
//...
}
syntax_error("unknown command %s", argv[0]);
return 1;
}
这里看到:
static int install_app(int argc, const char** argv) {
// The last argument must be the APK file
const char* file = argv[argc - 1];
if (!android::base::EndsWithIgnoreCase(file, ".apk")) {
return syntax_error("filename doesn't end .apk: %s", file);
}
struct stat sb;
if (stat(file, &sb) == -1) {
fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
return 1;
}
//打开apk文件,获取localFD的文件描述符
int localFd = adb_open(file, O_RDONLY);
if (localFd < 0) {
fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
return 1;
}
//【1】使用 cmd package 命令!
std::string error;
std::string cmd = "exec:cmd package";
// don't copy the APK name, but, copy the rest of the arguments as-is
while (argc-- > 1) {
cmd += " " + escape_arg(std::string(*argv++));
}
// add size parameter [required for streaming installs]
// do last to override any user specified value
cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size));
//使用连接到设备上的adb服务,获取远程描述符
int remoteFd = adb_connect(cmd, &error);
if (remoteFd < 0) {
fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
adb_close(localFd);
return 1;
}
//这里只是把文件复制过去,并没有进行安装
char buf[BUFSIZ];
copy_to_file(localFd, remoteFd);
read_status_line(remoteFd, buf, sizeof(buf)); //读取复制结果
adb_close(localFd);
adb_close(remoteFd);
if (!strncmp("Success", buf, 7)) {
fputs(buf, stdout);
return 0;
}
fprintf(stderr, "adb: failed to install %s: %s", file, buf);
return 1;
}
需要注意的是,这段代码中的实际应用程序的安装操作并没有完成,只是将文件内容从本地复制到了远程。实际的安装步骤可能在后续的代码中执行。
当使用adb install时,在android 9 的情况下会使用旧的安装方式,进行安装。回到用install_app_legacy(),最终调用pm_command
static int install_app_legacy(int argc, const char** argv) {
// 此时我们需要先将 apk 将拷贝到手机的指定目录下!
//【1】如果是要安装到内部存储,目标路径是 DATA_DEST,如果是存储则是 SD_DEST!
static const char *const DATA_DEST = "/data/local/tmp/%s";
static const char *const SD_DEST = "/sdcard/tmp/%s";
const char* where = DATA_DEST;
//【2】如果 adb 指令设置了 -s 参数,那就安装到外置存储,默认是内置存储!
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-s")) {
where = SD_DEST;
}
}
//【3】判断是否有 apk 文件参数!
int result = -1;
std::vector<const char*> apk_file = {argv[last_apk]};
//【4】计算目标位置,将要安装的 apk 拷贝到目标缓存位置
// 同时将 adb 命令的最后一个参数改为 apk 拷贝后的目标缓存位置!
// 目标位置:/data/local/tmp/name.apk!
std::string apk_dest = android::base::StringPrintf(
where, android::base::Basename(argv[last_apk]).c_str());
// do_sync_push 将此 apk 文件传输到目标路径,失败的话将跳转到 clenaup_apk
if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk;
// 设置新位置!
argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */
//执行安装
result = pm_command(argc, argv);
cleanup_apk:
// 删除目标缓存 apk 文件,PMS 会把该 apk 拷贝到 /data/app 目录下,所以这个缓存中的 apk 没用了!
delete_file(apk_dest);
return result;
}
static int pm_command(int argc, const char** argv) {
// 我们看到,这里会将 adb install 命令和参数,转为 pm 指令!
std::string cmd = "pm";
while (argc-- > 0) {
cmd += " " + escape_arg(*argv++);
}
// 继续调用 send_shell_command 方法!
return send_shell_command(cmd, false);
}
int send_shell_command(const std::string& command, bool disable_shell_protocol,
StandardStreamsCallbackInterface* callback) {
int fd;
bool use_shell_protocol = false;
while (true) {
bool attempt_connection = true;
// 使用 shell protocol
if (!disable_shell_protocol) {
FeatureSet features;
std::string error;
if (adb_get_feature_set(&features, &error)) {
// 如果定义了 feature,则替换 shell protocol!
use_shell_protocol = CanUseFeature(features, kFeatureShell2);
} else {
// Device was unreachable.
attempt_connection = false;
}
}
if (attempt_connection) {
std::string error;
// 此时 command 中携带的就是以 pm 开头的命令
std::string service_string = ShellServiceString(use_shell_protocol, "", command);
// 向 shell protocol 发送命令
fd = adb_connect(service_string, &error);
if (fd >= 0) {
break;
}
}
fprintf(stderr, "- waiting for device -\n");
if (!wait_for_device("wait-for-device")) {
return 1;
}
}
// 处理命令执行结果!
int exit_code = read_and_dump(fd, use_shell_protocol, callback);
if (adb_close(fd) < 0) {
PLOG(ERROR) << "failure closing FD " << fd;
}
return exit_code;
}
到这里,我们知道,最后其实是向 shell 服务发送 pm 命令,触发 apk 的安装!
cmd 的调用上面已经分析了,其实和 pm install 没有区别!
这里我们来看下 pm 命令的主要用法,通过 pm install 继续分析安装:
usage: pm path [--user USER_ID] PACKAGE
pm dump PACKAGE
pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]
pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]
[--install-location 0/1/2]
[--force-uuid internal|UUID]
pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]
pm install-commit SESSION_ID
pm install-abandon SESSION_ID
pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE
pm set-installer PACKAGE INSTALLER
pm move-package PACKAGE [internal|UUID]
pm move-primary-storage [internal|UUID]
pm clear [--user USER_ID] PACKAGE
pm enable [--user USER_ID] PACKAGE_OR_COMPONENT
pm disable [--user USER_ID] PACKAGE_OR_COMPONENT
pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT
pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT
pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT
pm set-user-restriction [--user USER_ID] RESTRICTION VALUE
pm hide [--user USER_ID] PACKAGE_OR_COMPONENT
pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT
pm grant [--user USER_ID] PACKAGE PERMISSION
pm revoke [--user USER_ID] PACKAGE PERMISSION
pm reset-permissions
pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}
pm get-app-link [--user USER_ID] PACKAGE
pm set-install-location [0/auto] [1/internal] [2/external]
pm get-install-location
pm set-permission-enforced PERMISSION [true|false]
pm trim-caches DESIRED_FREE_SPACE [internal|UUID]
pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME
pm remove-user USER_ID
pm get-max-users
pm 命令的源码位于 frameworks/base/cmds/pm 中,我们直接去看下目录结构:
chen@ubuntu:~/Mygit/Android$ ls frameworks/base/cmds/pm/
Android.mk MODULE_LICENSE_APACHE2 NOTICE pm
看看Android.mk中的内容:
chen@ubuntu:~/Mygit/Android$ cat frameworks/base/cmds/pm/Android.mk
# Copyright 2007 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := pm
LOCAL_SRC_FILES := pm #定义了该模块的源文件为 "pm",意味着要编译构建的文件名为 "pm"
LOCAL_MODULE_CLASS := EXECUTABLES #表示该模块是一个可执行文件
LOCAL_MODULE_TAGS := optional #表示该模块是一个可选项
include $(BUILD_PREBUILT)
接着,来看看 pm 脚本的内容:
chen@ubuntu:~/Mygit/Android$ cat frameworks/base/cmds/pm/pm
#!/system/bin/sh
cmd package "$@"
这看着像封装了一下调用/system/bin/cmd package
,只是简化使用 “cmd package” 命令的流程
app_process 其实就是 Zygote 的源码,在开机篇我们已经分析过了 Zygote 的启动,这里略过和 Zygote 相关的代码:
位置:frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
//...
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
//【1】获得要启动的 java 进程的 className
className.setTo(arg);
break;
} else {
--i;
break;
}
}
Vector<String8> args;
if (!className.isEmpty()) {
//【2】根据 application 的取值,判断我们启动的是应用类型进程,还是工具类型进程!
args.add(application ? String8("application") : String8("tool"));
runtime.setClassNameAndArgs(className, argc - i, argv + i);
if (!LOG_NDEBUG) {
String8 restOfArgs;
char* const* argv_new = argv + i;
int argc_new = argc - i;
for (int k = 0; k < argc_new; ++k) {
restOfArgs.append("\"");
restOfArgs.append(argv_new[k]);
restOfArgs.append("\" ");
}
ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());
}
} else { // zygote 启动才会进入!
//...
}
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
//【2.2】非 Zygote 进程的启动方式如下
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
}
接下来,会进入 AndroidRuntime 中去!!
这里的 className 传入的值为 com.android.internal.os.RuntimeInit
位置:"frameworks/base/core/jni/AndroidRuntime.cpp"
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
ALOGD(">>>>>> START %s uid %d <<<<<<\n",
className != NULL ? className : "(unknown)", getuid());
static const String8 startSystemServer("start-system-server");
/*
* 'startSystemServer == true' means runtime is obsolete and not run from
* init.rc anymore, so we print out the boot start event here.
*/
for (size_t i = 0; i < options.size(); ++i) {
if (options[i] == startSystemServer) {
/* track our progress through the boot sequence */
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}
}
const char* rootDir = getenv("ANDROID_ROOT");
if (rootDir == NULL) {
rootDir = "/system";
if (!hasDir("/system")) {
LOG_FATAL("No root directory specified, and /android does not exist.");
return;
}
setenv("ANDROID_ROOT", rootDir, 1);
}
//const char* kernelHack = getenv("LD_ASSUME_KERNEL");
//ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);
/* start the virtual machine */ //【1】启动虚拟机!
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
/*
* Register android functions.
*/ //【2】注册 Android 函数!
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
/*
* We want to call main() with a String array with arguments in it.
* At present we have two arguments, the class name and an option string.
* Create an array to hold them.
*/ //【3】创建一个 String 数组来保存传递进来的参数!
jclass stringClass;
jobjectArray strArray;
jstring classNameStr;
stringClass = env->FindClass("java/lang/String");
assert(stringClass != NULL);
strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
assert(strArray != NULL);
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);
for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/ //【4】启动虚拟机的主线程,该主线程不会退出,直到虚拟机退出!
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else { //【2.3】反射调用 RuntimeInit.main 函数,从 native 层进入 java 世界!
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
free(slashClassName);
ALOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
ALOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
ALOGW("Warning: VM did not shut down cleanly\n");
}
通过反射,进入 RuntimeInit 中!
位置:.frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
public static final void main(String[] argv) {
enableDdms();
if (argv.length == 2 && argv[1].equals("application")) {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
redirectLogStreams();
} else {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
}
//【1】进程一些初始化的操作,比如设置线程的默认异常处理 Handler,设置 Log 系统等等,在进程的启动时,我们分析过!!
commonInit();
/*
* Now that we're running in interpreted code, call back into native code
* to run the system.
*/ //【2.4】这里的关键点在这里!
nativeFinishInit();
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
nativeFinishInit 调用的是 native 方法,位于 AndroidRuntime.cpp 文件中!
位置:frameworks/base/core/jni/AndroidRuntime.cpp
static void com_android_internal_os_RuntimeInit_nativeFinishInit(JNIEnv* env, jobject clazz)
{
//【2.5】gCurRuntime 是 AndroidRuntime 子类 AppRuntime 的实例!
gCurRuntime->onStarted();
}
实际上 AndroidRuntime 并没有实现 onStarted 方法,真正是实现是在 App_main.cpp 中的 AppRuntime 类中,他是 AndroidRuntime 的子类!
位置:./frameworks/base/cmds/app_process/app_main.cpp
virtual void onStarted()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
//【2.6】执行 AndroidRuntime 的 callMain 方法!
AndroidRuntime* ar = AndroidRuntime::getRuntime();
ar->callMain(mClassName, mClass, mArgs);
IPCThreadState::self()->stopProcess();
hardware::IPCThreadState::self()->stopProcess();
}
位置:"frameworks/base/core/jni/AndroidRuntime.cpp"
status_t AndroidRuntime::callMain(const String8& className, jclass clazz,
const Vector<String8>& args)
{
JNIEnv* env;
jmethodID methodId;
ALOGD("Calling main entry %s", className.string());
env = getJNIEnv();
if (clazz == NULL || env == NULL) {
return UNKNOWN_ERROR;
}
methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
if (methodId == NULL) {
ALOGE("ERROR: could not find method %s.main(String[])\n", className.string());
return UNKNOWN_ERROR;
}
/*
* We want to call main() with a String array with our arguments in it.
* Create an array and populate it.
*/ //【1】创建一个 String 数组来封装参数!
jclass stringClass;
jobjectArray strArray;
const size_t numArgs = args.size();
stringClass = env->FindClass("java/lang/String");
strArray = env->NewObjectArray(numArgs, stringClass, NULL);
for (size_t i = 0; i < numArgs; i++) {
jstring argStr = env->NewStringUTF(args[i].string());
env->SetObjectArrayElement(strArray, i, argStr);
}
//【*2.7】最终反射调用了 Pm.main 方法!
env->CallStaticVoidMethod(clazz, methodId, strArray);
return NO_ERROR;
}
之前是用adb shell pm …命令
现在是用adb shell cmd package …命令,
也可用adb install 命令安装应用。
cmd package命令通过binder的shell cmd 调用
位置:frameworks/native/cmds/cmd/cmd.cpp
int main(int argc, char* const argv[])
{
//...
// TODO: block until a result is returned to MyResultReceiver.
//参数service即传入的package服务,经过Binder的一些传递(CPP 通过binder调用JAVA Binder服务)最终调用onShellCommand()
status_t err = IBinder::shellCommand(service, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, args,
cb, result);
//...
return res;
}
//位置:./frameworks/base/core/java/android/os/Binder.java
public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
@NonNull ResultReceiver resultReceiver) throws RemoteException {
onShellCommand(in, out, err, args, callback, resultReceiver);
}
对用package来说,即调用到
//位置:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new PackageManagerShellCommand(this)).exec(
this, in, out, err, args, callback, resultReceiver);
}
在PackageManagerShellCommand中,调用exec会调用onCommand我觉得
位置:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter pw = getOutPrintWriter();
try {
switch(cmd) {
case "path":
return runPath();
case "dump":
return runDump();
case "list":
return runList();
//...
case "install": //如果参数是install就调用runInstall
return runInstall();
case "preinstall":
return preInstall();
//...
case "uninstall":
return runUninstall();
case "clear":
return runClear();
case "enable":
return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
case "disable":
return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
//...
default: {
String nextArg = getNextArg();
if (nextArg == null) {
if (cmd.equalsIgnoreCase("-l")) {
return runListPackages(false);
} else if (cmd.equalsIgnoreCase("-lf")) {
return runListPackages(true);
}
} else if (getNextArg() == null) {
if (cmd.equalsIgnoreCase("-p")) {
return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM);
}
}
return handleDefaultCommands(cmd); //如果啥参数都没有,就调用onHelp
}
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
}
return -1;
}
大体的安装流程:
IBinder::shellCommand()/cmd.cpp --> .... --> onShellCommand()/PackageManagerService.java --> exec()/ShellCommand.java
--> onCommand()/PackageManagerShellCommand.java -->
runInstall()
+--> doCreateSession()
| +--> mInterface.getPackageInstaller().createSession(params, installerPackageName, userId);
+--> doCommitSession()
+--> new PackageInstaller.Session(mInterface.getPackageInstaller().openSession(sessionId));
+--> session.commit(receiver.getIntentSender())
private int runInstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final InstallParams params = makeInstallParams();
final String inPath = getNextArg();
setParamsSize(params, inPath);
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId); //创建安装的session
boolean abandonSession = true;
try {
if (inPath == null && params.sessionParams.sizeBytes == -1) {
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
//该过程其实是将inPath指向apk写入到.tmp目录下,指定文件名是base.apk
if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
//提交Session触发安装
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
abandonSession = false;
pw.println("Success");
return 0;
} finally {
if (abandonSession) {
try {
//如果安装异常,忽视本次事务
doAbandonSession(sessionId, false /*logSuccess*/);
} catch (Exception ignore) {
}
}
}
}
Pm 中会根据传入的安装参数,创建 InstallParams,封装安装参数!
位置:./frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
private InstallParams makeInstallParams() {
//【*2.9.1.1】创建 SessionParams 实例,封装安装参数!
final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
//【*2.9.1.2】创建 InstallParams,解耦合!
final InstallParams params = new InstallParams();
params.sessionParams = sessionParams; // 设置引用关系!
String opt;
boolean replaceExisting = true;
//【1】根据额外的参数,设置安装参数!
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-l":
//【1.1】该应用以 forward locked 方式安装,在该模式下,只有应用自己能够访问自己的代码和非资源文件!
sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
break;
case "-r": // ignore //【1.2】替换已存在的 apk
break;
case "-R":
replaceExisting = false;
break;
case "-i": // 显式指定安装器
params.installerPackageName = getNextArg();
if (params.installerPackageName == null) {
throw new IllegalArgumentException("Missing installer package");
}
break;
case "-t": // 允许测试应用安装(设置了 android:testOnly)
sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
break;
case "-s": // 安装到外置存储
sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
break;
case "-f": // 安装到内置存储
sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
break;
case "-d": //【1.3】允许降级安装!
sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
break;
case "-g": //【1.4】授予运行时权限
sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
break;
case "--dont-kill":
sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
break;
case "--originating-uri": // 安装不会杀 app 进程
sessionParams.originatingUri = Uri.parse(getNextArg());
break;
case "--referrer":
sessionParams.referrerUri = Uri.parse(getNextArg());
break;
case "-p": //【1.5】继承已存在的 apk,appPackageName 用以保存要继承的包名,也就是主 apk 的包名!
sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
sessionParams.appPackageName = getNextArg();
if (sessionParams.appPackageName == null) {
throw new IllegalArgumentException("Missing inherit package name");
}
break;
case "--pkg":
sessionParams.appPackageName = getNextArg();
if (sessionParams.appPackageName == null) {
throw new IllegalArgumentException("Missing package name");
}
break;
case "-S": // 显式指定应用的大小
final long sizeBytes = Long.parseLong(getNextArg());
if (sizeBytes <= 0) {
throw new IllegalArgumentException("Size must be positive");
}
sessionParams.setSize(sizeBytes);
break;
case "--abi": // 显式指定 abi
sessionParams.abiOverride = checkAbiArgument(getNextArg());
break;
case "--ephemeral": // 作为一个 lightweight "ephemeral" 应用!
case "--instant":
case "--instantapp":
sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
break;
case "--full":
sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
break;
case "--preload":
sessionParams.setInstallAsVirtualPreload();
break;
case "--user": // 指定安装的 userId
params.userId = UserHandle.parseUserArg(getNextArgRequired());
break;
case "--install-location": // 指定安装位置
sessionParams.installLocation = Integer.parseInt(getNextArg());
break;
case "--force-uuid":
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
sessionParams.volumeUuid = getNextArg();
if ("internal".equals(sessionParams.volumeUuid)) {
sessionParams.volumeUuid = null;
}
break;
case "--force-sdk":
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
if (replaceExisting) {
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
}
return params;
}
额外的参数很多,根据额外的参数,对 SessionParams 进行了属性设置!
sessionParams.installFlags 会根据不同的额外参数,设置不同的二进制位,具体的含义我上面也简单的注释了下。大家可以自己去看源码!
这里我们要注意下:
- 如果我们安装的是 split apk 的话,那么我们可以指定 -p 参数,后面指定主 apk 的包名,后面我们会分析其作用!
位置:./frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
private int doCreateSession(SessionParams params, String installerPackageName, int userId)
throws RemoteException {
userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
params.installFlags |= PackageManager.INSTALL_ALL_USERS;
}
//【*3.1】进入 PackageInstallerService!
final int sessionId = mInterface.getPackageInstaller()
.createSession(params, installerPackageName, userId);
return sessionId;
}
进入了 PackageInstallerService 服务,创建 Session 对象
回顾下参数:
if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
这里的 splitName 值为 “base.apk”;
位置:./frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
//【1】这里的 inPath 就是前面创建的 /data/local/tmp/ 目录!
final PrintWriter pw = getOutPrintWriter();
final ParcelFileDescriptor fd;
//【3】 定义输入流对象,指向待安装 APK 对应文件的源地址
if (STDIN_PATH.equals(inPath)) {
fd = new ParcelFileDescriptor(getInFileDescriptor());
} else if (inPath != null) {
fd = openFileForSystem(inPath, "r");
if (fd == null) {
return -1;
}
sizeBytes = fd.getStatSize();
if (sizeBytes < 0) {
getErrPrintWriter().println("Unable to get size of: " + inPath);
return -1;
}
} else {
fd = new ParcelFileDescriptor(getInFileDescriptor());
}
if (sizeBytes <= 0) {
getErrPrintWriter().println("Error: must specify a APK size");
return 1;
}
PackageInstaller.Session session = null;
try {
//【*3.3.2】根据前面创建的 Session id,获得本次安装事务对象!
//【*3.2.1】通过 PackageInstallerService.openSession 获得对应的 PackageInstallerSession 对象!
//【*2.9.3.1】封装成 Session 实例!
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
//【*2.9.3.1.1】 定义输出流对象,指向待安装 APK 对应文件的源地址!
session.write(splitName, 0, sizeBytes, fd);
if (logSuccess) {
pw.println("Success: streamed " + sizeBytes + " bytes");
}
//【3】拷贝完成,返回 STATUS_SUCCESS!
return 0;
} catch (IOException e) {
getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
return 1;
} finally {
IoUtils.closeQuietly(session);
}
}
doWriteSession 整个流程其实就是进行 apk 的拷贝操作!
位置:./frameworks/base/core/java/android/content/pm/PackageInstaller.java
Session 是对 PackageInstallerSession 的简单封装
public static class Session implements Closeable {
private IPackageInstallerSession mSession;
/** {@hide} */
public Session(IPackageInstallerSession session) {
mSession = session;
}
public void setProgress(float progress) {
//...
}
public void setStagingProgress(float progress) {
//...
}
public void addProgress(float progress) {
//...
}
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
//...
}
public void write(@NonNull String name, long offsetBytes, long lengthBytes,
@NonNull ParcelFileDescriptor fd) throws IOException {
//...
}
public void fsync(@NonNull OutputStream out) throws IOException {
if (ENABLE_REVOCABLE_FD) {
//...
}
public @NonNull String[] getNames() throws IOException {
//...
}
public @NonNull InputStream openRead(@NonNull String name) throws IOException {
//...
}
public void removeSplit(@NonNull String splitName) throws IOException {
//...
}
public void commit(@NonNull IntentSender statusReceiver) {
//...
}
@SystemApi
@RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
public void commitTransferred(@NonNull IntentSender statusReceiver) {
//...
}
public void transfer(@NonNull String packageName)
throws PackageManager.NameNotFoundException {
//...
}
@Override
public void close() {
//...
}
public void abandon() {
//...
}
}
mSession 指向了对应的 PackageInstallerSession 实例
同时其还提供了很多借口,来操作 PackageInstallerSession,这里我们用一张图先简单的看下:
Session.setProgress -> PackageInstallerSession.setClientProgress
Session.setStagingProgress -> PackageInstallerSession.setClientProgress
Session.addProgress -> PackageInstallerSession.addClientProgress
Session.openWrite -> PackageInstallerSession.openWrite
Session.openRead -> PackageInstallerSession.openRead
Session.commit -> PackageInstallerSession.commit
Session.abandon -> PackageInstallerSession.abandon
… … …
这里的 name 传入的是 base.apk:
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
try {
if (ENABLE_REVOCABLE_FD) {
return new ParcelFileDescriptor.AutoCloseOutputStream(
mSession.openWrite(name, offsetBytes, lengthBytes));
} else {
//【*4.1】这里会调用 PackageInstallerSession 的 openWrite 方法
// 将前面创建 Session 的文件目录 /data/app/vmdl[sessionId].tmp 封装成一个文件描述符对象!
final ParcelFileDescriptor clientSocket = mSession.openWrite(name,
offsetBytes, lengthBytes);
//【2】获得该文件描述符输出流
return new FileBridge.FileBridgeOutputStream(clientSocket);
}
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
mSession.openWrite 方法涉及到了文件相关处理,这里就不过多关注
public void addProgress(float progress) {
try {
//这里会调用 PackageInstallerSession 的 addClientProgress 方法
// 更新文件读写进度!
mSession.addClientProgress(progress);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
位置:./frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
@Override
public void addClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
//调用了 setClientProgress 方法
setClientProgress(mClientProgress + progress);
}
}
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
// Always publish first staging movement //【1】用于第一次更新进度
final boolean forcePublish = (mClientProgress == 0);
mClientProgress = progress;
//最终,调用 computeProgressLocked 方法
computeProgressLocked(forcePublish);
}
}
@GuardedBy("mLock")
private void computeProgressLocked(boolean forcePublish) {
//【1】计算进度
mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
+ MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
//【2】更新进度
// Only publish when meaningful change
if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
mReportedProgress = mProgress;
//同时通知事务观察者,进度的变化
mCallback.onSessionProgressChanged(this, mProgress);
}
}
位置:./frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
PackageInstaller.Session session = null;
try {
//获得前面创建的 Session 对象
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
// Sanity check that all .dm files match an apk.
// (The installer does not support standalone .dm files and will not process them.)
try {
DexMetadataHelper.validateDexPaths(session.getNames());
} catch (IllegalStateException | IOException e) {
pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]");
}
//创建了一个 LocalIntentReceiver
final LocalIntentReceiver receiver = new LocalIntentReceiver();
//将 receiver.getIntentSender() 传递给 pms,用户客户端进程获得安装结果
//调用 PackageInstallerSession 的 commit 方法,提交事务
session.commit(receiver.getIntentSender());
//处理返回结果
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
if (logSuccess) {
pw.println("Success");
}
} else {
pw.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
}
return status;
} finally {
IoUtils.closeQuietly(session);
}
}
到这里,提交过程就结束了,最终的逻辑是在 PackageInstallerService 中实现
LocalIntentReceiver 用来接收安装结果
private static class LocalIntentReceiver {
//以 Intent 的方式保存安装结果
private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
try {
//将结果保存到 mResult 中
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
//getIntentSender 将 getIntentSender 封装到 IntentSender 中,由于 IntentSender 实现了 Parcelable,所以可以跨进程传递:
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
try {
//返回安装结果
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
LocalIntentReceiver 有一个内部成员 mLocalSender,实现了 IIntentSender.Stub,用于跨进程通信。
系统进程会持有 mLocalSender 对应的代理对象,通过 send 方法,将安装的状态或者结果返回保存到 mResult 中。
上面写了
位置:./frameworks/base/core/java/android/content/pm/PackageInstaller.java
public void commit(@NonNull IntentSender statusReceiver) {
try {
//最终,调用了 PackageInstallerSession 的 commit 方法,进入系统进程
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
上面写了