expo新版本(54+)的Android打包apk尺寸和splits控制法
问题
之前为了控制expo打包apk文件的尺寸问题,采用了在原生android代码中改写配置文件的方法,加入splits项,实现各平台apk分离,以大幅减少总体生成体积。 这个方法我还做了B站视频。 这种方法虽说有效,但挺不优雅的,而且很麻烦。每次新项目建立都要手写一遍,一旦prebuild就废了,又要补充。 expo SDK54+后,我发现这个方法失效了,原因似乎在于新版本eas build不再直接读取android目录的配置,而是类似sandbox机制,每次自动生成所需资源和配置,然后打包。 expo官方至今并未给出对splits这样的直接支持。 其实想想呢原因很简单,除中国以外的安卓市场,目前都基于Google Play的标准,直接用aab即可,EAS只要支持生成aab文件,后续的分发等靠Goolge Play等平台优雅的分配即可,版本和体积控制等方面都是非常高效方便优雅的。 如果你的项目只是面向海外,不考虑国内小米华为OPPO等平台,那么后续的内容就不必看了。 否则,我们仍然需要一个更好的打包apk的办法,下面就介绍。
思路
咨询了ChatGPT,它居然很不靠谱地推荐用bundletool这个工具包,官网说“bundletool 可将 app bundle 转换为部署到设备的各种 APK。”,这是个天坑,除非参数中带有--mode=universal,打出来的apks文件是无法直接安装的,但这个模式下没法分离平台,体积和直接eas打包apk一样大,毫无意义。
然后我们又问了Claude-Opus-4.5,不愧是开发顶流,给出更好的答案,其有效性和优雅程度超过了expo旧版本时的我们的老方法。
希望你在读下去前可以看看我之前的B站视频,原理其实是一样的。
解决
1. 创建配置插件
expo项目根目录下,创建 plugins/withAndroidSplits.js:
const { withAppBuildGradle } = require('@expo/config-plugins');
const withAndroidSplits = (config) => {
return withAppBuildGradle(config, (config) => {
const buildGradle = config.modResults.contents;
const splitsConfig = `
splits {
abi {
enable System.getenv('ENABLE_ABI_SPLITS') == 'true'
reset()
include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
universalApk false
}
}
`;
if (!buildGradle.includes('splits {')) {
config.modResults.contents = buildGradle.replace(
/android\s*{/,
`android {\n${splitsConfig}`
);
}
return config;
});
};
module.exports = withAndroidSplits;
这里说一下原理:
splitsConfig一段是把按平台分开打包的配置写入android/app/build.gradle文件, 之前判断一下是否带有环境变量ENABLE_ABI_SPLITS(更优雅);
写成一个js插件,在下面的项目配置中我们会在打包时调用它,生成我们想要的配置和资源。
可以用expo prebuild --clean命令来验证其效果。
2.配置 app.json
{
"expo": {
"plugins": [
"./plugins/withAndroidSplits"
]
}
}
3. 配置 eas.json
{
...
"build": {
"production-splits": {
"android": {
"buildType": "apk",
"env": {
"ENABLE_ABI_SPLITS": "true"
}
},
"distribution": "internal"
}
}
}
4.EAS打包
注意,如果采用EAS云端打包:
eas build --platform android --profile production-splits
打包完成后,EAS 会提供下载链接,但云端打包默认只返回一个 APK。要获取所有分架构 APK,建议使用本地打包。
使用本地打包(推荐,可获取所有分架构 APK)
eas build --platform android --profile production-splits --local
补充
上述方法已经把基本原理和步骤说清,但真实的使用过程中可能还会有一些需要补充的注意事项。
1.签名
本地打包需要配置签名。在 android/app/build.gradle 中确保有签名配置,或者在 eas.json 中配置 credentialsSource。
{
"build": {
"production-splits": {
"android": {
"buildType": "apk",
"credentialsSource": "local",
"env": {
"ENABLE_ABI_SPLITS": "true"
}
}
}
}
}
具体签名方法,关注我B站expo课程。
2.打包库镜像
如果你在国内,可能会遇到默认的maven等库网络无法连接问题。
之前我们也是直接修改android原生配置,现在有更优雅的方法,直接在前面的plugins代码中加入即可。
我们可以写一个新的插件来替代:withAndroidBuildTweaks.js。
const {
withAppBuildGradle,
withProjectBuildGradle,
} = require('@expo/config-plugins');
const withAndroidBuildTweaks = (config) => {
/**
* 1️⃣ 修改 android/app/build.gradle(ABI splits)
*/
config = withAppBuildGradle(config, (config) => {
const contents = config.modResults.contents;
const splitsConfig = `
splits {
abi {
enable System.getenv('ENABLE_ABI_SPLITS') == 'true'
reset()
include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
universalApk false
}
}
`;
if (!contents.includes('splits {')) {
config.modResults.contents = contents.replace(
/android\s*{/,
`android {\n${splitsConfig}`
);
}
return config;
});
/**
* 2️⃣ 修改 android/build.gradle(Maven repositories)
*/
config = withProjectBuildGradle(config, (config) => {
let contents = config.modResults.contents;
if (!contents.includes('maven.aliyun.com')) {
contents = contents.replace(
/allprojects\s*{\s*repositories\s*{/,
`allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
`
);
}
config.modResults.contents = contents;
return config;
});
return config;
};
module.exports = withAndroidBuildTweaks;
后面一段在android/build.gradle文件中加入了国内maven库镜像。
记得改app.json中相应plugins文件名。