APK安裝流程系列文章整體內容如下:
- APK安裝流程詳解0——前言
- APK安裝流程詳解1——有關"安裝ing"的實體類概述
- APK安裝流程詳解2——PackageManager簡介
- APK安裝流程詳解3——PackageManager與PackageManagerService
- APK安裝流程詳解4——安裝中關于so庫的那些事
- APK安裝流程詳解5——PackageInstallerService和Installer
- APK安裝流程詳解6——PackageManagerService啟動前奏
- APK安裝流程詳解7——PackageManagerService的啟動流程(上)
- APK安裝流程詳解8——PackageManagerService的啟動流程(下)
- APK安裝流程詳解9——PackageParser解析APK(上)
- APK安裝流程詳解10——PackageParser解析APK(下)
- APK安裝流程詳解11——普通應用安裝簡介
- APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)
- APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)
- APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充
- APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補充
- APK安裝流程詳解16——Android包管理總結
本片文章的主要內容如下:
- 1、ABI簡介
- 2、PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析
- 3、PackageManagerService#setNativeLibraryPaths(PackageParser.Package)方法分析
一、ABI簡介
ABI全程是:Application binary interface,即:應用程序二進制接口,它定義了一套規則,允許編譯好的二進制目標代碼在所兼容該ABI的操作系統和硬件平臺中無需改動就能運行。
不同的Android手機使用不同的CPU,因此支持不同的指令集。CPU與指令集的每種組合都有其自己的應用二進制接口(或ABI)。"ABI"精確定義了"運行時,應用的機器碼和系統的交互方式"。你必須為應用要使用每個CPU架構指定ABI。
典型的ABI包含以下信息:
- 1、機器代碼應使用的CPU指令集
- 2、運行時內存存儲和加載的字節順序
- 3、可執行二進制文件(例如程序和共享庫)的格式,以及它們支持的內容類型
- 4、用于解析內容與系統之間的數據的各種約定。這些約定包括對齊限制,以及系統如何使用堆棧和在調用函數時注冊。
- 5、運行時可用于機器代碼的函數符號列表 - 通常來自非常具體的庫集。
由上述定義可以判斷:
ABI定義了規則,而具體的實現是由編譯器、CPU、操作系統共同來完成的。不同的CPU芯片(如:ARM、Intel x86、MIPS)支持不同的ABI架構,常見的ABI類型包含:armabi、armabi-v7a、x86、x86_64、mips、mips64、arm64-v8a等。
這也就是為什么我們編譯出的運行于windows的二進制程序不能運行于Mac OS/Linux/Android平臺了,因此CPU芯片和操作系統均不相同,支持的ABI類型也不一樣,因此無法識別對方的二進制程序。
而我們說的"交叉編譯"的核心原理也跟這些密切相關,交叉編譯,就是使用交叉編譯工具,在平臺上編譯生成另一個平臺的二進制可執行程序,為什么可以做到?因為交叉編譯工具實現了另一個平臺所定義的ABI規則。我們在Window/Linux平臺使用Android NDK交叉編譯工具來編譯出Android平臺的庫也是這個道理。
(一)、.so文件與ABI
如果你的項目中使用了NDK,它就生成了.so文件。如果你的項目只使用了Java語言進行編程,可能就不太關注so文件了。因為Java是跨平臺的。但是其實項目中的依賴函數庫或者引擎庫已經嵌入了so文件。并依賴不同的ABI,比如項目中使用了百度地圖,里面就會涉及相應的so文件。
Android應用支持的ABI取決于APK中位于lib/ABI目錄中的so文件,其中
ABI可能是上面說過的其中ABI的一種
(二)、關于so文件的一些補充
1、so文件的重要法則
處理so文件時有一條簡單但卻很重的法則:
應該盡最大可能為每個ABI提供經過優化過的.so文件,且最好不要混合著使用。即你應該為每個ABI目錄提供對應的so文件。
2、NDK兼容性
使用NDK時,一般人會傾向于使用最新的編譯憑條,但實際上這樣做是有問題的。因為NDK平臺是不向后兼容的,而是向前兼容的。所以推薦使用APP的minSdkVersion對應的編譯平臺。這也意味著當你引入一個預編譯好的.so文件時,你需要檢查它被編譯所用的平臺版本。
3、混合使用不同的編譯的so文件
so文件可以依賴于不同的C++運行時,靜態編譯或者動態加載,混合使用不同版本的C++運行時可能會導致很多奇怪的crash。最好避免這種情況。
PS:當只有一個so文件時,靜態編譯C++運行時是沒有問題的。但是當存在多個so文件時,應該讓所有so文件都動態鏈接相同的C++運行時。這意味著當引入一個新的預編譯so文件,而且項目中還存在其他so文件時,我們需要首先確認新引入的so文件使用的C++運行時是否已經存在的so文件一致。
(三)、ABI和CPU的關系
1、Android CPU的基礎知識
C++代碼必須根據Android 設備的CPU類型(通常稱為"ABIs")進行編譯,常用的五種 ABI:
- armeabiv-v7a:第七代及以上ARM處理器。2011年以后的生產的大部分Android設備都是用它。
- arm64-v8a:第8代、64位ARM處理器,設備不多,比如三星Galaxy S6
- armeabi:第5代、第6代ARM處理器,早期的手機用的比較多。
- x86:平臺、模擬器用得比較多。
- x86_64:64位的平板。
2、 ABI支持CPU列表
ABI支持CPU列表,如下:
舉例說明:
在x86設備上,選擇ABI的先后順序
- 第一步:在libs/x86目錄中如果存在.so文件的話,會被安裝,如果沒有走第二步。
- 第二步:會在armeabi-v7a中的.so文件,如果有,會被安裝,如果沒有會走第三步。
- 第三步:會在armeabi目錄中的.so文件尋找
PS:x86設備能夠很好的運行ARM類型函數庫,但并不保證100% 發生crash,特別是對舊設備,因為是運行在x86設備上模擬ARM的虛擬層上。
3、 ABI支持CPU的知識點
- 1、大部分CPU都支持多余一種的ABI
- 2、 當一個應用安裝在設備上,只有設備支持的CPU架構對應的.so文件會被安裝。
- 3、64位設備(arm64-v8a、x86_64、mips64)能夠運行32位的函數庫,但是以32位版本的ART和Android組件,將丟失64位優化過的性能(ART、webview、media等等)。
- 4、最好針對特定平臺提供相應平臺的二進制包,這種情況下運行時就少了一個模擬層(例如x86設備上模擬arm模擬層),從而得到更好的性能(歸功與最近的架構更新,例如硬件fpu,更多的寄存器,更好的向量化)。
- 5、會優先安裝優先級較高的ABI目錄,則其他優先級較低的ABI目錄(包括其他module中的ABI目錄),都無法安裝。例如:在cpu是ARMv7架構的手機上,如果檢測到armeabi-v7a,就會選擇安裝armeabi-v7a,則armeabi下的文件,就無法安裝了。
- 6、相應的ABI二進制文件,要放進相應的ABI目錄中
- 7、一般情況下不要隨便修改架構目錄名
(四)、常見問題:
1、so文件 放進了優先級低的ABI目錄
問題:
如果你的項目中,有其他優先級更好的ABI目錄,但是你把ABI文件方法放到了優先級低的目錄,最后導致你的ABI文件無法被加載
舉例:
某手機CPU架構是ARMv7,ABI文件是armeabi-v7a,但是放進了armeabi目錄中:
導致結果
項目中有armeabi-v7a的目錄,armeabi目錄的文件,無法被加載,然后運行報錯,出現類似于如下log信息。
Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/.xx../base.apk"],nativeLibraryDirectories=[/data/app/.xx../lib/arm, /vendor/lib, /system/lib]]] couldn't find "lib..xx...so"
解決方案:建議armeabi-v7a的目錄下的文件和armeabi目錄的文件保持一致。
2、兩個第三方SDK中的ABI文件優先級不一樣
問題:
兩個第三方的SDK中ABI文件優先級不一樣,手機加載運行時,會導致優先級低的庫,無法被加載。
例子:
某手機CPU架構是ARMv7,項目中使用了兩個第三方SDK:假設是"支付寶"和"銀聯".
- 支付寶:ABI文件是armeabi-v7a,所以放到armeabi-v7a目錄中。
- 銀聯:ABI文件是armeabi,所以放到armeabi目錄中。
導致結果:
在運行時,會發現運行后crash,出現如下日志:
Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/.xx../base.apk"],nativeLibraryDirectories=[/data/app/.xx../lib/arm, /vendor/lib, /system/lib]]] couldn't find "lib..xx...so"
解決方案:
解決方案1:
使用同一優先級的ABI文件,ABI文件放入到優先級相同的ABI目錄
比如:
- 支付寶:ABI文件是armeabi-v7a,放到armeabi-v7a目錄中。
- 銀聯:ABI文件是armeabi-v7a,放到armeabi-v7a目錄中。
或 - 支付寶:ABI文件是armeabi,放到armeabi目錄中。
- 銀聯:ABI文件是armeabi,放到armeabi目錄中。
解決方案2:
如果兩個第三方提供的是不同優先級的ABI文件,則將ABI文件放入到優先級相同的ABI。
比如:
- 支付寶:ABI文件是armeabi-v7a,放到armeabi目錄中。
- 銀聯:ABI文件是armeabi,放到armeabi目錄中。
二、 PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析
這個方法在PackageManagerService的installPackageLI方法里面被調用。代碼在代碼在PackageManagerService.java 12442行
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
...
derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride,true /* extract libs */);
...
}
在講解這個方法的時候我們先來了解一個概念是"primaryCpuAbi",關于ABI的概念可以參考我的文章Android ABI簡介 ,那"primaryCpuAbi"又是什么?
因為一個系統支持的ABI有很多,不止一個,比如一個64位的機器上它的supportAbiList,可能如下所示:
public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ",");
root@:/ # getprop ro.product.cpu.abilist
arm64-v8a,armeabi-v7a,armeabi
所以它能支持的abi有如上的3個,這個primaryCpuAbi就是要知道當前程序的abi在他支持的abi中最靠前的的哪一個。同時依靠這個primaryCpuAbi的值可以決定我們的程序是運行在32位還是64位的。
那我們來看下derivePackageAbi這個方法的內部實現
代碼在PackageManagerService.java 7553行
/**
* Derive the ABI of a non-system package located at {@code scanFile}. This information
* is derived purely on the basis of the contents of {@code scanFile} and
* {@code cpuAbiOverride}.
*
* If {@code extractLibs} is true, native libraries are extracted from the app if required.
*/
public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
String cpuAbiOverride, boolean extractLibs)
throws PackageManagerException {
// TODO: We can probably be smarter about this stuff. For installed apps,
// we can calculate this information at install time once and for all. For
// system apps, we can probably assume that this information doesn't change
// after the first boot scan. As things stand, we do lots of unnecessary work.
// Give ourselves some initial paths; we'll come back for another
// pass once we've determined ABI below.
// *********** 第一步 ***********
// 設置so庫的安裝路徑
setNativeLibraryPaths(pkg);
// We would never need to extract libs for forward-locked and external packages,
// since the container service will do it for us. We shouldn't attempt to
// extract libs from system app when it was not updated.
// 如果是系統級別的APP則不用每次都提取
if (pkg.isForwardLocked() || pkg.applicationInfo.isExternalAsec() ||
(isSystemApp(pkg) && !pkg.isUpdatedSystemApp())) {
extractLibs = false;
}
// 本地庫目錄
final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
// 是否有設置過nativeLibraryRootRequiresIsa
final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa;
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(scanFile);
// TODO(multiArch): This can be null for apps that didn't go through the
// usual installation process. We can calculate it again, like we
// do during install time.
//
// TODO(multiArch): Why do we need to rescan ASEC apps again ? It seems totally
// unnecessary.
// 獲取本地庫 的File
final File nativeLibraryRoot = new File(nativeLibraryRootStr);
// Null out the abis so that they can be recalculated.
// 第一順位的支持的abi
pkg.applicationInfo.primaryCpuAbi = null;
// 第二 順位的支持的abi
pkg.applicationInfo.secondaryCpuAbi = null;
// 是否支持多架構的APK,這種APK的AndroidManifest.xml里面會設置android:multiarch=true
// *********** 第二步 ***********
if (isMultiArch(pkg.applicationInfo)) {
// 如果支持多平臺
// Warn if we've set an abiOverride for multi-lib packages..
// By definition, we need to copy both 32 and 64 bit libraries for
// such packages.
if (pkg.cpuAbiOverride != null
&& !NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(pkg.cpuAbiOverride)) {
Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
}
// 初始化 32位的abi和64位的abi
int abi32 = PackageManager.NO_NATIVE_LIBRARIES;
int abi64 = PackageManager.NO_NATIVE_LIBRARIES;
// 如果有 設備支持的32位abi
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
// 如果需要導出
if (extractLibs) {
//調用 NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進行so庫拷貝
abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
useIsaSpecificSubdirs);
} else {
//調用 NativeLibraryHelper的findSupportedAbi方法讀取CPU支持的架構類型
abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
}
}
// 檢查是否有異常
maybeThrowExceptionForMultiArchCopy(
"Error unpackaging 32 bit native libs for multiarch app.", abi32);
// 如果有 設備支持的64位abi
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
if (extractLibs) {
//調用 NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進行so庫拷貝
abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
useIsaSpecificSubdirs);
} else {
//調用 NativeLibraryHelper的findSupportedAbi方法獲取CPU支持的架構類型
abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
}
}
// 檢查是否有異常
maybeThrowExceptionForMultiArchCopy(
"Error unpackaging 64 bit native libs for multiarch app.", abi64);
// 如果abi64有值,則說明有支持的64位庫
if (abi64 >= 0) {
// 設置 第一順位的abi即primaryCpuAbi為支持的64位ABI
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
}
// 如果abi32有值,則說明有支持的32位庫
if (abi32 >= 0) {
final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
if (abi64 >= 0) {
// 如果同時還支持64位, 設置第二順位的abi為32位的abi
pkg.applicationInfo.secondaryCpuAbi = abi;
} else {
// 如果只支持32位, 設置第一順位的abi位32的abi
pkg.applicationInfo.primaryCpuAbi = abi;
}
}
} else {
// 不支持多平臺
// 獲取設備中支持的CPU架構
String[] abiList = (cpuAbiOverride != null) ?
new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
// Enable gross and lame hacks for apps that are built with old
// SDK tools. We must scan their APKs for renderscript bitcode and
// not launch them if it's present. Don't bother checking on devices
// that don't have 64 bit support.
// 是否需要RenderScript重寫,RenderScript是Android平臺的一種類C腳本語言,咱們暫時不考慮
boolean needsRenderScriptOverride = false;
if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
abiList = Build.SUPPORTED_32_BIT_ABIS;
needsRenderScriptOverride = true;
}
final int copyRet;
//如果需要導出
if (extractLibs) {
//調用NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進行so拷貝
copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
} else {
//如果不需要導出
//調用NativeLibraryHelper的findSupportedAbi方法讀取CPU支持的架構類
copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
}
// 判斷是否出現異常
if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Error unpackaging native libs for app, errorCode=" + copyRet);
}
// 根據copyRet的值,確定當前APP的primaryCpuAbi的值
if (copyRet >= 0) {
// 設置應用包信息中的主要CPU架構類型,后續啟動DVM需要用到
pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
} else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
// 沒有本地庫
pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
} else if (needsRenderScriptOverride) {
pkg.applicationInfo.primaryCpuAbi = abiList[0];
}
}
} catch (IOException ioe) {
Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
} finally {
IoUtils.closeQuietly(handle);
}
// *********** 第三步 ***********
// Now that we've calculated the ABIs and determined if it's an internal app,
// we will go ahead and populate the nativeLibraryPath.
//更新so庫的安裝位置
setNativeLibraryPaths(pkg);
}
先來看下方法的注釋
導出位于scanFile的的ABI包,這個ABI信息是基于scanFile和cpuAbiOverride
如果extractLibs為真,則本地庫將會從應用程序中提取出來
方法內部注釋已經很清楚了,我將這個方法分為3部分
- 第一步:設置so的安裝路徑
- 第二步:對so進行具體的操作,這里面分為兩種情況:
- 情況A:其支持多平臺
- 情況B:不支持多平臺
- 第三步:更新so的安裝路徑
流程圖如下:
這個方法進行so拷貝的是 NativeLibraryHelper.copyNativeBinariesForSupportedAbi方法,讀取CPU支持的類型為NativeLibraryHelper的findSupportedAbi方法,下面我們就來了解下這兩個方法
(一)、NativeLibraryHelper的靜態方法findSupportedAbi
代碼在NativeLibraryHelper.java 191行
/**
* Checks if a given APK contains native code for any of the provided
* {@code supportedAbis}. Returns an index into {@code supportedAbis} if a matching
* ABI is found, {@link PackageManager#NO_NATIVE_LIBRARIES} if the
* APK doesn't contain any native code, and
* {@link PackageManager#INSTALL_FAILED_NO_MATCHING_ABIS} if none of the ABIs match.
*/
public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
int finalRes = NO_NATIVE_LIBRARIES;
// 遍歷handle的apkHandles
for (long apkHandle : handle.apkHandles) {
// 調用nativeFindSupportedAbi進行查找
final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
if (res == NO_NATIVE_LIBRARIES) {
// No native code, keep looking through all APKs.
} else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
// Found some native code, but no ABI match; update our final
// result if we haven't found other valid code.
if (finalRes < 0) {
finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
}
} else if (res >= 0) {
// Found valid native code, track the best ABI match
if (finalRes < 0 || res < finalRes) {
finalRes = res;
}
} else {
// Unexpected error; bail
return res;
}
}
return finalRes;
}
有注釋,先看一下注釋
- 檢查指定的APK是否包含指定的supportedAbis的Native代碼。如果匹配則返回一個對應supportedAbis的索引,如果沒有Native的代碼則返回PackageManager#NO_NATIVE_LIBRARIES,如果APK不包含對應的Native代碼,則返回ackageManager#INSTALL_FAILED_NO_MATCHING_ABIS
方法內部簡單,主要是調用了nativeFindSupportedAbi方法,通過我前面的文章Android跨進程通信IPC之3——關于"JNI"的那些事 我們知道它對應的文件是com_android_internal_content_NativeLibraryHelper.cpp
那我們就來在com_android_internal_content_NativeLibraryHelper.cpp文件中找下
代碼在577行,如下:
static JNINativeMethod gMethods[] = {
{"nativeOpenApk",
"(Ljava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_openApk},
{"nativeClose",
"(J)V",
(void *)com_android_internal_content_NativeLibraryHelper_close},
{"nativeCopyNativeBinaries",
"(JLjava/lang/String;Ljava/lang/String;ZZ)I",
(void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
{"nativeSumNativeBinaries",
"(JLjava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
{"nativeFindSupportedAbi",
"(J[Ljava/lang/String;)I",
(void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
{"hasRenderscriptBitcode", "(J)I",
(void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
};
我們看到nativeCopyNativeBinaries方法對應的是com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法,那我們再來找下com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法
那我們就來看下com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法
代碼在com_android_internal_content_NativeLibraryHelper.cpp 510行
static jint
com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,
jlong apkHandle, jobjectArray javaCpuAbisToSearch)
{
return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
}
我們看到com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法里面調用了findSupportedAbi方法
那我們再來看下findSupportedAbi方法
代碼在com_android_internal_content_NativeLibraryHelper.cpp 435行
static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
const int numAbis = env->GetArrayLength(supportedAbisArray);
Vector<ScopedUtfChars*> supportedAbis;
for (int i = 0; i < numAbis; ++i) {
supportedAbis.add(new ScopedUtfChars(env,
(jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
}
// 讀取apk文件
ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
if (zipFile == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
ZipEntryRO entry = NULL;
int status = NO_NATIVE_LIBRARIES;
// 開始遍歷apk中的每一個文件
while ((entry = it->next()) != NULL) {
// We're currently in the lib/ directory of the APK, so it does have some native
// code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
// libraries match.
if (status == NO_NATIVE_LIBRARIES) {
status = INSTALL_FAILED_NO_MATCHING_ABIS;
}
const char* fileName = it->currentEntry();
const char* lastSlash = it->lastSlash();
// Check to see if this CPU ABI matches what we are looking for.
const char* abiOffset = fileName + APK_LIB_LEN;
const size_t abiSize = lastSlash - abiOffset;
// 開始遍歷apk的子文件,獲取so文件的全路徑,如果這個路徑包含了cpu架構值,就記錄并返回索引值
for (int i = 0; i < numAbis; i++) {
const ScopedUtfChars* abi = supportedAbis[i];
if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {
// The entry that comes in first (i.e. with a lower index) has the higher priority.
if (((i < status) && (status >= 0)) || (status < 0) ) {
status = i;
}
}
}
}
for (int i = 0; i < numAbis; ++i) {
delete supportedAbis[i];
}
return status;
}
這里看到了先讀取apk文件,然后遍歷apk文件中的so文件,得到全路徑后再和傳遞撿來的abiList進行比較,得到合適的索引值。假設我們剛才拿到的abiList為:x86,然后就開始比較apk中有沒有這些架構平臺的so文件,如果有,就直接返回abiList的索引值。比如apk的libs結構如下:
- 如果這時候只有一種架構,libs文件下也有相關的ABI類型,就只能返回0了。
- 假設我們的abiList為:arm64-v8a,armeabi-v7a,armeabi。那么這時候返回的索引值是0,代表的是arm64-v8a架構。如果APK文件中沒有arm64-v8a目錄的話,那么就返回1。代表的是armeabi-v7a架構的架構。以此類推。得到引用支持的架構索引之后就可以獲取so釋放到設備中的目錄了。
(二)、NativeLibraryHelper的靜態方法copyNativeBinariesForSupportedAbi
代碼在NativeLibraryHelper.java 292行
public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
String[] abiList, boolean useIsaSubdir) throws IOException {
// 創建so 目錄
createNativeLibrarySubdir(libraryRoot);
/*
* If this is an internal application or our nativeLibraryPath points to
* the app-lib directory, unpack the libraries if necessary.
*/
// 獲取應用支持的架構類型
int abi = findSupportedAbi(handle, abiList);
if (abi >= 0) {
/*
* If we have a matching instruction set, construct a subdir under the native
* library root that corresponds to this instruction set.
*/
// 根據不同的架構獲取不同的目錄
final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);
final File subDir;
// 是否有父目錄
if (useIsaSubdir) {
// 如果有父目錄,則設置父目錄
final File isaSubdir = new File(libraryRoot, instructionSet);
createNativeLibrarySubdir(isaSubdir);
subDir = isaSubdir;
} else {
// 沒有父目錄
subDir = libraryRoot;
}
// 進行真正的so拷貝
int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]);
// 如果拷貝沒有成功
if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
return copyRet;
}
}
return abi;
}
他的核心業務代碼都在 native 層,它主要做了如下的工作:
這個方法里面的核心調用是** copyNativeBinaries**方法,下面我們就來看下這個方法
NativeLibraryHelper的靜態方法copyNativeBinaries
/**
* Copies native binaries to a shared library directory.
*
* @param handle APK file to scan for native libraries
* @param sharedLibraryDir directory for libraries to be copied to
* @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another
* error code from that class if not
*/
public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
// 遍歷handle的apkHandles數組
for (long apkHandle : handle.apkHandles) {
// 調用nativeCopyNativeBinaries方法,因為它是natvie開頭,所以它是native的
int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
handle.extractNativeLibs, HAS_NATIVE_BRIDGE);
if (res != INSTALL_SUCCEEDED) {
return res;
}
}
return INSTALL_SUCCEEDED;
}
先來翻譯一下注釋:
將Native的二進制文件復制到共享庫中
- 入參 handle:掃描出來的APK的Native庫
- 入參 sharedLibraryDir:要被復制到的目標目錄
- 出參 :如果復制成功則返回PackageManager#INSTALL_SUCCEEDED,或者其他錯誤碼
方法內部簡單,主要是調用了nativeCopyNativeBinaries方法,通過我前面的文章Android跨進程通信IPC之3——關于"JNI"的那些事 我們知道它對應的文件是com_android_internal_content_NativeLibraryHelper.cpp
那我們就來在com_android_internal_content_NativeLibraryHelper.cpp文件中找下
代碼在571行,如下:
static JNINativeMethod gMethods[] = {
{"nativeOpenApk",
"(Ljava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_openApk},
{"nativeClose",
"(J)V",
(void *)com_android_internal_content_NativeLibraryHelper_close},
{"nativeCopyNativeBinaries",
"(JLjava/lang/String;Ljava/lang/String;ZZ)I",
(void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
{"nativeSumNativeBinaries",
"(JLjava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
{"nativeFindSupportedAbi",
"(J[Ljava/lang/String;)I",
(void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
{"hasRenderscriptBitcode", "(J)I",
(void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
};
我們看到nativeCopyNativeBinaries方法對應的是com_android_internal_content_NativeLibraryHelper_copyNativeBinaries方法,那我們再來找下com_android_internal_content_NativeLibraryHelper_copyNativeBinaries方法
代碼在com_android_internal_content_NativeLibraryHelper.cpp 489行
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
jboolean extractNativeLibs, jboolean hasNativeBridge)
{
void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge };
return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi,
copyFileIfChanged, reinterpret_cast<void*>(args));
}
這個方法里面接著調用了iterateOverNativeFiles方法,那我們來看下iterateOverNativeFiles方法的內部實現
PS:這里面的copyFileIfChanged是個函數指針。
代碼在com_android_internal_content_NativeLibraryHelper.cpp 394行
static install_status_t
iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
iterFunc callFunc, void* callArg) {
// 讀取apk文件
ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
if (zipFile == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
if (it.get() == NULL) {
return INSTALL_FAILED_INVALID_APK;
}
const ScopedUtfChars cpuAbi(env, javaCpuAbi);
if (cpuAbi.c_str() == NULL) {
// This would've thrown, so this return code isn't observable by
// Java.
return INSTALL_FAILED_INVALID_APK;
}
ZipEntryRO entry = NULL;
// 開始遍歷apk中的每一個文件
while ((entry = it->next()) != NULL) {
const char* fileName = it->currentEntry();
const char* lastSlash = it->lastSlash();
// Check to make sure the CPU ABI of this file is one we support.
const char* cpuAbiOffset = fileName + APK_LIB_LEN;
const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
if (cpuAbi.size() == cpuAbiRegionSize && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
// 拷貝so,這一句才是關鍵。copyFileIfChanged完成釋放
install_status_t ret = callFunc(env, callArg, zipFile, entry, lastSlash + 1);
if (ret != INSTALL_SUCCEEDED) {
ALOGV("Failure for entry %s", lastSlash + 1);
return ret;
}
}
}
return INSTALL_SUCCEEDED;
}
我們看到釋放工作是在copyFileIfChanged函數里面,下面我們來下這個函數
PS:ZipFileRO的遍歷順序,它是根據文件對應的ZipEntryRO中的hash值而定,而對弈已經hasPrimaryAbi的情況下,非PrimaryAbi是直接跳過copy操作的,所以這里可能會出現很多拷貝so失敗的情況。
代碼在com_android_internal_content_NativeLibraryHelper.cpp 175行
/*
* Copy the native library if needed.
*
* This function assumes the library and path names passed in are considered safe.
*/
static install_status_t
copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
{
void** args = reinterpret_cast<void**>(arg);
jstring* javaNativeLibPath = (jstring*) args[0];
jboolean extractNativeLibs = *(jboolean*) args[1];
jboolean hasNativeBridge = *(jboolean*) args[2];
ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
uint32_t uncompLen;
uint32_t when;
uint32_t crc;
uint16_t method;
off64_t offset;
if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) {
ALOGD("Couldn't read zip entry info\n");
return INSTALL_FAILED_INVALID_APK;
}
if (!extractNativeLibs) {
// check if library is uncompressed and page-aligned
if (method != ZipFileRO::kCompressStored) {
ALOGD("Library '%s' is compressed - will not be able to open it directly from apk.\n",
fileName);
return INSTALL_FAILED_INVALID_APK;
}
if (offset % PAGE_SIZE != 0) {
ALOGD("Library '%s' is not page-aligned - will not be able to open it directly from"
" apk.\n", fileName);
return INSTALL_FAILED_INVALID_APK;
}
if (!hasNativeBridge) {
return INSTALL_SUCCEEDED;
}
}
// Build local file path
const size_t fileNameLen = strlen(fileName);
char localFileName[nativeLibPath.size() + fileNameLen + 2];
if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != nativeLibPath.size()) {
ALOGD("Couldn't allocate local file name for library");
return INSTALL_FAILED_INTERNAL_ERROR;
}
*(localFileName + nativeLibPath.size()) = '/';
if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, sizeof(localFileName)
- nativeLibPath.size() - 1) != fileNameLen) {
ALOGD("Couldn't allocate local file name for library");
return INSTALL_FAILED_INTERNAL_ERROR;
}
// Only copy out the native file if it's different.
// 只有so本地文件改變了才能拷貝
struct tm t;
ZipUtils::zipTimeToTimespec(when, &t);
const time_t modTime = mktime(&t);
struct stat64 st;
if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) {
return INSTALL_SUCCEEDED;
}
char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 2];
if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName))
!= nativeLibPath.size()) {
ALOGD("Couldn't allocate local file name for library");
return INSTALL_FAILED_INTERNAL_ERROR;
}
*(localFileName + nativeLibPath.size()) = '/';
if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN,
TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) {
ALOGI("Couldn't allocate temporary file name for library");
return INSTALL_FAILED_INTERNAL_ERROR;
}
// 生成了一個臨時文件,用于拷貝
int fd = mkstemp(localTmpFileName);
if (fd < 0) {
ALOGI("Couldn't open temporary file name: %s: %s\n", localTmpFileName, strerror(errno));
return INSTALL_FAILED_CONTAINER_ERROR;
}
// 解壓縮so文件
if (!zipFile->uncompressEntry(zipEntry, fd)) {
ALOGI("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
close(fd);
unlink(localTmpFileName);
return INSTALL_FAILED_CONTAINER_ERROR;
}
close(fd);
// Set the modification time for this file to the ZIP's mod time.
struct timeval times[2];
times[0].tv_sec = st.st_atime;
times[1].tv_sec = modTime;
times[0].tv_usec = times[1].tv_usec = 0;
if (utimes(localTmpFileName, times) < 0) {
ALOGI("Couldn't change modification time on %s: %s\n", localTmpFileName, strerror(errno));
unlink(localTmpFileName);
return INSTALL_FAILED_CONTAINER_ERROR;
}
// Set the mode to 755
static const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
if (chmod(localTmpFileName, mode) < 0) {
ALOGI("Couldn't change permissions on %s: %s\n", localTmpFileName, strerror(errno));
unlink(localTmpFileName);
return INSTALL_FAILED_CONTAINER_ERROR;
}
// Finally, rename it to the final name.
if (rename(localTmpFileName, localFileName) < 0) {
ALOGI("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno));
unlink(localTmpFileName);
return INSTALL_FAILED_CONTAINER_ERROR;
}
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
return INSTALL_SUCCEEDED;
}
上述就是解壓縮so文件的實現。先判斷so名字和不合法,然后判斷是不是文件改變了, 然后創建一個臨時文件,最后解壓縮,用臨時文件拷貝so到指定目錄,結尾關閉一些鏈接。
至此 derivePackageAbi方法分析完畢
三、PackageManagerService#setNativeLibraryPaths(PackageParser.Package)方法分析
上面在derivePackageAbi方面會調用setNativeLibraryPaths方法,我們就簡單的分析下這個方法
代碼在PackageManagerService.java 7841 行
/**
* Derive and set the location of native libraries for the given package,
* which varies depending on where and how the package was installed.
*/
private void setNativeLibraryPaths(PackageParser.Package pkg) {
final ApplicationInfo info = pkg.applicationInfo;
final String codePath = pkg.codePath;
final File codeFile = new File(codePath);
final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp();
final boolean asecApp = info.isForwardLocked() || info.isExternalAsec();
info.nativeLibraryRootDir = null;
info.nativeLibraryRootRequiresIsa = false;
info.nativeLibraryDir = null;
info.secondaryNativeLibraryDir = null;
// 判斷是不是apk文件,其實就是判斷文件是不是以.apk結尾
if (isApkFile(codeFile)) {
// Monolithic install
// 如果是系統相關的應用
if (bundledApp) {
// If "/system/lib64/apkname" exists, assume that is the per-package
// native library directory to use; otherwise use "/system/lib/apkname".
// 獲取apk系統根目錄的路徑
final String apkRoot = calculateBundledApkRoot(info.sourceDir);
final boolean is64Bit = VMRuntime.is64BitInstructionSet(
getPrimaryInstructionSet(info));
// This is a bundled system app so choose the path based on the ABI.
// if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this
// is just the default path.
final String apkName = deriveCodePathName(codePath);
final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
info.nativeLibraryRootDir = Environment.buildPath(new File(apkRoot), libDir,
apkName).getAbsolutePath();
if (info.secondaryCpuAbi != null) {
final String secondaryLibDir = is64Bit ? LIB_DIR_NAME : LIB64_DIR_NAME;
info.secondaryNativeLibraryDir = Environment.buildPath(new File(apkRoot),
secondaryLibDir, apkName).getAbsolutePath();
}
} else if (asecApp) {
// 如果是asec的App
info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME)
.getAbsolutePath();
} else {
// 普通的App
final String apkName = deriveCodePathName(codePath);
// 在data/app-lib下簡歷一個apk目錄
info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
.getAbsolutePath();
}
info.nativeLibraryRootRequiresIsa = false;
info.nativeLibraryDir = info.nativeLibraryRootDir;
} else {
// Cluster install
// 如果是目錄
info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
info.nativeLibraryRootRequiresIsa = true;
// 目錄下直接創建一個lib目錄
info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
getPrimaryInstructionSet(info)).getAbsolutePath();
if (info.secondaryCpuAbi != null) {
info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,
VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();
}
}
}
這個方法就是確定lib庫最終的目錄,我們看下邏輯,這里分幾種情況
- 是APK文件
- 系統相關應用,先判斷是不是64位
- 是64位:/system/lib64/apkname
- 不是64位:/system/lib/apkname- ASEC應用:父目錄/lib/apkname
- 普通應用:在data/app-lib目錄下創建apk目錄
- 不是APK文件:直接在當前目錄下創建一個lib目錄
這個方法里面有一個比較重要的方法calculateBundledApkRoot獲取系統應用的根目錄
1、calculateBundledApkRoot(String) 方法解析
代碼在PackageManagerService.java 7805 行
private static String calculateBundledApkRoot(final String codePathString) {
final File codePath = new File(codePathString);
final File codeRoot;
if (FileUtils.contains(Environment.getRootDirectory(), codePath)) {
codeRoot = Environment.getRootDirectory();
} else if (FileUtils.contains(Environment.getOemDirectory(), codePath)) {
codeRoot = Environment.getOemDirectory();
} else if (FileUtils.contains(Environment.getVendorDirectory(), codePath)) {
codeRoot = Environment.getVendorDirectory();
} else {
// Unrecognized code path; take its top real segment as the apk root:
// e.g. /something/app/blah.apk => /something
try {
File f = codePath.getCanonicalFile();
File parent = f.getParentFile(); // non-null because codePath is a file
File tmp;
while ((tmp = parent.getParentFile()) != null) {
f = parent;
parent = tmp;
}
codeRoot = f;
Slog.w(TAG, "Unrecognized code path "
+ codePath + " - using " + codeRoot);
} catch (IOException e) {
// Can't canonicalize the code path -- shenanigans?
Slog.w(TAG, "Can't canonicalize code path " + codePath);
return Environment.getRootDirectory().getPath();
}
}
return codeRoot.getPath();
}
這個方法其實就是獲取相應的目錄,主要分為4種情況
- 1、如果是system目錄,則返回system目錄
- 2、如果是oem目錄,則返回oem目錄
- 3、如果是vendor目錄,則返回vendor
- 4、無法識別的目錄則獲取其根目錄
上一篇文章 APK安裝流程詳解3——PackageManager與PackageManagerService
下一篇文章 APK安裝流程詳解5——PackageInstallerService和Installer