阅读 1616

Android 适配 64 位架构

背景

64位的应用性能更好,也能运行在未来仅支持 64 位架构的设备上。目前各个应用市场也对 64 适配提出了要求。

Google Play:

自 2019 年 8 月 1 日起,在 Google Play 上发布的应用必须支持 64 位架构。

国内:

小米应用商店与OPPO应用商店、vivo应用商店等已经发出通知

  • 2021年12月底:现有和新发布的应用/游戏,需上传包含64位包体的APK包(支持双包在架,和64位兼容32位的两个形式,不再接收仅支持32位的APK包)
  • 2022年8月底:硬件支持64位的系统,将仅接收含64位版本的APK包
  • 2023年底:硬件将仅支持64位APK,32位应用无法在终端上运行

32 和 64 位区别

这里需要先说一下 CPU 类型,每种 CPU 类型对应了一种 ABI(Application Binary Interface),常见的 abi 有 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 等。

  • armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多,基本可以淘汰了
  • armeabiv-v7a: 第7代及以上的 ARM 处理器。2011年15月以后的生产的大部分Android设备都使用它
  • arm64-v8a: 第8代、64位ARM处理器
  • x86: 平板、模拟器用得比较多
  • x86_64: 64位的平板

这里主要看 arm 架构的,新的架构能够兼容旧的 abi 对应的 so,例如 arm64-v8a 架构的 CPU 能够运行 armeabi-v7a 架构的 so,反过来不行,这就是为什么现在很多 APP 只包含 armeabi-v7a 的包但是能够在最新的 CPU 上运行。如果以后的 CPU 不再兼容旧的架构了的话,现在只包含 armeabi 或者 armeabi-v7a 架构的 APP 就不能再运行了。

那如果同时包含两种架构呢?如果是支持 64 位系统的机器,会有两个Zygote(一个32位,一个64位)进程同时运行。APP 安装的时候根据 lib 目录里面支持的架构和机器自己的 CPU 类型来决定 primaryCpuAbi,在启动的时候会根据安装时候确定的 primaryCpuAbi 的值来决定是从64位还是32位的Zygote进程fork出子进程,如果从 64的 fork,则是以64位模式运行。

是否已满足 64 位要求

如果没有使用任何原生代码,那就已经满足 64 位的要求了。如何查看是否有使用原生库?,比较快捷的方法是使用 Android Studio 提供的 APK 分析器。入口在菜单 Build > Analyze APK…

查看 lib 文件夹,如果里只有 armeabi 或者 armeabi-v7a 文件夹,就是只支持 32 位,不支持 64 位。 如果同时还有 arm64-v8a 文件夹,则说明有 64 位原生库,是否与 32 位有相同的功能和质量,还需要进行测试。

快速找出不支持 64 位的原生库

应用内的原生库的来源一般有三处:

  • 第三方库
  • 工程内的 so 文件
  • C/C++ 源码模块

目前很多第三方库已经同时支持 32 和 64 位了,但是有些还不支持,如何找出这部分不支持的库或者文件呢?如果项目中的 so 文件数量很多,就很难通过肉眼的方式来查找,这里提供一个 gradle 脚本可以很方便快速得找出那些不支持 64 位的库。 在主模块的 build.gradle 最后面添加如下代码:

tasks.whenTaskAdded { task ->
    if (task.name=='mergeDebugNativeLibs') {
        task.doFirst {
            println("==========================================================")
            def v7a = []
            def arm64 = []
            it.inputs.files.each { file ->
                if (file.absolutePath.endsWith("/jni")) {
//                    println("==========" + file.absolutePath)
                    if (file.isDirectory()) {
                        file.listFiles().each { soFileDir ->
                            if (soFileDir.absolutePath.contains("armeabi-v7a")) {
                                if (soFileDir.isDirectory()) {
                                    soFileDir.listFiles().each {
                                        println(it.absolutePath)
                                        v7a.add(it.name)
                                    }
                                }
                            }
                            if (soFileDir.absolutePath.contains("arm64-v8a")) {
                                if (soFileDir.isDirectory()) {
                                    soFileDir.listFiles().each {
                                        println(it.absolutePath)
                                        arm64.add(it.name)
                                    }
                                }
                            }
                        }
                    }
                }
            }
            println("v7a size: ${v7a.size()}")
            println("arm64 size: ${arm64.size()}")
            println("so in v7a, but not in arm64:")
            v7a.each {
                if (!arm64.contains(it)) {
                    println("$it")
                }
            }
            println("==========================================================")
        }
    }
}
复制代码

然后执行 .\gradlew assembleDebug,这里的 Debug 可以根据实际项目中的 Flavor 进行替换。Demo 工程的输出如下:

> Task :app:mergeDebugNativeLibs
==========================================================
xxx\5d0e00f6a703ec622708978bebed0322\mmkv-static-1.2.8\jni\arm64-v8a\libmmkv.so
xxx\5d0e00f6a703ec622708978bebed0322\mmkv-static-1.2.8\jni\armeabi-v7a\libmmkv.so
xxx\7206efbbb5863dbe5969c1c384b81177\openDefault-4.2.7\jni\armeabi-v7a\libweibosdkcore.so
xxx\ccd11b53aab95a933b06ef9e74f9fb44\sentry-android-ndk-3.1.3\jni\arm64-v8a\libsentry-android.so
xxx\ccd11b53aab95a933b06ef9e74f9fb44\sentry-android-ndk-3.1.3\jni\arm64-v8a\libsentry.so
xxx\ccd11b53aab95a933b06ef9e74f9fb44\sentry-android-ndk-3.1.3\jni\armeabi-v7a\libsentry-android.so
xxx\ccd11b53aab95a933b06ef9e74f9fb44\sentry-android-ndk-3.1.3\jni\armeabi-v7a\libsentry.so
v7a size: 4
arm64 size: 3
so in v7a, but not in arm64:
libweibosdkcore.so
==========================================================
复制代码

从输出可以快速看出哪些已经支持 64 位,找到哪些还不支持 64 位的 so 文件,以及他们所在的路径。

Demo 工程见:github.com/callmepeanu…

适配 64 位

找到不支持 64 位的 so 列表后,如果是第三方库或者从外部引入的 so 文件,需要看是否有适配过 64 位的新版本,或者找提供方获取支持 64 位的版本。 如果是自己项目中的 C/C++ 源码编译出来的,需要在编译选项和源码层面做对 64 位架构的适配并生成对应架构的 so 库文件。

打包

可以把所有支持的 abi 的 so 都打在一个包里,这样一个安装包就可以适配所有设备,缺点就是包体积会增大,特别是原生库比较多的情况。 目前应用市场提供了分别上传32位兼容包和64位包的能力,所以可以利用构建多个 APK 的能力来打出支持不同 abi 的包,应用市场根据用户手机 CPU 类型分发对应的包,可以减少用户下载包的大小。 支持构建多个 APK 只需要在 build.gradle 中添加如下配置:

android {
	
	...
		
    splits {
        abi {
            enable true
            reset()
            include 'armeabi-v7a', 'arm64-v8a'
            universalApk false
        }
    }
}
复制代码

生成的安装包如下图所示:

image.png

参考:

文章分类
Android
文章标签