Android的打包文件apk的安装和优化原理
0x00
apk安装的方式有:
1、开机启动时安装
2、通过adb install 或者在手机中点击apk,进行界面安装。
0x01
开机启动后在system_server中调用PackageManagerService.main,随着调用的深入,循环对每个apk都调用scanPackageLI方法,这个函数提取apk的AndroidManifest.xml里面的内容放在PackagemanagerService中,并且安装了apk,还有优化了dex。
安装apk的代码:
-
int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid,
-
pkg.applicationInfo.uid);
优化dex的代码:
-
if (performDexOptLI(pkg, forceDex) == DEX_OPT_FAILED) {
-
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
-
return null;
-
}
-
private int performDexOptLI(PackageParser.Package pkg, boolean forceDex) {
-
boolean performed = false;
-
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0 && mInstaller != null) {
-
String path = pkg.mScanPath;
-
int ret = 0;
-
try {
-
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
-
ret = mInstaller.dexopt(path, pkg.applicationInfo.uid,
-
!isForwardLocked(pkg));
-
pkg.mDidDexOpt = true;
-
performed = true;
-
}
-
} catch (FileNotFoundException e) {
-
Slog.w(TAG, "Apk not found for dexopt: " + path);
-
ret = -1;
-
} catch (IOException e) {
-
Slog.w(TAG, "IOException reading apk: " + path, e);
-
ret = -1;
-
} catch (dalvik.system.StaleDexCacheError e) {
-
Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
-
ret = -1;
-
} catch (Exception e) {
-
Slog.w(TAG, "Exception when doing dexopt : ", e);
-
ret = -1;
-
}
-
if (ret < 0) {
-
//error from installer
-
return DEX_OPT_FAILED;
-
}
-
}
-
-
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
-
}
mInstaller.dexopt 通过socket通信 让installd 进程(由init进程起来了)执行do_dexopt-->dexopt-->fork出子进程去执行run_dexopt,安装和优化的调用流程请参考Android安装服务installd源码分析
。
run_dexopt代码如下:
-
static void run_dexopt(int zip_fd, int odex_fd, const char* input_file_name,
-
const char* dexopt_flags)
-
{
-
//input_file_name为apk的路径
-
static const char* DEX_OPT_BIN = "/system/bin/dexopt";
-
static const int MAX_INT_LEN = 12;
-
char zip_num[MAX_INT_LEN];
-
char odex_num[MAX_INT_LEN];
-
sprintf(zip_num, "%d", zip_fd);//apk文件句柄
-
sprintf(odex_num, "%d", odex_fd);//dex文件句柄
-
//调用/system/bin/dexopt工具来优化apk文件
-
execl(DEX_OPT_BIN, DEX_OPT_BIN, "--zip", zip_num, odex_num, input_file_name,
-
dexopt_flags, (char*) NULL);
-
ALOGE("execl(%s) failed: %s\n", DEX_OPT_BIN, strerror(errno));
-
}
fork出的子线程执行的是/system/bin/dexopt,代码位于dalvik\dexopt\OptMain.c
0x02
执行的是/system/bin/dexopt,实际上就是OptMain.c的main函数。
-
/*
-
* Main entry point. Decide where to go.
-
*/
-
int main(int argc, char* const argv[])
-
{
-
set_process_name("dexopt");
-
-
setvbuf(stdout, NULL, _IONBF, 0);
-
-
if (argc > 1) {
-
if (strcmp(argv[1], "--zip") == 0)
-
return fromZip(argc, argv);
-
else if (strcmp(argv[1], "--dex") == 0)
-
return fromDex(argc, argv);
-
else if (strcmp(argv[1], "--preopt") == 0)
-
return preopt(argc, argv);
-
}
-
......
-
return 1;
-
}
代码位于dalvik\dexopt\OptMain.c。
由于执行时传入的参数是--zip,所以这里执行fromZip。
-
static int fromZip(int argc, char* const argv[])
-
{
-
......
-
-
result = processZipFile(zipFd, cacheFd, zipName, dexoptFlags);
-
-
bail:
-
return result;
-
}
代码位于dalvik\dexopt\OptMain.c。
然后经过processZipFile->extractAndProcessZip->dvmContinueOptimization。
-
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
-
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
-
{
-
DexClassLookup* pClassLookup = NULL;
-
RegisterMapBuilder* pRegMapBuilder = NULL;
-
u4 headerFlags = 0;
-
-
......
-
-
{
-
/*
-
* Map the entire file (so we don't have to worry about page
-
* alignment). The expectation is that the output file contains
-
* our DEX data plus room for a small header.
-
*/
-
bool success;
-
void* mapAddr;
-
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
-
MAP_SHARED, fd, 0);
-
if (mapAddr == MAP_FAILED) {
-
LOGE("unable to mmap DEX cache: %s\n", strerror(errno));
-
goto bail;
-
}
-
-
......
-
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
-
&headerFlags, &pClassLookup);
-
-
if (success) {
-
DvmDex* pDvmDex = NULL;
-
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
-
-
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
-
LOGE("Unable to create DexFile\n");
-
success = false;
-
} else {
-
......
-
}
-
}
-
-
......
-
-
if (!success)
-
goto bail;
-
}
-
-
......
-
-
if (writeDependencies(fd, modWhen, crc) != 0) {
-
LOGW("Failed writing dependencies\n");
-
goto bail;
-
}
-
-
......
-
if (!writeOptData(fd, pClassLookup, pRegMapBuilder)) {
-
LOGW("Failed writing opt data\n");
-
goto bail;
-
}
-
-
......
-
DexOptHeader optHdr;
-
memset(&optHdr, 0xff, sizeof(optHdr));
-
memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
-
memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
-
optHdr.dexOffset = (u4) dexOffset;
-
optHdr.dexLength = (u4) dexLength;
-
optHdr.depsOffset = (u4) depsOffset;
-
optHdr.depsLength = (u4) depsLength;
-
optHdr.optOffset = (u4) optOffset;
-
optHdr.optLength = (u4) optLength;
-
-
optHdr.flags = headerFlags;
-
optHdr.checksum = optChecksum;
-
-
fsync(fd); /* ensure previous writes go before header is written */
-
-
lseek(fd, 0, SEEK_SET);
-
if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0)
-
goto bail;
-
-
LOGV("Successfully wrote DEX header\n");
-
result = true;
-
-
//dvmRegisterMapDumpStats();
-
-
bail:
-
dvmFreeRegisterMapBuilder(pRegMapBuilder);
-
free(pClassLookup);
-
return result;
-
}
dexOffset为odex文件头部大小,dexLength为dex文件长度。首先调用mmap把要优化的dex加载到内存虚拟地址mapAddr,这个dex其实就是位于/data/dalvik-cache/xxx@classes.dex。
然后调用rewriteDex函数对目标文件进行优化验证,其主要内容包括:字符顺序调整、字节码替换、字节码验证以及文件结构重新对齐。
然后通过writeDependencies写入依赖库信息,writeOptData写入其他优化信息,包括类索引信息以及寄存器映射关系。
最后修改odex文件的头部内容。
生成odex更为详细的流程请参考Android系统ODEX文件格式解析。
此时生成的odex其实就是位于/data/dalvik-cache/xxx@classes.dex。
odex结构图如下:
0x03
adb install的安装流程请参考深入理解PackageManagerService。整个安装流程,首先把apk拷贝到/data/local/tmp目录下,在安装的过程中把apk拷贝到/data/app中,最后调用了PackageManagerService的InstallPackagtLI,这个函数调用了installNewPackageLI,installNewPackageLI调用了scanPackageLI,在这个函数里面完成了apk的优化和安装,优化和安装的流程和上面一样。
0x04
本文中讲解了用于PathClassLoader加载/data/dalvik-cache/xxx@classes.dex的生成流程。
那么DexClassLoader加载apk的流程是什么呢?
注意PathClassLoader和DexClassLoader的构造函数有不同:
PathClassLoader:
-
public PathClassLoader(String path, String libPath, ClassLoader parent) {
-
super(parent);
-
-
if (path == null)
-
throw new NullPointerException();
-
-
this.path = path;
-
this.libPath = libPath;
-
-
mPaths = path.split(":");
-
int length = mPaths.length;
-
-
//System.out.println("PathClassLoader: " + mPaths);
-
mFiles = new File[length];
-
mZips = new ZipFile[length];
-
mDexs = new DexFile[length];
-
-
boolean wantDex =
-
System.getProperty("android.vm.dexfile", "").equals("true");
-
-
/* open all Zip and DEX files up front */
-
for (int i = 0; i < length; i++) {
-
//System.out.println("My path is: " + mPaths[i]);
-
File pathFile = new File(mPaths[i]);
-
mFiles[i] = pathFile;
-
-
if (pathFile.isFile()) {
-
try {
-
mZips[i] = new ZipFile(pathFile);
-
}
-
catch (IOException ioex) {
-
// expecting IOException and ZipException
-
//System.out.println("Failed opening '" + pathFile + "': " + ioex);
-
//ioex.printStackTrace();
-
}
-
if (wantDex) {
-
/* we need both DEX and Zip, because dex has no resources */
-
try {
-
mDexs[i] = new DexFile(pathFile);
-
}
-
catch (IOException ioex) {}
-
}
-
}
-
}
-
......
-
}
最终调用的是new DexFile(pathFile)。
而DexClassLoader:
-
public DexClassLoader(String dexPath, String dexOutputDir, String libPath,
-
ClassLoader parent) {
-
-
super(parent);
-
-
if (dexPath == null || dexOutputDir == null)
-
throw new NullPointerException();
-
-
mRawDexPath = dexPath;
-
mDexOutputPath = dexOutputDir;
-
mRawLibPath = libPath;
-
-
String[] dexPathList = mRawDexPath.split(":");
-
int length = dexPathList.length;
-
-
//System.out.println("DexClassLoader: " + dexPathList);
-
mFiles = new File[length];
-
mZips = new ZipFile[length];
-
mDexs = new DexFile[length];
-
-
/* open all Zip and DEX files up front */
-
for (int i = 0; i < length; i++) {
-
//System.out.println("My path is: " + dexPathList[i]);
-
File pathFile = new File(dexPathList[i]);
-
mFiles[i] = pathFile;
-
-
if (pathFile.isFile()) {
-
try {
-
mZips[i] = new ZipFile(pathFile);
-
} catch (IOException ioex) {
-
// expecting IOException and ZipException
-
System.out.println("Failed opening '" + pathFile
-
+ "': " + ioex);
-
//ioex.printStackTrace();
-
}
-
-
/* we need both DEX and Zip, because dex has no resources */
-
try {
-
String outputName =
-
generateOutputName(dexPathList[i], mDexOutputPath);
-
mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
-
} catch (IOException ioex) {
-
// might be a resource-only zip
-
System.out.println("Failed loadDex '" + pathFile
-
+ "': " + ioex);
-
}
-
} else {
-
if (VERBOSE_DEBUG)
-
System.out.println("Not found: " + pathFile.getPath());
-
}
-
}
-
-
.......
-
}
最终调用的是DexFile.loadDex(dexPathList[i], outputName, 0)。
说明DexClassLoader还需要指定一个生成优化后的apk的路径。而PathClassLoader则不需要,因为在安装阶段已经生成了/data/dalvik-cache/xxx@classes.dex。