APK安裝流程詳解4——安裝中關(guān)于so庫(kù)的那些事

APK安裝流程系列文章整體內(nèi)容如下:

本片文章的主要內(nèi)容如下:

  • 1程帕、ABI簡(jiǎn)介
  • 2、PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析
  • 3愧哟、PackageManagerService#setNativeLibraryPaths(PackageParser.Package)方法分析

一骑科、ABI簡(jiǎn)介

ABI全程是:Application binary interface橡淑,即:應(yīng)用程序二進(jìn)制接口,它定義了一套規(guī)則纵散,允許編譯好的二進(jìn)制目標(biāo)代碼在所兼容該ABI的操作系統(tǒng)和硬件平臺(tái)中無(wú)需改動(dòng)就能運(yùn)行梳码。

不同的Android手機(jī)使用不同的CPU隐圾,因此支持不同的指令集。CPU與指令集的每種組合都有其自己的應(yīng)用二進(jìn)制接口(或ABI)掰茶。"ABI"精確定義了"運(yùn)行時(shí)暇藏,應(yīng)用的機(jī)器碼和系統(tǒng)的交互方式"。你必須為應(yīng)用要使用每個(gè)CPU架構(gòu)指定ABI濒蒋。

典型的ABI包含以下信息:

  • 1盐碱、機(jī)器代碼應(yīng)使用的CPU指令集
  • 2、運(yùn)行時(shí)內(nèi)存存儲(chǔ)和加載的字節(jié)順序
  • 3沪伙、可執(zhí)行二進(jìn)制文件(例如程序和共享庫(kù))的格式瓮顽,以及它們支持的內(nèi)容類型
  • 4、用于解析內(nèi)容與系統(tǒng)之間的數(shù)據(jù)的各種約定围橡。這些約定包括對(duì)齊限制暖混,以及系統(tǒng)如何使用堆棧和在調(diào)用函數(shù)時(shí)注冊(cè)。
  • 5翁授、運(yùn)行時(shí)可用于機(jī)器代碼的函數(shù)符號(hào)列表 - 通常來(lái)自非常具體的庫(kù)集拣播。

由上述定義可以判斷:

ABI定義了規(guī)則,而具體的實(shí)現(xiàn)是由編譯器收擦、CPU贮配、操作系統(tǒng)共同來(lái)完成的。不同的CPU芯片(如:ARM塞赂、Intel x86泪勒、MIPS)支持不同的ABI架構(gòu),常見(jiàn)的ABI類型包含:armabi宴猾、armabi-v7a圆存、x86、x86_64鳍置、mips辽剧、mips64、arm64-v8a等税产。
這也就是為什么我們編譯出的運(yùn)行于windows的二進(jìn)制程序不能運(yùn)行于Mac OS/Linux/Android平臺(tái)了怕轿,因此CPU芯片和操作系統(tǒng)均不相同,支持的ABI類型也不一樣辟拷,因此無(wú)法識(shí)別對(duì)方的二進(jìn)制程序撞羽。

而我們說(shuō)的"交叉編譯"的核心原理也跟這些密切相關(guān),交叉編譯衫冻,就是使用交叉編譯工具诀紊,在平臺(tái)上編譯生成另一個(gè)平臺(tái)的二進(jìn)制可執(zhí)行程序,為什么可以做到隅俘?因?yàn)榻徊婢幾g工具實(shí)現(xiàn)了另一個(gè)平臺(tái)所定義的ABI規(guī)則邻奠。我們?cè)赪indow/Linux平臺(tái)使用Android NDK交叉編譯工具來(lái)編譯出Android平臺(tái)的庫(kù)也是這個(gè)道理笤喳。

(一)、.so文件與ABI

如果你的項(xiàng)目中使用了NDK碌宴,它就生成了.so文件杀狡。如果你的項(xiàng)目只使用了Java語(yǔ)言進(jìn)行編程,可能就不太關(guān)注so文件了贰镣。因?yàn)镴ava是跨平臺(tái)的呜象。但是其實(shí)項(xiàng)目中的依賴函數(shù)庫(kù)或者引擎庫(kù)已經(jīng)嵌入了so文件。并依賴不同的ABI碑隆,比如項(xiàng)目中使用了百度地圖恭陡,里面就會(huì)涉及相應(yīng)的so文件。

