Android Apk大小和App性能一直是React Native Developers的主要关注点。 我们的应用程序是用React Native编写,目前Android版本的Apk Size已经达到80MB左右。 因此,想出一个不错的减小Apk Size解决方案来并提高应用程序性能是一个挑战。
在此博客中,我们将逐步介绍减小apk大小以及性能提高和应用程序启动时间减少的步骤。
一、JSBundle 及 Asset
随着0.59的发布,同时发布了新版本的JSC,新版0.59的最大优势在于,可以为64位架构生成APK。但是,随之带来的问题是包体积的增加,目前RN团队也正在努力从React Native中删除几个模块来进行优化。
1. Import Cost、删除无用的 import
import cost 作为 Visual Studio 插件, 使用它我们能够分析每个导入模块的成本。 然后,我们仅导入所需的模块,而不导入整个库。 并看到JS包可视化器发生了翻天覆地的变化,因此我们几乎对每个在JS包中占用较大空间的库都进行了更改。在管理 import 时,我们遇到了许多未使用的导入语句。 因此,删除这些也有助于我们减少JS包的大小。
2. 删除无用的第三方库
检查package.json文件下是否有多余的第三方依赖包, 并将其删除。
3. 封装重复性代码
编写一次代码并一次又一次地重复使用是最好的做法,可以更快地编写代码,扩展产品规模,并且还有助于防止JS捆绑包的大小增加。
4. 在Production环境过滤不用的模块, 资源
(1) 代码块
__DEV__ 用来区分测试与线上环境。我们可以借助它来实现在Production环境过滤不用的代码或资源。例如, 有一个自定义的A模块:
只需要在测试环境中将其编译到jsbundle文件中。我们可以这样做
loadAModule() {
if (__DEV__) {
require('A.module');
}
return null;
}
⚠️注意
loadAModule() {
if (!__DEV__) {
return null;
}
require('A.module');
}
上述代码中, 我们先判断不是测试环境返回null。仔细想想这段代码是否有问题呢? 执行react-native bundle打包后, 你会发现仍然可以找到A模块代码对应的moduleId。也同样证明了A模块被打进了jsbundle。所以, 在 React Native 中,可以通过 __DEV__ 在不同的环境(development还是 production)来有选择地加载一些JS文件。但是 require 的分支,必须放在对应的 if(__DEV__){}else{} 里,这样才能被RN打包的时候过滤掉。RN打包时,会删除 if/else 里的死码;但是在 if/else 之外的 require,即使永远不会被执行到,也会被静态解析并打进bundle中。
想了解更多关于 React (Native) 的死码,可以看这篇译文:[译]JavaScript中的development模式怎么实现
(2) Image资源
RN中的图片资源我们一般会进行统一管理(推荐), 在功能模块中引用。同样, 我们可以借助__DEV__来实现是否加载对等的图片资源
const inusrance = __DEV__ ? {
insuranceAnswerBg: require('../Images/insurance/insurance_answer_bg.png'),
} : null;
5. Metro-Bundler过滤代码
在RN0.57版本之后,打包工具由Package从RN-Ci中迁移出来, 统一使用Metro来实现。关于Metro更详细的使用方式、分析介绍可以参考我之前的文章:
同时, 新一代的Metro为开发者提供了 “插件式” 的功能使用方式。功能也更为丰富, 例如: transformer、serializer 等等, 我们可以借助 serializer 来实现在打包过程中, 对相应代码的过滤
// metro.config.js
const moduleArray = require('./metro.filter');
/**
* 对未使用的文件模块进行过滤
* return false 则过滤不编译
* @param {*} module
* @returns
*/
function postProcessModulesFilter(module) {
if (moduleFilter(module.path) >= 0) {
console.log(`代码过滤中: ${module.path}`);
return false;
}
return true;
}
/**
* 正则匹配module
* @param {*} path
* @returns
*/
function moduleFilter(path) {
for (const i in moduleArray) {
if (path.match(`/${moduleArray[i]}/`)) {
return i;
}
}
return -1;
}
/**
* Metro 配置
* @format
*/
module.exports = {
serializer: {
processModuleFilter: postProcessModulesFilter,
}
};
// metro.filter.js
// 配置在Production需要过滤的模块文件
module.exports = [
'Components/Insurance',
'Containers/Insurance',
'Components/ActivityEleven',
'Containers/ActivityEleven',
];
上述代码中, 我们使用 processModuleFilter 来过滤 metro.filter.js中声明的代码模块
⚠️注意
在使用processModuleFilter进行代码过滤时, 一定要保证被过滤的模块代码不能在任何地方使用, 否则打包出来的jsBundle仍会包含对应模块对import, 在解析时, 由于找不到对应对moduleId, 而解析失败导致应用Crash
二、Android
现在,在减小JS包大小之后,我们开始在android studio中使用“ APK分析器”来分析apk。 我们注意到,我们已经减小了index.android.bundle文件以及libs文件夹中的大量文件。 因此,我们开始进一步分析apk。
1. 试用新的R8代码收缩器
android.enableR8=true
代码压缩可以通过删除未使用的代码和资源来帮助减小APK的大小。 它比ProGuard更快地缩短代码。
2. 启用资源缩减
shrinkResources true
它有助于使用资源缩减来识别和删除不必要的资源。
3. ResConfigs
resConfigs "zh"
在构建应用程序时,它将删除所有其他本地化资源。
4. 压缩图像
aaptOptions {
cruncherEnabled = false
}
要压缩应用中使用的图片资源,可以在android {}部分中将此行添加到 build.gradle
5. MinifyEnabled
开启proguard混淆, 如果您将 minifyEnabled
属性设为 true
,R8 会将来自所有可用来源的规则合并在一起。
6. zipAlignEnabled
启用zipAlign压缩优化