判断当前进程运行时环境(32/64位)的方法

问题由来

最近项目里添加了64位动态库,即app/libs下的armeabiarm64-v8a两个存储动态库的目录。项目还有拷贝动态库的需要,即:从apk中将动态库拷贝到指定目录下,然后通过System.load()来加载。问题是:

应该拷贝apk中哪个版本的动态库呢?armeabi还是arm64-v8a

简单的回答:主要得看应用进程是被哪个zygote创建出来的。

应用进程被哪个zygote创建

Android系统从5.0开始支持64bit CPU,于是系统就有了zygote和zygote64两个进程来分别创建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出来的,就是说运行时环境是64位。

功能实现方案

要写一个功能函数来判断当前运行环境是64bit还是32bit,怎么实现呢?

最先想到的方法就是获取到父进程PID,然后查到父进程名字,拿名字跟zygote64zygote字符串作对比。
问题是系统没提供相关的接口(在Java中启动ps进程,然后分析输出日志应该可以达到目的,但是实现起来比较麻烦)。

另外,从/proc/<pid>/cmdline中也能看到进程名,但是应用进程没有权限读取zygote进程的cmdline。

有没有系统接口呢?

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

加载动态库的细节

回过头来想一个问题:应用进程被zygote或zygote64启动是由什么因素决定的?

主要有三个:

  • CPU ABI,就是CPU支持或兼容哪些架构
    • CPU支持的架构主要包括armeabi、armeabi-v7a、arm64-v8a、x86、mips。
  • Android OS,即系统是否支持64位运行环境
    • 5.0以上开始支持64bit CPU
  • 应用的动态库类型
    • 应用程序本身提供的动态库类型。比如,我的工程里有armeabi和arm64-v8a两种。(如果应用不携带任何动态库,运行时环境由系统自己来判断)。

然后就是系统启动应用时的决策过程和逻辑:

决策过程可以简单概括为:

  1. PackageManagerService扫描应用程序时,先查看lib目录下是否有动态库,有哪种类型的动态库。例如,在64位设备上:
  • 如果app的 lib 目录下存在armeabi,则以32位兼容方式运行
  • 存在arm64-v8a 则以64位运行
  • 如果没有任何 so,按照系统的默认配置决定,也就是64位运行。
  1. 以上决策过程的结果记录在ApplicationInfo.primaryCpuAbi和ApplicationInfo.secondaryCpuAbi两个Filed上。

  2. ActivityManagerServicestartProcessLocked()方法实现启动应用。

  • 这个方法最终是向相应的zygote进程发出fork的请求 zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)。其中的参数abi就是第二步中提到的ApplicationInfo.primaryCpuAbi和secondaryCpuAbi。ABI参数不同,使用到的zygote进程也不同。
  1. 在确定zygote进程后,要加载的动态库类型就能确定了。zygote64会加载64bit的动态库,例如,/system/lib64/libart.so,还有应用程序的arm64-v8a下的动态库。

所以,可以通过查看libart.so的路径来判断zygote的类型;也可以判断手机的CPU ABI来判断。

最终方案:

  • 6.0+:使用系统接口(since Android M)
    • android.os.Process.is64Bit()
  • 5.x:有两个判断方法
    • ① 根据应用进程依赖的libart.so的实际路径来判断:
      • 路径是"/system/lib64/libart.so" 对应 zygote64
      • 路径是"/system/lib/libart.so" 对应 zygote
    • ② 根据CPU ABI来判断
      • 根据Build.SUPPORTED_ABIS返回的数组第一个值来判断
        • arm64-v8a 对应 zygote64
        • armeabi-v7a,armeabi等 对应 zygote
  • 4.x:只能是32位环境。

(5.0上)详细实现如下:

方法① 查找libart.so的路径

使用ClassLoader的findLibrary方法来查找libart.so的路径;该方法是隐藏函数,所以需要使用反射。

说明: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) {
            //如果发生异常就用方法②
            return is64bitCPU();
        }

        return false;
    }

方法② 根据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,而此时系统只能以32位环境来运行应用,这就出问题了。

但是,暂时没法验证这个种case,不好找这样的ROM,因为厂商一般不会这么做。所以,这种情况暂时可以不用考虑。

参考文章

Android 64位兼容方式运行32位分析
腾讯Bugly干货分享】动态链接库加载原理及HotFix方案介绍
Android系统启动-zygote篇
理解Android进程创建流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容