apk安裝和優化原理
來自: http://blog.csdn.net/jltxgcy/article/details/50550420
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;
} 代碼位于dalvik\vm\analysis\DexPrepare.c
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。