PackageManagerService真正干活的是installd,通过Native Binder调用。
为什么需要installd守护进程?因为权限问题,PKMS只有system权限,installd却具有root权限。
在SystemServer中installd服务启动
1、客服端实现
1 | //启动installer服务,PKMS相关任务的执行者 |
Installer代码比较简洁,主要为一些创建、删除文件等操作。
1 | @Override |
2、服务端实现
Android7.0后单一的init.rc文件被拆分,放在对应分区的etc/init目录中,每个服务一个rc文件,与该服务相关的触发器,操作等也定义在同一rc文件中。
frameworks/native/cmds/installd/Android.bp编译文件中
1 | cc_binary { |
frameworks/native/cmds/installd/installd.rc中启动installd进程
1 | service installd /system/bin/installd |
installd.cpp主函数如下
1 | static int installd_main(const int argc ATTRIBUTE_UNUSED, char *argv[]) { |
InstalldNativeService::start()将该服务发布到native层的servicemanager中
1 | status_t InstalldNativeService::start() { |
BinderService
/framework/native/libs/binder/include/binder/BinderService.h
1 | static status_t publish(bool allowIsolated = false) { |
所有的install.java中定义的功能都是通过binder调用native层的InstalldNativeService来实现
install.java中dexopt方法,最后是执行Native层的dexopt方法。
1 | binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t uid, |
android::installd::dexopt操作在dexopt.cpp文件中
int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set,
int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
const char* volume_uuid, const char* class_loader_context, const char* se_info,
bool downgrade, int target_sdk_version, const char* profile_name,
const char* dex_metadata_path, const char* compilation_reason, std::string* error_msg) {
CHECK(pkgname != nullptr);
CHECK(pkgname[0] != 0);
CHECK(error_msg != nullptr);
CHECK_EQ(dexopt_flags & ~DEXOPT_MASK, 0)
<< "dexopt flags contains unknown fields: " << dexopt_flags;
if (!validate_dex_path_size(dex_path)) {
*error_msg = StringPrintf("Failed to validate %s", dex_path);
return -1;
}
if (class_loader_context != nullptr && strlen(class_loader_context) > PKG_PATH_MAX) {
*error_msg = StringPrintf("Class loader context exceeds the allowed size: %s",
class_loader_context);
LOG(ERROR) << *error_msg;
return -1;
}
bool is_public = (dexopt_flags & DEXOPT_PUBLIC) != 0;
bool debuggable = (dexopt_flags & DEXOPT_DEBUGGABLE) != 0;
bool boot_complete = (dexopt_flags & DEXOPT_BOOTCOMPLETE) != 0;
bool profile_guided = (dexopt_flags & DEXOPT_PROFILE_GUIDED) != 0;
bool is_secondary_dex = (dexopt_flags & DEXOPT_SECONDARY_DEX) != 0;
bool background_job_compile = (dexopt_flags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
bool enable_hidden_api_checks = (dexopt_flags & DEXOPT_ENABLE_HIDDEN_API_CHECKS) != 0;
bool generate_compact_dex = (dexopt_flags & DEXOPT_GENERATE_COMPACT_DEX) != 0;
bool generate_app_image = (dexopt_flags & DEXOPT_GENERATE_APP_IMAGE) != 0;
// Check if we're dealing with a secondary dex file and if we need to compile it.
std::string oat_dir_str;
if (is_secondary_dex) {
if (process_secondary_dex_dexopt(dex_path, pkgname, dexopt_flags, volume_uuid, uid,
instruction_set, compiler_filter, &is_public, &dexopt_needed, &oat_dir_str,
downgrade, class_loader_context, error_msg)) {
oat_dir = oat_dir_str.c_str();
if (dexopt_needed == NO_DEXOPT_NEEDED) {
return 0; // Nothing to do, report success.
}
} else {
if (error_msg->empty()) { // TODO: Make this a CHECK.
*error_msg = "Failed processing secondary.";
}
return -1; // We had an error, logged in the process method.
}
} else {
// Currently these flags are only use for secondary dex files.
// Verify that they are not set for primary apks.
CHECK((dexopt_flags & DEXOPT_STORAGE_CE) == 0);
CHECK((dexopt_flags & DEXOPT_STORAGE_DE) == 0);
}
// Open the input file.
unique_fd input_fd(open(dex_path, O_RDONLY, 0));
if (input_fd.get() < 0) {
*error_msg = StringPrintf("installd cannot open '%s' for input during dexopt", dex_path);
LOG(ERROR) << *error_msg;
return -1;
}
// Create the output OAT file.
char out_oat_path[PKG_PATH_MAX];
Dex2oatFileWrapper out_oat_fd = open_oat_out_file(dex_path, oat_dir, is_public, uid,
instruction_set, is_secondary_dex, out_oat_path);
if (out_oat_fd.get() < 0) {
*error_msg = "Could not open out oat file.";
return -1;
}
// Open vdex files.
Dex2oatFileWrapper in_vdex_fd;
Dex2oatFileWrapper out_vdex_fd;
if (!open_vdex_files_for_dex2oat(dex_path, out_oat_path, dexopt_needed, instruction_set,
is_public, uid, is_secondary_dex, profile_guided, &in_vdex_fd, &out_vdex_fd)) {
*error_msg = "Could not open vdex files.";
return -1;
}
// Ensure that the oat dir and the compiler artifacts of secondary dex files have the correct
// selinux context (we generate them on the fly during the dexopt invocation and they don't
// fully inherit their parent context).
// Note that for primary apk the oat files are created before, in a separate installd
// call which also does the restorecon. TODO(calin): unify the paths.
if (is_secondary_dex) {
if (selinux_android_restorecon_pkgdir(oat_dir, se_info, uid,
SELINUX_ANDROID_RESTORECON_RECURSE)) {
*error_msg = std::string("Failed to restorecon ").append(oat_dir);
LOG(ERROR) << *error_msg;
return -1;
}
}
// Create a swap file if necessary.
unique_fd swap_fd = maybe_open_dexopt_swap_file(out_oat_path);
// Create the app image file if needed.
Dex2oatFileWrapper image_fd = maybe_open_app_image(
out_oat_path, generate_app_image, is_public, uid, is_secondary_dex);
// Open the reference profile if needed.
Dex2oatFileWrapper reference_profile_fd = maybe_open_reference_profile(
pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex);
unique_fd dex_metadata_fd;
if (dex_metadata_path != nullptr) {
dex_metadata_fd.reset(TEMP_FAILURE_RETRY(open(dex_metadata_path, O_RDONLY | O_NOFOLLOW)));
if (dex_metadata_fd.get() < 0) {
PLOG(ERROR) << "Failed to open dex metadata file " << dex_metadata_path;
}
}
LOG(VERBOSE) << "DexInv: --- BEGIN '" << dex_path << "' ---";
pid_t pid = fork();
if (pid == 0) {
/* child -- drop privileges before continuing */
drop_capabilities(uid);
SetDex2OatScheduling(boot_complete);
if (flock(out_oat_fd.get(), LOCK_EX | LOCK_NB) != 0) {
PLOG(ERROR) << "flock(" << out_oat_path << ") failed";
_exit(DexoptReturnCodes::kFlock);
}
run_dex2oat(input_fd.get(),
out_oat_fd.get(),
in_vdex_fd.get(),
out_vdex_fd.get(),
image_fd.get(),
dex_path,
out_oat_path,
swap_fd.get(),
instruction_set,
compiler_filter,
debuggable,
boot_complete,
background_job_compile,
reference_profile_fd.get(),
class_loader_context,
target_sdk_version,
enable_hidden_api_checks,
generate_compact_dex,
dex_metadata_fd.get(),
compilation_reason);
} else {
int res = wait_child(pid);
if (res == 0) {
LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' (success) ---";
} else {
LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- status=0x"
<< std::hex << std::setw(4) << res << ", process failed";
*error_msg = format_dexopt_error(res, dex_path);
return res;
}
}
update_out_oat_access_times(dex_path, out_oat_path);
// We've been successful, don't delete output.
out_oat_fd.SetCleanup(false);
out_vdex_fd.SetCleanup(false);
image_fd.SetCleanup(false);
reference_profile_fd.SetCleanup(false);
return 0;
}
dexopt
dexopt 针对 Dalvik 虚拟机,dex2oat 后者针对 Art 虚拟机(Android4.4)。
- dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。
- dex2oat 是对 dex 文件的 AOT 提前编译操作,其需要一个 dex 文件,然后对其进行编译,结果是一个本地可执行的 ELF 文件,可以直接被本地处理器执行。
除此之外在上图还可以看到 Dalvik 虚拟机中有使用 JIT 编译器,也就是说其也能将程序运行的热点 java 字节码编译成本地 code 执行,所以其与 Art 虚拟机还是有区别的。Art 虚拟机的 dex2oat 是提前编译所有 dex 字节码,而 Dalvik 虚拟机只编译使用启发式检测中最频繁执行的热点字节码。
在系统首次启动的场景中,系统会对/system/app、/system/priv-app、/data/app目录下的所有APK进行dex字节码到本地机器码的翻译,同样也会对/system/framework目录下的APK或者JAR文件,以及这些APK所引用的外部JAR,进行dex字节码到本地机器码的翻译。这样可以保证除了应用之外,系统中使用java来开发的系统服务,也会统一地从dex字节码翻译成本地机器码。详细内容请移步老罗的博客Android ART运行时无缝替换Dalvik虚拟机的过程分析。
1、JVM、DVM、ART虚拟机了解
JVM虚拟机运行的是java字节码: java->java bytecode(class)->java bytecode(jar) 注!java虚拟机基于栈,基于栈的机器必须使用指令来载入和操作栈上的数据,所需指令相对来说比较多。
Dalvik虚拟机解释执行的dex字节码: java->java bytecode(class)->dalvik bytecode(dex) 注:相对JVM,Dalvik基于寄存器,且经过优化并允许有限的内存中同时运行多个虚拟机实例,每个Dalvik应用作为一个独立的Linxu进程执行。
如果一个应用中有很多类,编译后会相应生成很多class文件,class文件之间也会有不少冗余信息,dex格式文件把所有classs文件内容整合到一个文件,这样可以减少整体文件占用,IO操作,同时也提高了类的查找速度。此外,dex格式文件增加了新的操作码支持,文件结构也相对简洁,使用等长的指令来提高解析速度。而且dex文件会尽量扩大只读结构的大小,来提高进程间数据共享的速度。
ART虚拟机执行的本地机器码: java->java bytecode(class)->dalvik bytecode(dex)->optimized android runtime machine code(oat) 注:ART所使用的AOT(Ahead-Of-Time)编译,在应用首次安装时,字节码预编译成机器码存储在本地,也就是说在程序运行前编译。而Dalvik是典型的JIT(Just_In_Time),此模式下,应用每次运行的时候,字节码都需要即时编译器转换为机器码再执行,也就是在程序运行时编译。因此在App运行时,ART模式相对于Dalvik省去了解释字节码的过程,占用内存也相应减少,进而提高App的运行效率。
2、Odex
从上面一节中我们知道,在编译打包APK时,Java类会被编译成一个或者多个字节码文件(.class),通过dx工具CLASS文件转换成一个DEX(Dalvik Executable)文件。 通常情况下,我们看到的Android应用程序实际上是一个以.apk为后缀名的压缩文件。我们可以通过压缩工具对apk进行解压,解压出来的内容中有一个名为classes.dex的文件。那么我们首次开机的时候系统需要将其从apk中解压出来保存在data/app目录中。
如果当前运行在Dalvik虚拟机下,Dalvik会对classes.dex进行一次“翻译”,“翻译”的过程也就是守护进程installd的函数dexopt来对dex字节码进行优化,实际上也就是由dex文件生成odex文件,最终odex文件被保存在手机的VM缓存目录data/dalvik-cache下(注意!这里所生成的odex文件依旧是以dex为后缀名,格式如:system@priv-app@Settings@Settings.apk@classes.dex)。
如果当前运行于Art模式下, Art同样会在首次进入系统的时候调用/system/bin/dexopt工具来将dex字节码翻译成本地机器码,保存在data/dalvik-cache下。 那么这里需要注意的是,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个dey文件(表示这是一个优化过的dex),后者对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了。
由于在系统首次启动时会对应用进行安装,那么在预置APK比较多的情况下,将会大大增加系统首次启动的时间。从前面的描述可知,既然无论是DVM还是ART,对DEX的优化结果都是保存在一个相同名称的odex文件,那么如果我们把这两个过程在ROM编译的时候预处理提取Odex文件将会大大优化系统首次启动的时间。
3、预编译提取Odex
在目录/build/core/dex_preopt_odex_install.mk中的代码:
1 | ifeq ($(LOCAL_MODULE),helloworld) |
helloworld可替换为需要跳过提取odex的apk的LOCAL_MODULE名字,如Settings等。
odex优化的地方
1.首次开机或者升级
在SystemServer.java 中有mPackageManagerService.updatePackagesIfNeeded()
1 | updatePackagesIfNeeded->performDexOptUpgrade->performDexOptTraced->performDexOptInternal->performDexOptInternalWithDependenciesLI->PackageDexOptimizer.performDexOpt->performDexOptLI->dexOptPath->Installer.dexopt->InstalldNativeService.dexopt->dexopt.dexopt |
2.安装应用
在PKMS.installPackageLI函数中有:
1 | mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, |
3.IPackageManager.aidl提供了performDexOpt方法
在PKMS中有实现的地方,但是没找到调用的地方
4.IPackageManager.aidl提供了performDexOptMode方法
在PKMS中有实现的地方,在PackageManagerShellCommand中会被调用,应该是提供给shell命令调用
5.OTA升级后
在SystemServer.java 中有OtaDexoptService.main(mSystemContext, mPackageManagerService);
public static OtaDexoptService main(Context context,
PackageManagerService packageManagerService) {
OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
ServiceManager.addService("otadexopt", ota);
// Now it's time to check whether we need to move any A/B artifacts.
ota.moveAbArtifacts(packageManagerService.mInstaller);
return ota;
private void moveAbArtifacts(Installer installer) {
if (mDexoptCommands != null) {
throw new IllegalStateException("Should not be ota-dexopting when trying to move.");
}
//如果不是升级上来的,就return掉
if (!mPackageManagerService.isUpgrade()) {
Slog.d(TAG, "No upgrade, skipping A/B artifacts check.");
return;
}
installer.moveAb(path, dexCodeInstructionSet, oatDir);
moveAbArtifacts函数的逻辑:
1.判断是否升级
2.判断扫描过的package是否有code,没有则跳过
3.判断package的code路径是否为空,为空则跳过
4.如果package的code在system或者vendor目录下,跳过
5.满足上述条件,调用Installer.java中的moveAb方法
最终是调用dexopt.cpp的move_ab方法
OtaDexoptService也提供给shell命令一些方法来调用
6.在系统空闲的时候
是通过BackgroundDexOptService来实现的,BackgroundDexOptService继承了JobService
这里启动了两个任务
1.开机的时候执行odex优化 JOB_POST_BOOT_UPDATE
执行条件:开机一分钟内
2.在系统休眠的时候执行优化 JOB_IDLE_OPTIMIZE
执行条件:设备处于空闲,插入充电器,且每隔一分钟或者一天就检查一次(根据debug开关控制)
1 | private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG |
判断是否需要做dex2oat的逻辑:
1)是否需要编译的类型分类:
1 | class OatFileAssistant { |
核心逻辑:
1 | art/runtime/oat_file_assistant.cc |
这里有两个概念需要了解:
- oat location 与odex location 分别是什么?
app的安装系统目录data/app和system/app,这个路径下每个应用都会生成一个类似包名+乱码的一个文件夹,里面存放主apk以及编译文件。
oat location对应的是oat文件夹路径
odex location对应的是oat/arm or arm64/odex文件路径
如果有odex优先用odex。 - 正负数是指的什么?
正数对应in_odex_path ,负数对应out_oat_path
2)DexOptNeeded各类型赋值
这里主要是看看这几个判断类型是在哪赋值的,这样就知道编译的触发条件有哪些了
1 | if (!oat_file_assistant.IsUpToDate()) { |
过期逻辑一般是先IsUpToDate判断是否过期,然后MakeUpToDate做过期操作,很明显这个部分还是在oat_file_assistant.cc做的
1 | art/runtime/oat_file_assistant.cc |
没有编过就通过MakeUpToDate来置DexOptNeeded编译类型
1 | OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) { |
主要赋值在GetBestInfo()
1 | OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { |
ART 如何编译 DEX
ART 如何编译 DEX 代码还有个compile filter以参数的形式来决定:从 Android O 开始,有四个官方支持的过滤器:
- verify:只运行 DEX 代码验证。
- quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解释器性能。
- speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。
- speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。
verify 和quicken 他俩都没执行编译,之后代码执行需要跑解释器。而speed-profile 和 speed 都执行了编译,区别是speed-profile根据profile记录的热点函数来编译,属于部分编译,而speed属于全编。
执行效率上:
verify < quicken < speed-profile < speed
编译速度上:
verify > quicken > speed-profile > speed
1 | [pm.dexopt.ab-ota]: [speed-profile] |
启动时间相关
主要还是看执行模式
- Android大版本之间相同场景的执行模式是否有区别, dex2oat编译的时候会耗时,并且是多线程的,cpu占用率也会比较高。
- 方法执行是走的解释模式还是执行机器码,执行时间会有差别。
处理方法
如果应用running时间差距比较大,则可以将应用强制按speed进行编译,对比执行效率差异。
adb shell cmd package compile -c -f -m speed <包名>
应用编译成speed模式后,理论上同平台的机器对比,相同阶段(比如同一个inflate)耗时应该接近。若按speed编译后还存在差异,那就得看是否是其他方面的问题了。