Android應(yīng)用支持的ABI取決于APK中位于lib/ABI目錄中的so文件上煤,其中
ABI可能是上面說(shuō)過(guò)的其中ABI的一種

(二)休玩、關(guān)于so文件的一些補(bǔ)充

1、so文件的重要法則

處理so文件時(shí)有一條簡(jiǎn)單但卻很重的法則:

應(yīng)該盡最大可能為每個(gè)ABI提供經(jīng)過(guò)優(yōu)化過(guò)的.so文件劫狠,且最好不要混合著使用哥捕。即你應(yīng)該為每個(gè)ABI目錄提供對(duì)應(yīng)的so文件。

2嘉熊、NDK兼容性

使用NDK時(shí),一般人會(huì)傾向于使用最新的編譯憑條扬舒,但實(shí)際上這樣做是有問(wèn)題的阐肤。因?yàn)镹DK平臺(tái)是不向后兼容的,而是向前兼容的讲坎。所以推薦使用APP的minSdkVersion對(duì)應(yīng)的編譯平臺(tái)孕惜。這也意味著當(dāng)你引入一個(gè)預(yù)編譯好的.so文件時(shí),你需要檢查它被編譯所用的平臺(tái)版本晨炕。

3衫画、混合使用不同的編譯的so文件

so文件可以依賴于不同的C++運(yùn)行時(shí),靜態(tài)編譯或者動(dòng)態(tài)加載瓮栗,混合使用不同版本的C++運(yùn)行時(shí)可能會(huì)導(dǎo)致很多奇怪的crash削罩。最好避免這種情況。

PS:當(dāng)只有一個(gè)so文件時(shí)费奸,靜態(tài)編譯C++運(yùn)行時(shí)是沒(méi)有問(wèn)題的弥激。但是當(dāng)存在多個(gè)so文件時(shí),應(yīng)該讓所有so文件都動(dòng)態(tài)鏈接相同的C++運(yùn)行時(shí)愿阐。這意味著當(dāng)引入一個(gè)新的預(yù)編譯so文件微服,而且項(xiàng)目中還存在其他so文件時(shí),我們需要首先確認(rèn)新引入的so文件使用的C++運(yùn)行時(shí)是否已經(jīng)存在的so文件一致缨历。

(三)以蕴、ABI和CPU的關(guān)系

1糙麦、Android CPU的基礎(chǔ)知識(shí)

C++代碼必須根據(jù)Android 設(shè)備的CPU類型(通常稱為"ABIs")進(jìn)行編譯,常用的五種 ABI:

  • armeabiv-v7a:第七代及以上ARM處理器丛肮。2011年以后的生產(chǎn)的大部分Android設(shè)備都是用它赡磅。
  • arm64-v8a:第8代、64位ARM處理器腾供,設(shè)備不多仆邓,比如三星Galaxy S6
  • armeabi:第5代、第6代ARM處理器伴鳖,早期的手機(jī)用的比較多节值。
  • x86:平臺(tái)、模擬器用得比較多榜聂。
  • x86_64:64位的平板搞疗。
2、 ABI支持CPU列表

ABI支持CPU列表须肆,如下:


ABI支持CPU列表.png

舉例說(shuō)明:

在x86設(shè)備上匿乃,選擇ABI的先后順序

  • 第一步:在libs/x86目錄中如果存在.so文件的話,會(huì)被安裝豌汇,如果沒(méi)有走第二步幢炸。
  • 第二步:會(huì)在armeabi-v7a中的.so文件,如果有拒贱,會(huì)被安裝宛徊,如果沒(méi)有會(huì)走第三步。
  • 第三步:會(huì)在armeabi目錄中的.so文件尋找

PS:x86設(shè)備能夠很好的運(yùn)行ARM類型函數(shù)庫(kù)逻澳,但并不保證100% 發(fā)生crash闸天,特別是對(duì)舊設(shè)備,因?yàn)槭沁\(yùn)行在x86設(shè)備上模擬ARM的虛擬層上斜做。

