dex2oat与应用安装时间优化
背景
-
4.4之前,android使用dalvik虚拟机,采用JIT(Just-in-time 即时编译),在运行时将字节码即时翻译成机器码再执行
-
5.0开始,android使用art虚拟机,采用AOT(Ahead Of Time 运行前编译),在安装时将字节码(.dex)翻译成机器码(.oat)再执行,提高运行时效率
-
由于dex2oat过程涉及 读取dex -> 以类为粒度编译 -> 生成许多中间文件 ->合并为.oat(odex)文件 所以EMMC性能 ,CPU性能, swap区大小, 等硬件性能在一定程度上决定了编译的速度, 而3561平台性能较弱,编译时间的增加直接的造成安装时间提高, 同时应用的热更新包编译会占据一定的cpu资源造成系统卡顿,直接影响用户体验,本文主要从灵活修改dex2oat调度策略来优化dex2oat过程,而没有减少dex2oat时间
优化
1.对与安装应用,根据应用的方法数来决定是否执行dex2oat
-
如果对所有应用强制不进行dex2oat,能一劳永逸,将安装时间压缩到最低,但是这违背了art虚拟机设计的初衷
-
虽然很多市场上的app体积比较大,但是除去资源文件,他的类数量和方法数量不一定像体积那么大,对于方法数,
较小的应用,去做dex2oat的优化是有必要的,因为与dex2oat时间产生直接关系的是方法数而不是apk体积 -
我们希望在3561平台上将所有应用的安装时间控制在1分钟以内,经过测试和统计,执行dex2oat在一分钟左右的应用方法数在110000左右,所以对于方法数在110000以下的应用执行dex2oat,对方法数在110000以上的应用禁止dex2oat,以达到性能和安装时间均衡的状态
commands.cpp
# 从property中获取dex2oat方法数边界值
bool have_dex2oat_num_dex_methods_flag = property_get("fly.dex2oat.num_dex_methods",
dex2oat_num_dex_methods_flag, NULL) > 0;
# 设置--num-dex-methods参数,
if(have_dex2oat_num_dex_methods_flag){
sprintf(dex2oat_num_dex_method_arg, "--num-dex-methods=%s", dex2oat_num_dex_methods_flag);
}
dex2oat.cc
if (!image_ &&
compiler_options_->IsCompilationEnabled()) {
size_t num_methods = 0;
for (size_t i = 0; i != dex_files_.size(); ++i) {
const DexFile* dex_file = dex_files_[i];
CHECK(dex_file != nullptr);
num_methods += dex_file->NumMethodIds();
}
# GetNumDexMethodsThreshold()获取--num-dex-methods参数
if (num_methods != 0
&& compiler_options_->GetNumDexMethodsThreshold() != 0
&& num_methods <= compiler_options_->GetNumDexMethodsThreshold()) {
# 方法数小于--num-dex-methods则设置为kSpeed,执行dex2oat
compiler_options_->SetCompilerFilter(CompilerOptions::kSpeed);
}
else{
# 方法数大于--num-dex-methods则设置为kInterpretOnly,禁止dex2oat
compiler_options_->SetCompilerFilter(CompilerOptions::kInterpretOnly);
}
}
2.对与应用插件,限制对插件执行dex2oat的线程数
热更新不可能直接把java源码下发,只能下发dex文件,在本地把基线dex给反编译然后合并再回编译很明显不可能,毕竟baksmali.jar和smali.jar加起来都快5m了,热更新框架不可能搞这么大。但是利用dex2oat的这个特性,我们可以只下发包括更新代码的dex,客户端接收后把新dex作为classes.dex,旧dex作为classes2.dex,送进dex2oat,就能得到一个更新过代码的oat文件,尽可能简单的完成热更新。
作者:琴梨梨
链接:https://www.jianshu.com/p/cf63266cca86
插件下发时,dex2oat默认会开启和cpu核心数相同的线程数去编译,占据非常的的cpu资源,很容易会造成系统卡顿
插件一般会下载到APP的私有数据目录,即 “data/data/” 或 “data/user/” ,而使用dex2oat时会把dex的源路径作为参数传入,根据参数有无包含这两个路径可以判断是否是一个插件在调用dex2oat,如果是的话则限制dex2oat的线程数为1,保证热更新的正常和系统的流畅
dex2oat.cc
for (const char* dex_file_name : dex_filenames_) {
std::string fileNameStr(dex_file_name);
if(fileNameStr.find("data/user/") != std::string::npos || fileNameStr.find("data/data/") != std::string::npos) {
compiler_options_->SetCompilerFilter(CompilerOptions::kSpeed);
# 限制线程数为1
thread_count_ = 1;
break;
}
}