Android分包配置

ABI 分包

Posted by Tan Lin on April 23, 2019

前言

最近遇到一个RN项目打包问题,明明很少的业务功能,release包竟然达到43MB,简直不敢相信。以为是minify没有设置为true导致无用代码较多,一些 发现gradle文件里确实也没有设置为true,改之,无果…

突然想起,之前提到过,so库文件平台包过多会导致安装包体积庞大,一看,果然splits下的abi块include了全部平台,仅保留armeabi-v7a 然后打包,仍然40多MB…

究竟是哪里的问题呢?

Gradle splits

ABI(Application Binary Interface) 硬件平台不同,底层库的实现就有可能不同。 为了兼容各个平台,同一个底层实现就会有不同的版本,运行到相应的机型上,系统自动选择依赖库,必然造成没有用到的库页眉包含在apk里,造成不必要的流量浪费

Density,众所周知,Android的屏幕碎片化及其严重,DPI[Dots Per Inch] 表示设备的分辨率,属于普通屏/高清屏/超高清屏。—-DIP[Device Independent Pixel]:(布局单位DP) dp->px 0.75 1 1.5 2 3 4 dpi 120->160-240-320-480-640 type ldpi->mdpi-hdpi-xhdpi=xxhdpi-xxxhdpi

因此,分包有这3种方式:density abi density+abi

app/build.gradle

apply plugin: 'com.android.application'

// 这是density和abi混合的写法
android {
    deaultConfig {}
    splits {
        density {
            enable true
            reset()
            include "mdpi", "hdpi"
        }
        abi {
            enable true
            reset()
            include "x86", "x86_64"
        }
    }
}

The android developer website abi-split

By default, when Gradle generates multiple APKs, each APK will have the same version information, as specified in the module-level build.gradle file. Because the Google Play Store does not allow multiple APKs for the same app that all have the same version information, you need to ensure each APK has its own unique versionCode before you upload to the Play Store.

// The output from the example configuration includes the following 4 APKs:

1. app-hdpiX86-release.apk: Contains code and resources for hdpi density and x86 ABI only.
2. app-hdpiX86_64-release.apk: Contains code and resources for hdpi density and x86_64 ABI only.
3. app-mdpiX86-release.apk: Contains code and resources for mdpi density and x86 ABI only.
4. app-mdpiX86_64-release.apk: Contains code and resources for mdpi density and x86_64 ABI only.

app/build.gradle

apply plugin: 'com.android.application'

// 这是只针对abi分包的写法
android {
    deaultConfig {}
    splits {
        abi {
            enable true
            reset()
            include "armeabi-v7a", x86", "arm64-v8a", "x86_64"
            // Specifies that we do not want to also generate a universal APK that includes all ABIs.
            // 只有abi才有这个属性,density是没有的
            universalApk false
        }
    }
    // versionCode=1
    // 如果要上传到`Google Play Store`上,同一个应用的同一个版本号只能存在一个文件,如果不做经过以下步骤处理,无法确定用户应该安装哪个apk
    // 这样编译出来后的apk外观上没啥变化,但是通过反编译可以看出,版本号是1001, 2001, 3001, 4001
    // 如果,universalApk 设置为true,版本号应该比这些版本号都小。以便让google play优先选择符合用户设备的apk,如果没找到再fallback到这个通用的apk
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
            def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) { // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1000 + defaultConfig.versionCode
            }
        }
    }
    // universalApk=true 时的特殊处理
    // TODO
}

Gradle ndk

另一个同事也发现是abi的问题,他在buildTypes块的release里添加了如下代码,效果显著:编译出的release包瞬间降到12MB左右。奇怪在我的印象中, ndk一般不是在代码里需要些native代码的时候才需要添加吗,RN里没有jni相关的代码,怎么也会有ndk的配置???

细想,自己写jni代码的时候,也是只写了C++代码然后交给ndk编译出各种平台的so库,似乎明白了:项目中其他库使用了jni然后再制定只生成”armeabi-v7a”平台的库

    ndk {
        "armeabi-v7a"
    }

解压后,apk里确实只有”armeabi-v7a”这一个库文件

初步结论

按照abi分包之后,会生成不同的apk包,但需要注意这些apk的版本号需要修改,特别注意的是xxx-universal.apk这个包的版本号应该比其他几个apk的版本号低 由于RN项目默认禁用abi分包,亦即会将所有平台的so都添加到apk文件里,所以添加的 ndk { "armeabi-v7a } 就是告诉编译器,只需要生成 armeabi-v7a 的动态库

首选abi/次选abi

so (shared object): 由C/C++编译生成

armeabi设备只兼容armeabi
armeabi-v7a设备兼容armeabi-v7a、armeabi
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi
x86设备兼容x86、armeabi
mips64设备兼容mips64、mips
mips只兼容mips

armeabi 是大部分CPU都兼容的,除了mips和mips64外,在非armeabi设备上运行性能和兼容性是有损耗的。
64位架构的CPU向下兼容其对应的32位。

| CPU 架构 | 支持的ABI | 备注 | |: —— :|: ——- :|: ——-:| |ARMv5|armeabi|| |ARMv7|armeabi-v7a、armeabi|| |ARMv8|arm64-v8a、armeabi-v7a、armeabi| |x86|x86|Binary Translator| |x86_64|x86_64、x86|Binary Translator| |MIPS|mips|| |MIPS64|mips64、mips|| 值得注意的是原本x86架构的CPU是不支持运行arm架构的native代码的,但英特尔和谷歌合作在x86机子的系统内核层之上加入了Binary Translator(二进制转换器), 所以x86机子也能跑armeabi的动态库。因此完整表格可能像这样

注意

如果所有的so库都包含在应用中,应用在安装的时候会选择主abi,没有就会选择副abi,还没找到的话也会正常安装应用,只是运行时会报错,找不到so库

参考

Android中的ABI
Intel-Binary Translator