3苞氮、 ABI支持CPU的知識(shí)點(diǎn)

  • 1、大部分CPU都支持多余一種的ABI
  • 2瓤逼、 當(dāng)一個(gè)應(yīng)用安裝在設(shè)備上笼吟,只有設(shè)備支持的CPU架構(gòu)對(duì)應(yīng)的.so文件會(huì)被安裝。
  • 3抛姑、64位設(shè)備(arm64-v8a赞厕、x86_64、mips64)能夠運(yùn)行32位的函數(shù)庫(kù)定硝,但是以32位版本的ART和Android組件皿桑,將丟失64位優(yōu)化過(guò)的性能(ART、webview、media等等)诲侮。
  • 4镀虐、最好針對(duì)特定平臺(tái)提供相應(yīng)平臺(tái)的二進(jìn)制包,這種情況下運(yùn)行時(shí)就少了一個(gè)模擬層(例如x86設(shè)備上模擬arm模擬層)沟绪,從而得到更好的性能(歸功與最近的架構(gòu)更新刮便,例如硬件fpu,更多的寄存器绽慈,更好的向量化)恨旱。
  • 5、會(huì)優(yōu)先安裝優(yōu)先級(jí)較高的ABI目錄坝疼,則其他優(yōu)先級(jí)較低的ABI目錄(包括其他module中的ABI目錄)搜贤,都無(wú)法安裝。例如:在cpu是ARMv7架構(gòu)的手機(jī)上钝凶,如果檢測(cè)到armeabi-v7a仪芒,就會(huì)選擇安裝armeabi-v7a,則armeabi下的文件耕陷,就無(wú)法安裝了掂名。
  • 6、相應(yīng)的ABI二進(jìn)制文件哟沫,要放進(jìn)相應(yīng)的ABI目錄中
  • 7饺蔑、一般情況下不要隨便修改架構(gòu)目錄名

(四)、常見(jiàn)問(wèn)題:

1嗜诀、so文件 放進(jìn)了優(yōu)先級(jí)低的ABI目錄
問(wèn)題:

如果你的項(xiàng)目中膀钠,有其他優(yōu)先級(jí)更好的ABI目錄,但是你把ABI文件方法放到了優(yōu)先級(jí)低的目錄裹虫,最后導(dǎo)致你的ABI文件無(wú)法被加載

舉例:

某手機(jī)CPU架構(gòu)是ARMv7,ABI文件是armeabi-v7a融击,但是放進(jìn)了armeabi目錄中:

導(dǎo)致結(jié)果

項(xiàng)目中有armeabi-v7a的目錄筑公,armeabi目錄的文件,無(wú)法被加載匣屡,然后運(yùn)行報(bào)錯(cuò)拇涤,出現(xiàn)類似于如下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、兩個(gè)第三方SDK中的ABI文件優(yōu)先級(jí)不一樣
問(wèn)題:

兩個(gè)第三方的SDK中ABI文件優(yōu)先級(jí)不一樣也拜,手機(jī)加載運(yùn)行時(shí)以舒,會(huì)導(dǎo)致優(yōu)先級(jí)低的庫(kù),無(wú)法被加載慢哈。

例子:

某手機(jī)CPU架構(gòu)是ARMv7蔓钟,項(xiàng)目中使用了兩個(gè)第三方SDK:假設(shè)是"支付寶"和"銀聯(lián)".

  • 支付寶:ABI文件是armeabi-v7a,所以放到armeabi-v7a目錄中卵贱。
  • 銀聯(lián):ABI文件是armeabi滥沫,所以放到armeabi目錄中。
導(dǎo)致結(jié)果:

在運(yùn)行時(shí)键俱,會(huì)發(fā)現(xiàn)運(yùn)行后crash兰绣,出現(xiàn)如下日志:

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:
使用同一優(yōu)先級(jí)的ABI文件,ABI文件放入到優(yōu)先級(jí)相同的ABI目錄
比如:

  • 支付寶:ABI文件是armeabi-v7a方妖,放到armeabi-v7a目錄中狭魂。
  • 銀聯(lián):ABI文件是armeabi-v7a,放到armeabi-v7a目錄中党觅。
  • 支付寶:ABI文件是armeabi雌澄,放到armeabi目錄中。
  • 銀聯(lián):ABI文件是armeabi杯瞻,放到armeabi目錄中镐牺。

