判斷當前進程運行時環(huán)境(32/64位)的方法

問題由來

最近項目里添加了64位動態(tài)庫,即app/libs下的armeabiarm64-v8a兩個存儲動態(tài)庫的目錄。項目還有拷貝動態(tài)庫的需要,即:從apk中將動態(tài)庫拷貝到指定目錄下,然后通過System.load()來加載。問題是:

應該拷貝apk中哪個版本的動態(tài)庫呢?armeabi還是arm64-v8a

簡單的回答:主要得看應用進程是被哪個zygote創(chuàng)建出來的。

應用進程被哪個zygote創(chuàng)建

Android系統(tǒng)從5.0開始支持64bit CPU,于是系統(tǒng)就有了zygote和zygote64兩個進程來分別創(chuàng)建32和64位的應用進程。怎么確定我的應用進程由哪個zygote啟動的?

簡單驗證一下:使用$adb shell ps | grep zygote 能看到兩個zygote和zygote64兩個進程。再使用$adb shell ps | grep <package_name>就能找到應用進程ID和父進程ID。如下圖:

$ adb shell ps | grep zygote
root      511   1     8760   768   __skb_recv 0000000000 S zygote
root      746   1     2118616 27044 poll_sched 0000000000 S zygote64
root      747   1     1554780 14612 poll_sched 0000000000 S zygote

$ adb shell ps | grep <package_name>
u0_a108   4162  746   1780048 95476 SyS_epoll_ 0000000000 S <package_name>

從輸出中看到應用的父進程PID是746,也就是說是被zygote64給fork出來的,就是說運行時環(huán)境是64位。

功能實現(xiàn)方案

要寫一個功能函數(shù)來判斷當前運行環(huán)境是64bit還是32bit,怎么實現(xiàn)呢?

最先想到的方法就是獲取到父進程PID,然后查到父進程名字,拿名字跟zygote64zygote字符串作對比。
問題是系統(tǒng)沒提供相關的接口(在Java中啟動ps進程,然后分析輸出日志應該可以達到目的,但是實現(xiàn)起來比較麻煩)。

另外,從/proc/<pid>/cmdline中也能看到進程名,但是應用進程沒有權限讀取zygote進程的cmdline。

有沒有系統(tǒng)接口呢?

有。但是只在6.0及以后的SDK上才提供。5.0上是不能用的。

加載動態(tài)庫的細節(jié)

回過頭來想一個問題:應用進程被zygote或zygote64啟動是由什么因素決定的?

主要有三個:

  • CPU ABI,就是CPU支持或兼容哪些架構
    • CPU支持的架構主要包括armeabi、armeabi-v7a、arm64-v8a、x86、mips。
  • Android OS,即系統(tǒng)是否支持64位運行環(huán)境
    • 5.0以上開始支持64bit CPU
  • 應用的動態(tài)庫類型
    • 應用程序本身提供的動態(tài)庫類型。比如,我的工程里有armeabi和arm64-v8a兩種。(如果應用不攜帶任何動態(tài)庫,運行時環(huán)境由系統(tǒng)自己來判斷)。

然后就是系統(tǒng)啟動應用時的決策過程和邏輯:

決策過程可以簡單概括為:

  1. PackageManagerService掃描應用程序時,先查看lib目錄下是否有動態(tài)庫,有哪種類型的動態(tài)庫。例如,在64位設備上:
  • 如果app的 lib 目錄下存在armeabi,則以32位兼容方式運行
  • 存在arm64-v8a 則以64位運行
  • 如果沒有任何 so,按照系統(tǒng)的默認配置決定,也就是64位運行。
  1. 以上決策過程的結果記錄在ApplicationInfo.primaryCpuAbi和ApplicationInfo.secondaryCpuAbi兩個Filed上。

  2. ActivityManagerServicestartProcessLocked()方法實現(xiàn)啟動應用。

  • 這個方法最終是向相應的zygote進程發(fā)出fork的請求 zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)。其中的參數(shù)abi就是第二步中提到的ApplicationInfo.primaryCpuAbi和secondaryCpuAbi。ABI參數(shù)不同,使用到的zygote進程也不同。
  1. 在確定zygote進程后,要加載的動態(tài)庫類型就能確定了。zygote64會加載64bit的動態(tài)庫,例如,/system/lib64/libart.so,還有應用程序的arm64-v8a下的動態(tài)庫。

所以,可以通過查看libart.so的路徑來判斷zygote的類型;也可以判斷手機的CPU ABI來判斷。

最終方案:

  • 6.0+:使用系統(tǒng)接口(since Android M)
    • android.os.Process.is64Bit()
  • 5.x:有兩個判斷方法
    • ① 根據(jù)應用進程依賴的libart.so的實際路徑來判斷:
      • 路徑是"/system/lib64/libart.so" 對應 zygote64
      • 路徑是"/system/lib/libart.so" 對應 zygote
    • ② 根據(jù)CPU ABI來判斷
      • 根據(jù)Build.SUPPORTED_ABIS返回的數(shù)組第一個值來判斷
        • arm64-v8a 對應 zygote64
        • armeabi-v7a,armeabi等 對應 zygote
  • 4.x:只能是32位環(huán)境。

(5.0上)詳細實現(xiàn)如下:

方法① 查找libart.so的路徑

使用ClassLoader的findLibrary方法來查找libart.so的路徑;該方法是隱藏函數(shù),所以需要使用反射。

說明:zygote64會加載/system/lib64/libart.so,zygote會加載"/system/lib/libart.so"

    private static boolean isART64() {

        final String tag = "is64ART";
        final String fileName = "art";

        try {
            ClassLoader classLoader = getContext().getClassLoader();
            Class<?> cls = ClassLoader.class;
            Method method = cls.getDeclaredMethod("findLibrary", String.class);
            Object object = method.invoke(classLoader, fileName);
            if (object != null) {
                return ((String)object).contains("lib64");
            }
        } catch (Exception e) {
            //如果發(fā)生異常就用方法②
            return is64bitCPU();
        }

        return false;
    }

方法② 根據(jù)CPU ABI來判斷

    private static boolean is64bitCPU() {
        String CPU_ABI = null;
        if (Build.VERSION.SDK_INT >= 21) {
            String[] CPU_ABIS = Build.SUPPORTED_ABIS;
            if (CPU_ABIS.length > 0) {
                CPU_ABI = CPU_ABIS[0];
            }
        } else {
            CPU_ABI = Build.CPU_ABI;
        }

        if (CPU_ABI != null && CPU_ABI.contains("arm64")) {
            return true;
        }

        return false;
    }

這個方法可能有一個缺陷:如果將32位的ROM安裝在支持arm64-v8a的機器上,查詢SUPPORTED_ABIS時依然得到的是arm64-v8a,而此時系統(tǒng)只能以32位環(huán)境來運行應用,這就出問題了。

但是,暫時沒法驗證這個種case,不好找這樣的ROM,因為廠商一般不會這么做。所以,這種情況暫時可以不用考慮。

參考文章

Android 64位兼容方式運行32位分析
騰訊Bugly干貨分享】動態(tài)鏈接庫加載原理及HotFix方案介紹
Android系統(tǒng)啟動-zygote篇
理解Android進程創(chuàng)建流程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容