Skip to main content

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文件名。