解決方案2:
如果兩個(gè)第三方提供的是不同優(yōu)先級(jí)的ABI文件睬涧,則將ABI文件放入到優(yōu)先級(jí)相同的ABI旗唁。
比如:

  • 支付寶:ABI文件是armeabi-v7a检疫,放到armeabi目錄中。
  • 銀聯(lián):ABI文件是armeabi夺溢,放到armeabi目錄中风响。

二鞋怀、 PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析

這個(gè)方法在PackageManagerService的installPackageLI方法里面被調(diào)用接箫。代碼在代碼在PackageManagerService.java 12442行

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
        ...
         derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride,true /* extract libs */);
        ...
   }

在講解這個(gè)方法的時(shí)候我們先來(lái)了解一個(gè)概念是"primaryCpuAbi"辛友,關(guān)于ABI的概念可以參考我的文章Android ABI簡(jiǎn)介 废累,那"primaryCpuAbi"又是什么邑滨?

因?yàn)橐粋€(gè)系統(tǒng)支持的ABI有很多掖看,不止一個(gè),比如一個(gè)64位的機(jī)器上它的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個(gè)刹泄,這個(gè)primaryCpuAbi就是要知道當(dāng)前程序的abi在他支持的abi中最靠前的的哪一個(gè)特石。同時(shí)依靠這個(gè)primaryCpuAbi的值可以決定我們的程序是運(yùn)行在32位還是64位的县匠。

那我們來(lái)看下derivePackageAbi這個(gè)方法的內(nèi)部實(shí)現(xiàn)
代碼在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.
         // *********** 第一步 *********** 
         // 設(shè)置so庫(kù)的安裝路徑
        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.
        // 如果是系統(tǒng)級(jí)別的APP則不用每次都提取
        if (pkg.isForwardLocked() || pkg.applicationInfo.isExternalAsec() ||
                (isSystemApp(pkg) && !pkg.isUpdatedSystemApp())) {
            extractLibs = false;
        }

        // 本地庫(kù)目錄
        final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
       // 是否有設(shè)置過(guò)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.
            // 獲取本地庫(kù) 的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;
           // 是否支持多架構(gòu)的APK贼穆,這種APK的AndroidManifest.xml里面會(huì)設(shè)置android:multiarch=true
         // *********** 第二步 *********** 
            if (isMultiArch(pkg.applicationInfo)) {
                // 如果支持多平臺(tái)
                // 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;
                // 如果有 設(shè)備支持的32位abi
                if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                    // 如果需要導(dǎo)出
                    if (extractLibs) {
                         //調(diào)用 NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進(jìn)行so庫(kù)拷貝
                        abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS,
                                useIsaSpecificSubdirs);
                    } else {
                         //調(diào)用 NativeLibraryHelper的findSupportedAbi方法讀取CPU支持的架構(gòu)類型
                        abi32 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
                    }
                }

                // 檢查是否有異常
                maybeThrowExceptionForMultiArchCopy(
                        "Error unpackaging 32 bit native libs for multiarch app.", abi32);

                 // 如果有 設(shè)備支持的64位abi
                if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                    if (extractLibs) {
                         //調(diào)用 NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進(jìn)行so庫(kù)拷貝
                        abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                                nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
                                useIsaSpecificSubdirs);
                    } else {
                         //調(diào)用 NativeLibraryHelper的findSupportedAbi方法獲取CPU支持的架構(gòu)類型
                        abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
                    }
                }
                 // 檢查是否有異常
                maybeThrowExceptionForMultiArchCopy(
                        "Error unpackaging 64 bit native libs for multiarch app.", abi64);

                // 如果abi64有值顶瞳,則說(shuō)明有支持的64位庫(kù)
                if (abi64 >= 0) {
                    // 設(shè)置 第一順位的abi即primaryCpuAbi為支持的64位ABI
                    pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
                }

                // 如果abi32有值焰络,則說(shuō)明有支持的32位庫(kù)
                if (abi32 >= 0) {
                    
                    final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                    if (abi64 >= 0) {
                      // 如果同時(shí)還支持64位, 設(shè)置第二順位的abi為32位的abi
                        pkg.applicationInfo.secondaryCpuAbi = abi;
                    } else {
                      // 如果只支持32位协饲, 設(shè)置第一順位的abi位32的abi
                        pkg.applicationInfo.primaryCpuAbi = abi;
                    }
                }
            } else {
                 // 不支持多平臺(tái)

                // 獲取設(shè)備中支持的CPU架構(gòu)
                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平臺(tái)的一種類C腳本語(yǔ)言铭污,咱們暫時(shí)不考慮
                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;
                 //如果需要導(dǎo)出
                if (extractLibs) {
                     //調(diào)用NativeLibraryHelper的copyNativeBinariesForSupportedAbi方法進(jìn)行so拷貝
                    copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
                            nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
                } else {
                    //如果不需要導(dǎo)出
                     //調(diào)用NativeLibraryHelper的findSupportedAbi方法讀取CPU支持的架構(gòu)類
                    copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
                }

                 // 判斷是否出現(xiàn)異常
                if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                            "Error unpackaging native libs for app, errorCode=" + copyRet);
                }
                // 根據(jù)copyRet的值嘹狞,確定當(dāng)前APP的primaryCpuAbi的值
                if (copyRet >= 0) {
                    // 設(shè)置應(yīng)用包信息中的主要CPU架構(gòu)類型,后續(xù)啟動(dòng)DVM需要用到
                    pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
                } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
                    // 沒(méi)有本地庫(kù)
                    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庫(kù)的安裝位置
        setNativeLibraryPaths(pkg);
    }

