React Native 包体积优化实践

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压缩优化

发布了214 篇原创文章 · 获赞 371 · 访问量 92万+

猜你喜欢

转载自blog.csdn.net/u013718120/article/details/105346652