先來(lái)看下方法的注釋

導(dǎo)出位于scanFile的的ABI包知市,這個(gè)ABI信息是基于scanFile和cpuAbiOverride
如果extractLibs為真速蕊,則本地庫(kù)將會(huì)從應(yīng)用程序中提取出來(lái)

方法內(nèi)部注釋已經(jīng)很清楚了规哲,我將這個(gè)方法分為3部分

  • 第一步:設(shè)置so的安裝路徑
  • 第二步:對(duì)so進(jìn)行具體的操作隅肥,這里面分為兩種情況:
    • 情況A:其支持多平臺(tái)
    • 情況B:不支持多平臺(tái)
  • 第三步:更新so的安裝路徑

流程圖如下:


流程圖.png
這個(gè)方法進(jìn)行so拷貝的是 NativeLibraryHelper.copyNativeBinariesForSupportedAbi方法腥放,讀取CPU支持的類型為NativeLibraryHelper的findSupportedAbi方法秃症,下面我們就來(lái)了解下這兩個(gè)方法

(一)种柑、NativeLibraryHelper的靜態(tài)方法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) {
            // 調(diào)用nativeFindSupportedAbi進(jìn)行查找
            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代碼舞虱。如果匹配則返回一個(gè)對(duì)應(yīng)supportedAbis的索引母市,如果沒(méi)有Native的代碼則返回PackageManager#NO_NATIVE_LIBRARIES患久,如果APK不包含對(duì)應(yīng)的Native代碼返帕,則返回ackageManager#INSTALL_FAILED_NO_MATCHING_ABIS

方法內(nèi)部簡(jiǎn)單荆萤,主要是調(diào)用了nativeFindSupportedAbi方法链韭,通過(guò)我前面的文章Android跨進(jìn)程通信IPC之3——關(guān)于"JNI"的那些事 我們知道它對(duì)應(yīng)的文件是com_android_internal_content_NativeLibraryHelper.cpp

那我們就來(lái)在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方法對(duì)應(yīng)的是com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法旋讹,那我們?cè)賮?lái)找下com_android_internal_content_NativeLibraryHelper_findSupportedAbi方法

那我們就來(lái)看下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方法里面調(diào)用了findSupportedAbi方法

那我們?cè)賮?lái)看下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中的每一個(gè)文件
    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文件的全路徑鞭呕,如果這個(gè)路徑包含了cpu架構(gòu)值琅拌,就記錄并返回索引值
        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文件党晋,得到全路徑后再和傳遞撿來(lái)的abiList進(jìn)行比較,得到合適的索引值扳剿。假設(shè)我們剛才拿到的abiList為:x86庇绽,然后就開始比較apk中有沒(méi)有這些架構(gòu)平臺(tái)的so文件瞧掺,如果有凡傅,就直接返回abiList的索引值夏跷。比如apk的libs結(jié)構(gòu)如下:

image.png
  • 如果這時(shí)候只有一種架構(gòu)释簿,libs文件下也有相關(guān)的ABI類型庶溶,就只能返回0了偏螺。
  • 假設(shè)我們的abiList為:arm64-v8a,armeabi-v7a,armeabi套像。那么這時(shí)候返回的索引值是0贞让,代表的是arm64-v8a架構(gòu)喳张。如果APK文件中沒(méi)有arm64-v8a目錄的話销部,那么就返回1舅桩。代表的是armeabi-v7a架構(gòu)的架構(gòu)擂涛。以此類推歼指。得到引用支持的架構(gòu)索引之后就可以獲取so釋放到設(shè)備中的目錄了踩身。

(二)、NativeLibraryHelper的靜態(tài)方法copyNativeBinariesForSupportedAbi

代碼在NativeLibraryHelper.java 292行

    public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
            String[] abiList, boolean useIsaSubdir) throws IOException {

        // 創(chuàng)建so 目錄 
        createNativeLibrarySubdir(libraryRoot);

        /*
         * If this is an internal application or our nativeLibraryPath points to
         * the app-lib directory, unpack the libraries if necessary.
         */
        // 獲取應(yīng)用支持的架構(gòu)類型
        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.
             */
            // 根據(jù)不同的架構(gòu)獲取不同的目錄
            final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);
            final File subDir;
           // 是否有父目錄
            if (useIsaSubdir) {
                // 如果有父目錄附鸽,則設(shè)置父目錄
                final File isaSubdir = new File(libraryRoot, instructionSet);
                createNativeLibrarySubdir(isaSubdir);
                subDir = isaSubdir;
            } else {
                // 沒(méi)有父目錄
                subDir = libraryRoot;
            }
            // 進(jìn)行真正的so拷貝
            int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]);
            // 如果拷貝沒(méi)有成功
            if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
                return copyRet;
            }
        }
        return abi;
    }

他的核心業(yè)務(wù)代碼都在 native 層坷备,它主要做了如下的工作:


copyNativeBinariesForSupportedAbi.png

這個(gè)方法里面的核心調(diào)用是** copyNativeBinaries**方法,下面我們就來(lái)看下這個(gè)方法

NativeLibraryHelper的靜態(tài)方法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數(shù)組
        for (long apkHandle : handle.apkHandles) {
            // 調(diào)用nativeCopyNativeBinaries方法竟秫,因?yàn)樗莕atvie開頭肥败,所以它是native的
            int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
                    handle.extractNativeLibs, HAS_NATIVE_BRIDGE);
            if (res != INSTALL_SUCCEEDED) {
                return res;
            }
        }
        return INSTALL_SUCCEEDED;
    }

先來(lái)翻譯一下注釋:

將Native的二進(jìn)制文件復(fù)制到共享庫(kù)中

  • 入?yún)?handle:掃描出來(lái)的APK的Native庫(kù)
  • 入?yún)?sharedLibraryDir:要被復(fù)制到的目標(biāo)目錄
  • 出參 :如果復(fù)制成功則返回PackageManager#INSTALL_SUCCEEDED皿哨,或者其他錯(cuò)誤碼

方法內(nèi)部簡(jiǎn)單往史,主要是調(diào)用了nativeCopyNativeBinaries方法挨决,通過(guò)我前面的文章Android跨進(jìn)程通信IPC之3——關(guān)于"JNI"的那些事 我們知道它對(duì)應(yīng)的文件是com_android_internal_content_NativeLibraryHelper.cpp

那我們就來(lái)在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方法對(duì)應(yīng)的是com_android_internal_content_NativeLibraryHelper_copyNativeBinaries方法慎陵,那我們?cè)賮?lái)找下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));
}

這個(gè)方法里面接著調(diào)用了iterateOverNativeFiles方法,那我們來(lái)看下iterateOverNativeFiles方法的內(nèi)部實(shí)現(xiàn)

PS:這里面的copyFileIfChanged是個(gè)函數(shù)指針润梯。

代碼在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中的每一個(gè)文件
    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纺铭,這一句才是關(guān)鍵舶赔。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函數(shù)里面竟纳,下面我們來(lái)下這個(gè)函數(shù)

PS:ZipFileRO的遍歷順序,它是根據(jù)文件對(duì)應(yīng)的ZipEntryRO中的hash值而定石咬,而對(duì)弈已經(jīng)hasPrimaryAbi的情況下删性,非PrimaryAbi是直接跳過(guò)copy操作的蹬挺,所以這里可能會(huì)出現(xiàn)很多拷貝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;
    }
    // 生成了一個(gè)臨時(shí)文件,用于拷貝
    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文件的實(shí)現(xiàn)。先判斷so名字和不合法池充,然后判斷是不是文件改變了收夸, 然后創(chuàng)建一個(gè)臨時(shí)文件咱圆,最后解壓縮序苏,用臨時(shí)文件拷貝so到指定目錄,結(jié)尾關(guān)閉一些鏈接匈睁。

至此 derivePackageAbi方法分析完畢

三桶错、PackageManagerService#setNativeLibraryPaths(PackageParser.Package)方法分析

上面在derivePackageAbi方面會(huì)調(diào)用setNativeLibraryPaths方法院刁,我們就簡(jiǎn)單的分析下這個(gè)方法

代碼在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文件再榄,其實(shí)就是判斷文件是不是以.apk結(jié)尾
        if (isApkFile(codeFile)) {
            // Monolithic install
            // 如果是系統(tǒng)相關(guān)的應(yīng)用
            if (bundledApp) {
                // If "/system/lib64/apkname" exists, assume that is the per-package
                // native library directory to use; otherwise use "/system/lib/apkname".
                 // 獲取apk系統(tǒng)根目錄的路徑
                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下簡(jiǎn)歷一個(gè)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; 
             // 目錄下直接創(chuàng)建一個(gè)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();
            }
        }
    }

這個(gè)方法就是確定lib庫(kù)最終的目錄剑按,我們看下邏輯艺蝴,這里分幾種情況

  • 是APK文件
    • 系統(tǒng)相關(guān)應(yīng)用,先判斷是不是64位
      - 是64位:/system/lib64/apkname
      - 不是64位:/system/lib/apkname
    • ASEC應(yīng)用:父目錄/lib/apkname
    • 普通應(yīng)用:在data/app-lib目錄下創(chuàng)建apk目錄
  • 不是APK文件:直接在當(dāng)前目錄下創(chuàng)建一個(gè)lib目錄

這個(gè)方法里面有一個(gè)比較重要的方法calculateBundledApkRoot獲取系統(tǒng)應(yīng)用的根目錄

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();
    }

這個(gè)方法其實(shí)就是獲取相應(yīng)的目錄,主要分為4種情況

  • 1陨闹、如果是system目錄薄坏,則返回system目錄
  • 2趋厉、如果是oem目錄,則返回oem目錄
  • 3胶坠、如果是vendor目錄君账,則返回vendor
  • 4、無(wú)法識(shí)別的目錄則獲取其根目錄

上一篇文章 APK安裝流程詳解3——PackageManager與PackageManagerService
下一篇文章 APK安裝流程詳解5——PackageInstallerService和Installer

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沈善,一起剝皮案震驚了整個(gè)濱河市乡数,隨后出現(xiàn)的幾起案子闻牡,更是在濱河造成了極大的恐慌玖翅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嗜桌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)层亿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門碌更,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事∧枇樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)液走。 經(jīng)常有香客問(wèn)我髓废,道長(zhǎng),這世上最難降的妖魔是什么涌攻? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任剂买,我火速辦了婚禮租副,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糟港。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般伸头。 火紅的嫁衣襯著肌膚如雪扫步。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死期犬,一個(gè)胖子當(dāng)著我的面吹牛遣总,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播花盐,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼熙揍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼氏涩!你這毒婦竟也來(lái)了意系?” 一聲冷哼從身側(cè)響起迎瞧,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤压语,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡市俊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年僵刮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驹吮。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脆淹,靈堂內(nèi)的尸體忽然破棺而出蝗蛙,到底是詐尸還是另有隱情歼郭,我是刑警寧澤泰涂,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布驳棱,位于F島的核電站农曲,受9級(jí)特大地震影響笙以,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逻炊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一视乐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧较雕,春花似錦凄吏、人聲如沸蚤吹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞧毙。三九已至示姿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逊笆,已是汗流浹背栈戳。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留难裆,地道東北人子檀。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親命锄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容