为了实现和探究ReactNative的分包功能,以及构建一个 相对从性能上 和 技术上都比较ok 的项目架构 而存在的一个库。你可以把它理解为一个 App的技术架构 方案。
1.1 注意集成的时候 和 发build 的时候 权限问题
你需要注意的点 权限问题,Error调试弹出层Activey,Http在deb模式i下是否安全的问题
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapprnn">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyappRNN"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity
android:name=".MainActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
1.2 onCreate 怎么样才是完整的?
实际上 在 官方的文档中 这里的代码是不完整的,比较全的代码在这里
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
}
SoLoader.init(this, false);
mReactRootView = new ReactRootView(this);
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
// 有一些第三方可能不能自动链接,对于这些包我们可以用下面的方式手动添加进来:
// packages.add(new MyReactNativePackage());
// 同时需要手动把他们添加到`settings.gradle`和 `app/build.gradle`配置文件中。
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的MyReactNativeApp 必须对应"index.js"中的
// "AppRegistry.registerComponent()"的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
- build 的时候到底如何做呢?
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/
这个是官方给的shell 但实际上,对于我们的这个项目而言,它是这样构建的(等一下你问我怎么知道这样改是正确的?看RN的源码啦 弟弟,好吧有时间我会出一个文章来详解源码的细节)
react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res/
- assets 资源在android 中到底如何进行的呢?
通过分析原来的项目,发现都是build 的阶段 会生成一份固定的资源路径文件,这里是一份详细的流程说明 ,https://juejin.cn/post/7113713363900694565,但是实际上打出来的包还是会在res资源下 进行载入,文件的名称变化而已,对于常用的 build apk 包查看是否符合预期,可以尝试使用 反编译工具进行查看 🔧https://cloud.tencent.com/developer/article/1904018
基于此建议业务包都采取http 加载资源
- 关于native 模块的集成
首先第一点要说明的就是 react-native 的cli 更新到9.x版本它不在支持 link ,什么意思呢?也就是说 “yarn react-native link xxx”会报错哈,
我们这里选用 react-native-device-info 做native 模块来验证,是否可用, 有下面几点需要注意
-
9.x 下的cli 不需要link 只需要 yarn add 就完了 ,很方便
-
注意把 settings.gradle 中的配置改了 (7.x 的 gradle 的管理方式不一样!不改的话,会报错的哈),注意要重载一下gradle ,到此为止 你的Android Native 模块已经可以正常使用了
dependencyResolutionManagement { // 注意这里 要去掉 请见一个 github 的issuss https://github.com/realm/realm-java/issues/7374 ,以及 7.0 下 gradle 的管理文档 // repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
-
然后把 权限 加上 因为要读区mac 地址,所以 设备的wifi 权限要授予
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
然后就用 TestNativeInfo.js 下的代码跑就好了。
要想完善拆包方案,就必须对包 和RN的运行原理有所了解
1.1 说到拆包我们先了解 “包” 是什么, 由 什么组成
一个 包 bundle 说白了 就说 一些js 代码,只不过后缀叫 bundle ,它实际上是一些js 代码,只不过这些代码的运行 环境在RN 提供的环境 不是在浏览器,通过这些代码RN 引擎可以使用 Native 组件 渲染 出你想要的UI ,好 这就是 包 bundle。
一个rn 的bundle 主要由三部分构成
-
环境变量 和 require define 方法的预定义 (polyfills)
-
模块代码定义 (module define)
-
执行 (require 调用)
1.2 从一个简单的 RNDemo 分析 一个 简单的bundle 的构建
在根目录 下有一个RNDemo =>
import { StyleSheet, Text, View, AppRegistry } from "react-native";
class BU1 extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>BU1 </Text>
</View>
);
}
}
const styles = StyleSheet.create({
// -----省略
});
AppRegistry.registerComponent("Bu1Activity", () => BU1);
执行build 之后 ,我们来分析bundle 嘛
yarn react-native bundle --platform android --dev false --entry-file ./RNDemo.js --bundle-output ./android/app/src/main/assets/rn.android.bundle --assets-dest ./android/app/src/main/res --minify false --reset-cache
# 上面有几个参数 --minify false 不要混淆,--reset-cache 清理缓存 具体的可以看 @react-native-community/cli 源代码
首先我们前面说过 个rn 的bundle 主要由三部分构成 (polyfills、defined、require )
先看第一部分 polyfills 它从第 1行 一直到 第 799 行
// 第一句话
var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),
__DEV__=false,
process=this.process||{},
__METRO_GLOBAL_PREFIX__='';
process.env=process.env||{};
process.env.NODE_ENV=process.env.NODE_ENV||"production";
//可以看到 它定义了 运行时的基本环境变量 __BUNDLE_START_TIME__、__DEV__、__METRO_GLOBAL_PREFIX__..... 其作用是给RN 的Native 容器识别的 ,我们这里不深入,你只需要 知道没有这个 RN 的Native 容器识别会异常! 报错闪退
// 解析来 是三个闭包立即执行 函数 ,重点是第一个 它定义了 __r ,__d, 这两个函数 就说后面 模块定义 和 模块执行的关键函数
global.__r = metroRequire;
global[__METRO_GLOBAL_PREFIX__ + "__d"] = define;
metroRequire.packModuleId = packModuleId;
var modules = clear();
function clear() {
modules = Object.create(null);
return modules;
}
var moduleDefinersBySegmentID = [];
var definingSegmentByModuleID = new Map();
// 下面的说 __r 的主要定义
function metroRequire(moduleId) {
var moduleIdReallyIsNumber = moduleId;
var module = modules[moduleIdReallyIsNumber];
return module && module.isInitialized ? module.publicModule.exports : guardedLoadModule(moduleIdReallyIsNumber, module);
}
// 可以看到上述函数 的作用是 从 module(在下称它为 模块组册表 )看看 是否已经初始化 了 ,如果是 就导出 (exports) 如果没有就 加载一次 (guardedLoadModule)
function guardedLoadModule(moduleId, module) {
if (!inGuard && global.ErrorUtils) {
inGuard = true;
var returnValue;
try {
returnValue = loadModuleImplementation(moduleId, module);
} catch (e) {
global.ErrorUtils.reportFatalError(e);
}
inGuard = false;
return returnValue;
} else {
return loadModuleImplementation(moduleId, module);
}
}
// 上述函数 最重要的事情 就是 执行 loadModuleImplementation 函数,传递 moduleId 和 module
function loadModuleImplementation(moduleId, module) {
if (!module && moduleDefinersBySegmentID.length > 0) {
var _definingSegmentByMod;
var segmentId = (_definingSegmentByMod = definingSegmentByModuleID.get(moduleId)) !== null && _definingSegmentByMod !== undefined ? _definingSegmentByMod : 0;
var definer = moduleDefinersBySegmentID[segmentId];
if (definer != null) {
definer(moduleId);
module = modules[moduleId];
definingSegmentByModuleID.delete(moduleId);
}
}
var nativeRequire = global.nativeRequire;
if (!module && nativeRequire) {
var _unpackModuleId = unpackModuleId(moduleId),
_segmentId = _unpackModuleId.segmentId,
localId = _unpackModuleId.localId;
nativeRequire(localId, _segmentId);
module = modules[moduleId];
}
if (!module) {
throw unknownModuleError(moduleId);
}
if (module.hasError) {
throw moduleThrewError(moduleId, module.error);
}
module.isInitialized = true;
var _module = module,
factory = _module.factory,
dependencyMap = _module.dependencyMap;
try {
var moduleObject = module.publicModule;
moduleObject.id = moduleId;
factory(global, metroRequire, metroImportDefault, metroImportAll, moduleObject, moduleObject.exports, dependencyMap);
{
module.factory = undefined;
module.dependencyMap = undefined;
}
return moduleObject.exports;
} catch (e) {
module.hasError = true;
module.error = e;
module.isInitialized = false;
module.publicModule.exports = undefined;
throw e;
} finally {}
}
// 上述 重要的函数就是 factory(global, metroRequire, metroImportDefault, metroImportAll, moduleObject, moduleObject.exports, dependencyMap); 。它复杂执行模块的代码 ,好了 到这里为止我们就够了,现在不用分析太深入,要特别注意的是 factory 不是 定义好的函数,而是传入 的函数 ! factory = _module.factory, 具体点来说,它的执行是依据每个模块 的传入参数来执行的
// 然后我们来看看 __d define ,这个东西就比较的简单了
function define(factory, moduleId, dependencyMap) {
if (modules[moduleId] != null) {
return;
}
var mod = {
dependencyMap: dependencyMap,
factory: factory,
hasError: false,
importedAll: EMPTY,
importedDefault: EMPTY,
isInitialized: false,
publicModule: {
exports: {}
}
};
modules[moduleId] = mod;
}
// 可以看到这个非常的简单,就是在 组册表(modules)中 添加 对应的 模块
我们再来看看 重要的 一个 module 的定义是如何实现的
// 为了方便起见 我们直接找到 BU1 组件的声明 通过全局搜索🔍 我们找到了这个 定义,他在 802 -> 876 行
// 我们先看他 __d 参数部分 ,它 的执行器 factory = fn,模块id = 0 , 依赖模块的Map(别的依赖模块的 moduleId) = [1,2,3,4,6,9,10,12,179]
__d(fn,0,[1,2,3,4,6,9,10,12,179])
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
var _interopRequireDefault = _$$_REQUIRE(_dependencyMap[0]);
var _classCallCheck2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[1]));
var _createClass2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[2]));
var _inherits2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[3]));
var _possibleConstructorReturn2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[4]));
var _getPrototypeOf2 = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[5]));
// 下面三个模块 是 react -> react-native -> jsxRuntime 的重要模块 !分包负责 核心加载 RN 以来,JSXruntime 解析
var _react = _interopRequireDefault(_$$_REQUIRE(_dependencyMap[6]));
var _reactNative = _$$_REQUIRE(_dependencyMap[7]);
var _jsxRuntime = _$$_REQUIRE(_dependencyMap[8]);
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
// BU1 组件编译后的渲染就是 这一坨
var BU1 = function (_React$Component) {
(0, _inherits2.default)(BU1, _React$Component);
var _super = _createSuper(BU1);
function BU1() {
(0, _classCallCheck2.default)(this, BU1);
return _super.apply(this, arguments);
}
(0, _createClass2.default)(BU1, [{
key: "render",
value: function render() {
return (0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.container,
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.hello,
children: "BU1 "
})
});
}
}]);
return BU1;
}(_react.default.Component);
// 我们自己写的styles 函数
var styles = _reactNative.StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
height: 100
},
hello: {
fontSize: 20,
textAlign: "center",
margin: 10
},
imgView: {
width: "100%"
},
img: {
width: "100%",
height: 600
},
flatContainer: {
flex: 1
}
});
// RNDemo 的 registerComponent 函数
_reactNative.AppRegistry.registerComponent("Bu1Activity", function () {
return BU1;
});
},0,[1,2,3,4,6,9,10,12,179]);
最后 就是RNDemo 的__r 执行了
__r(27);
// 27 这个模块id 我们可以去看看它在做什么
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
'use strict';
var start = Date.now();
_$$_REQUIRE(_dependencyMap[0]);
_$$_REQUIRE(_dependencyMap[1]);
_$$_REQUIRE(_dependencyMap[2]);
_$$_REQUIRE(_dependencyMap[3]);
_$$_REQUIRE(_dependencyMap[4]);
_$$_REQUIRE(_dependencyMap[5]);
_$$_REQUIRE(_dependencyMap[6]);
_$$_REQUIRE(_dependencyMap[7]);
_$$_REQUIRE(_dependencyMap[8]);
_$$_REQUIRE(_dependencyMap[9]);
_$$_REQUIRE(_dependencyMap[10]);
_$$_REQUIRE(_dependencyMap[11]);
var GlobalPerformanceLogger = _$$_REQUIRE(_dependencyMap[12]);
GlobalPerformanceLogger.markPoint('initializeCore_start', GlobalPerformanceLogger.currentTimestamp() - (Date.now() - start));
GlobalPerformanceLogger.markPoint('initializeCore_end');
},27,[28,29,30,32,56,62,65,70,101,105,106,116,78]);
// 这个 模块,可以这样理解,它实际上是 在执行 initializeCore_start,初始化的工作 initializeCore,预载入一些系统 模块
// 直接就是执行 RNDemo 1 的模块代码了 具体的细节这里就不说了,核心就是 执行 模块中 的factory 代码 既__d 的第一个参数fn
__r(0);
1.3 从 刚才的demo 我们来看 metro 的打包工作流
我们了解完 bundle 的生成之后,不妨陷入了一个思考 🤔 这些模块id 如何生成的呢?
首先我们看命了行
yarn react-native bundle
--platform android
--dev false
--entry-file ./RNDemo.js
--bundle-output ./android/app/src/main/assets/rn.android.bundle
--assets-dest ./android/app/src/main/res
--minify false
--reset-cache
// 我们不妨找一下 react-native cli 的源码 它位于/node_modules/bin 下的目录(为什么是bin 目录?你对node 不熟悉,请去补充一下node 相关的知识)
'use strict';
var cli = require('@react-native-community/cli');
if (require.main === module) {
cli.run();
}
module.exports = cli;
// 可以看到 实际上就是执行 @react-native-community/cli 里的 cli
// 然后我们去看看 官方,仓库源代码 仓库里有一份清晰的文档说明,详细的描述里 每个参数的作用 ,这里不详细的解了
// 我们找到源代码仓库 .cli/ 里面有一个bin bin 里有一个run ,run 函数定义在 index 中
async function run() {
try {
await setupAndRun();
} catch (e) {
handleError(e);
}
}
async function setupAndRun() {
....
// 重点函数 从 detachedCommands 添加更多的 command
for (const command of detachedCommands) {
attachCommand(command);
}
....
}
// command 在 commands index 中 于是我们发现了
import {Command, DetachedCommand} from '@react-native-community/cli-types';
import {commands as cleanCommands} from '@react-native-community/cli-clean';
import {commands as doctorCommands} from '@react-native-community/cli-doctor';
import {commands as configCommands} from '@react-native-community/cli-config';
import {commands as metroCommands} from '@react-native-community/cli-plugin-metro';
import profileHermes from '@react-native-community/cli-hermes';
import upgrade from './upgrade/upgrade';
import init from './init';
export const projectCommands = [
...metroCommands,
...configCommands,
cleanCommands.clean,
doctorCommands.info,
upgrade,
profileHermes,
] as Command[];
export const detachedCommands = [
init,
doctorCommands.doctor,
] as DetachedCommand[];
// 我们找到 cli-plugin-metro 是我们需要的因为 在其文件夹下 我们发现了start 和bundle 两个command
// 解析来 我们找到了它的调用链
import Server from 'metro/src/Server';
const server = new Server(config);
try {
const bundle = await output.build(server, requestOpts);
await output.save(bundle, args, logger.info);
}
// 然后我们来看 Server metro 仓库的 Server 中
class Server {
constructor(config, options) {
this._config = config;
this._serverOptions = options;
if (this._config.resetCache) {
this._config.cacheStores.forEach((store) => store.clear());
this._config.reporter.update({
type: "transform_cache_reset",
});
}
this._reporter = config.reporter;
this._logger = Logger;
this._platforms = new Set(this._config.resolver.platforms);
this._isEnded = false; // TODO(T34760917): These two properties should eventually be instantiated
// elsewhere and passed as parameters, since they are also needed by
// the HmrServer.
// The whole bundling/serializing logic should follow as well.
this._createModuleId = config.serializer.createModuleIdFactory();
this._bundler = new IncrementalBundler(config, {
hasReducedPerformance: options && options.hasReducedPerformance,
watch: options ? options.watch : undefined,
});
this._nextBundleBuildID = 1;
}
//....
// 诶 重点代码 _createModuleId ,创建 ModuleId 但 它从那儿来呢?我们回到执行的地方 @react-native-community/的 cli-plugin-metro中 找到 buildBundle, 它就是命令 执行的地方
// 这个函数下 loadMetroConfig 返回一个config 我们看看 loadMetroConfig 在干什么
async function buildBundle(
args: CommandLineArgs,
ctx: Config,
output: typeof outputBundle = outputBundle,
) {
const config = await loadMetroConfig(ctx, {
maxWorkers: args.maxWorkers,
resetCache: args.resetCache,
config: args.config,
});
return buildBundleWithConfig(args, config, output);
}
export default function loadMetroConfig(
ctx: ConfigLoadingContext,
options?: ConfigOptionsT,
): Promise<MetroConfig> {
const defaultConfig = getDefaultConfig(ctx);
if (options && options.reporter) {
defaultConfig.reporter = options.reporter;
}
// 发现这里有一个 loadConfig
return loadConfig({cwd: ctx.root, ...options}, defaultConfig);
}
// loadConfig 从 metro 里 来 通过调用链我们锁定了 这行代码
const getDefaultConfig = require('./defaults');
// 它里面正好有一个
const defaultCreateModuleIdFactory = require('metro/src/lib/createModuleIdFactory');
// 然后我们先不阅读 具体内容,鉴于 爱metro 和 cli 中反复 跳 我们先理解metro
首先我们在metro 官网找到了 相关的 build 构建流程 (https://facebook.github.io/metro/docs/concepts)。主要分下面几个阶段
- Resolution (依据入口文件 解析,他于Transformation 是并行的 )
- Transformation (转换比如一些es6 的语法)
- Serialization (序列化,实际上moduleId 就是这个理生成的)组合成单个 JavaScript 文件的模块包。
// metro 官方文档(https://facebook.github.io/metro/docs/configuration#serializer-options)中提到了 Serialization 时期使用到的几个函数,其中我们要关注的点是“moduleId 如何生成的 ”
// 具体的源代码在 ./node_modules/metro/src/lib/createModuleIdFactory.js 这里是metro 默认 的 moduleId 生成方式
function createModuleIdFactory() {
const fileToIdMap = new Map();
let nextId = 0;
return (path) => {
let id = fileToIdMap.get(path);
if (typeof id !== "number") {
id = nextId++;
fileToIdMap.set(path, id);
}
return id;
};
}
// 不难看出 非常的简单 就是0 开始的 自增,后面我们分包的时候 需要手动的定制一些 moduleId 要不然 运行的时候 会导致 模块的依赖出现问题 和冲突 导致闪退!
顺便说一下 Serialization时期 还有一个重要的函数 processModuleFilter,他可以完成模块 build 阶段的过滤,当他 返回 false 就是不打入,这个特性对我们后续的拆包会很有用。
到此为止,我们对bundle 和 metro 的浅析接结束了,以上都是前置内容是了解后续拆包方案的 js部分的基础
1.4 js基础部分我们掰开 说完整了,我们看看 RN 在Android 上的loading 原理
我们先梳理流程
// 创建一个ReactRootView
mReactRootView = new ReactRootView(this);
// 增加依赖
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
packages.add(new RNToolPackage());
// 创建 ReactInstanceManager 实例
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index") // 仅dev 下有效
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 组册 js 组件 并挂到ReactRootView 实例上
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
// 把 mReactRootView 设置到当前的 View 上
setContentView(mReactRootView);
解析来我们浅析 每个调用链
// 我们看看这个 ReactRootView 类 还有这个类的 startReactApplication 方法
class ReactRootView extends FrameLayout implements RootView, ReactRoot {
//..... 省去部分代码
// 可以看到它继承 FrameLayout ,并且实现了 两个借口,
@ThreadConfined("UI")
public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName, @Nullable Bundle initialProperties, @Nullable String initialUITemplate) {
Systrace.beginSection(0L, "startReactApplication");
try {
UiThreadUtil.assertOnUiThread();
Assertions.assertCondition(this.mReactInstanceManager == null, "This root view has already been attached to a catalyst instance manager");
// 看看 mReactInstanceManager 实例是否正常 加载
// 赋值
this.mReactInstanceManager = reactInstanceManager;
this.mJSModuleName = moduleName; // 用上面的例子来说 这个地方的值 就是 MyReactNativeApp
this.mAppProperties = initialProperties;
this.mInitialUITemplate = initialUITemplate;
// 创建 jscore 基础容器 上下午
this.mReactInstanceManager.createReactContextInBackground();
if (ReactFeatureFlags.enableEagerRootViewAttachment) {
if (!this.mWasMeasured) {
// 适配屏幕
this.setSurfaceConstraintsToScreenSize();
}
// 简单的理解就是 让这个RootView 和 reactInstanceManager 关联起来 这一步上是rn 容器的基础
// 一些js 通信view 渲染的都在这个里面 由 reactInstanceManager 管理
this.attachToReactInstanceManager();
}
} finally {
Systrace.endSection(0L);
}
}
private void attachToReactInstanceManager() {
Systrace.beginSection(0L, "attachToReactInstanceManager");
ReactMarker.logMarker(ReactMarkerConstants,ROOT_VIEW_ATTACH_TO_REACT_INSTANCE_MANAGER_START);
if (this.getId() != -1) {
ReactSoftExceptionLogger.logSoftException("ReactRootView", new IllegalViewOperationException("Trying to attach a ReactRootView with an explicit id already set to [" + this.getId() + "]. React Native uses the id field to track react tags and will overwrite this field. If that is fine, explicitly overwrite the id field to View.NO_ID."));
}
try {
if (!this.mIsAttachedToInstance) {
this.mIsAttachedToInstance = true;
// 重点 ReactInstanceManager attachRootView 当前view
((ReactInstanceManager)Assertions.assertNotNull(this.mReactInstanceManager)).attachRootView(this);
// 执行 我们自己定义的监听器(详细见RCTDeviceEventEmitter
this.getViewTreeObserver().addOnGlobalLayoutListener(this.
getCustomGlobalLayoutListener());
return;
}
} finally {
ReactMarker.logMarker(ReactMarkerConstants.ROOT_VIEW_ATTACH_TO_REACT_INSTANCE_MANAGER_END);
Systrace.endSection(0L);
}
}
}
// 这个内容比较简单 读取当前 application ,然后返回 package List
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
// 如果还需要其它package 可以接着add
packages.add(new RNToolPackage());
// PackageList class
public class PackageList {
private Application application;
private ReactNativeHost reactNativeHost;
private MainPackageConfig mConfig;
...
public PackageList(Application application) {
this(application, null);
}
public ArrayList<ReactPackage> getPackages() {
return new ArrayList<>(Arrays.<ReactPackage>asList(
new MainReactPackage(mConfig),
new AsyncStoragePackage(),
new RNDeviceInfo()
));
}
}
// 我们来看这个
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index") // 仅dev 下有效
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
class ReactInstanceManager {
public static ReactInstanceManagerBuilder builder() {
return new ReactInstanceManagerBuilder();
}
// 构造函数
ReactInstanceManager(..../* 太多了省去不写 后面有说明 */){
// 这两个function 不是我们讨论的重点 省去
initializeSoLoaderIfNecessary(applicationContext);//
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
this.mApplicationContext = applicationContext;
this.mCurrentActivity = currentActivity;
this.mDefaultBackButtonImpl = defaultHardwareBackBtnHandler;
this.mJavaScriptExecutorFactory = javaScriptExecutorFactory;
this.mBundleLoader = bundleLoader;
this.mJSMainModulePath = jsMainModulePath; // 只有在dev 的时候有用
this.mPackages = new ArrayList();
this.mUseDeveloperSupport = useDeveloperSupport;
this.mRequireActivity = requireActivity;
Systrace.beginSection(0L, "ReactInstanceManager.initDevSupportManager");
// dev 模式下 才使用 mJSMainModulePath
this.mDevSupportManager = devSupportManagerFactory.create(applicationContext, this.createDevHelperInterface(), this.mJSMainModulePath, useDeveloperSupport, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory);
Systrace.endSection(0L);
this.mBridgeIdleDebugListener = bridgeIdleDebugListener;
this.mLifecycleState = initialLifecycleState;
this.mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
this.mJSExceptionHandler = jSExceptionHandler;
this.mTMMDelegateBuilder = tmmDelegateBuilder;
// 开启线程执行载入 package
synchronized(this.mPackages) {
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: Use Split Packages");
this.mPackages.add(new CoreModulesPackage(this, new DefaultHardwareBackBtnHandler() {
public void invokeDefaultOnBackPressed() {
ReactInstanceManager.this.invokeDefaultOnBackPressed();
}
}, mUIImplementationProvider, lazyViewManagersEnabled, minTimeLeftInFrameForNonBatchedOperationMs));
if (this.mUseDeveloperSupport) {
this.mPackages.add(new DebugCorePackage());
}
this.mPackages.addAll(packages);
}
this.mJSIModulePackage = jsiModulePackage;
ReactChoreographer.initialize();
if (this.mUseDeveloperSupport) {
this.mDevSupportManager.startInspector();
}
this.registerCxxErrorHandlerFunc();
}
// 是否创建 了 InitContext
public boolean hasStartedCreatingInitialContext() {
return this.mHasStartedCreatingInitialContext;
}
// 加一个监听器 看看 context 容器 实例 是否载入
public void addReactInstanceEventListener(com.facebook.react.ReactInstanceEventListener listener) {
this.mReactInstanceEventListeners.add(listener);
}
}
class ReactInstanceManagerBuilder {
....
ReactInstanceManagerBuilder() { // 指定一个JS 解释器
this.jsInterpreter = JSInterpreter.OLD_LOGIC;
// JSInterpreter JS解释器,里面有三种模式 OLD_LOGIC,JSC,HERMES
}
// 为 ReactInstanceManagerBuilder 实例 设置当前 application
public ReactInstanceManagerBuilder setApplication(Application application) {
this.mApplication = application;
return this;
}
// 为 ReactInstanceManagerBuilder 实例 设置当前 activity
public ReactInstanceManagerBuilder setCurrentActivity(Activity activity) {
this.mCurrentActivity = activity;
return this;
}
// 设置当前 mJSBundleAssetUrl,此时 mJSBundleLoader = null
public ReactInstanceManagerBuilder setBundleAssetName(String bundleAssetName) {
this.mJSBundleAssetUrl = bundleAssetName == null ? null : "assets://" + bundleAssetName;
this.mJSBundleLoader = null;
return this;
}
// 设置 mJSMainModulePath 这个只有在 dev 模式下有效,至于为什么 请看后面的一个源代码 --TODO
public ReactInstanceManagerBuilder setJSMainModulePath(String jsMainModulePath) {
this.mJSMainModulePath = jsMainModulePath;
return this;
}
// 把 PackageList 全部添加到自己身上
public ReactInstanceManagerBuilder addPackages(List<ReactPackage> reactPackages) {
this.mPackages.addAll(reactPackages);
return this;
}
// 设置是否dev 模式
public ReactInstanceManagerBuilder setUseDeveloperSupport(boolean useDeveloperSupport) {
this.mUseDeveloperSupport = useDeveloperSupport;
return this;
}
// 设置是否生命周期 他说这些枚举 位于facebook 的包下
// BEFORE_CREATE, 创建之前
// BEFORE_RESUME, resume 之前
// RESUMED; 已经 resume
public ReactInstanceManagerBuilder setInitialLifecycleState(LifecycleState initialLifecycleState) {
this.mInitialLifecycleState = initialLifecycleState;
return this;
}
public ReactInstanceManager build() {
Assertions.assertNotNull(this.mApplication, "Application property has not been set with this builder");
if (this.mInitialLifecycleState == LifecycleState.RESUMED) {
Assertions.assertNotNull(this.mCurrentActivity, "Activity needs to be set if initial lifecycle state is resumed");
}
Assertions.assertCondition(this.mUseDeveloperSupport || this.mJSBundleAssetUrl != null || this.mJSBundleLoader != null, "JS Bundle File or Asset URL has to be provided when dev support is disabled");
Assertions.assertCondition(this.mJSMainModulePath != null || this.mJSBundleAssetUrl != null || this.mJSBundleLoader != null, "Either MainModulePath or JS Bundle File needs to be provided");
// RN 的UI 提供者
if (this.mUIImplementationProvider == null) {
this.mUIImplementationProvider = new UIImplementationProvider();
}
// 获取当前包名
String appName = this.mApplication.getPackageName();
String deviceName = AndroidInfoHelpers.getFriendlyDeviceName(); // 获取设备名称
// 创建一个 ReactInstanceManager
return new ReactInstanceManager(
this.mApplication,
this.mCurrentActivity,
this.mDefaultHardwareBackBtnHandler, // android 物理返回键处理程序
this.mJavaScriptExecutorFactory == null ? this.getDefaultJSExecutorFactory(appName, deviceName, this.mApplication.getApplicationContext()) : this.mJavaScriptExecutorFactory,
this.mJSBundleLoader == null && this.mJSBundleAssetUrl != null ? JSBundleLoader.createAssetLoader(this.mApplication, this.mJSBundleAssetUrl, false) : this.mJSBundleLoader,
// mJSBundleLoader js bundle 捆绑器 详细见下面的类
this.mJSMainModulePath,
this.mPackages,
this.mUseDeveloperSupport,
(DevSupportManagerFactory)(this.mDevSupportManagerFactory == null ? new DefaultDevSupportManagerFactory() : this.mDevSupportManagerFactory),
this.mRequireActivity,
this.mBridgeIdleDebugListener, (LifecycleState)Assertions.assertNotNull(this.mInitialLifecycleState, "Initial lifecycle state was not set"),
this.mUIImplementationProvider,
this.mJSExceptionHandler,
this.mRedBoxHandler,
this.mLazyViewManagersEnabled, // boolean 是否开启 lazy 加载
this.mDevBundleDownloadListener, // dev bundle 下载监听器
this.mMinNumShakes,
this.mMinTimeLeftInFrameForNonBatchedOperationMs,
this.mJSIModulesPackage, // ReactInstanceManager 里的 jsiModulePackage 这个 package 还和 rn 的bridge 有关 这里不深入
this.mCustomPackagerCommandHandlers,
this.mTMMDelegateBuilder,
this.mSurfaceDelegateFactory);
}
// 工厂函数 js 执行器 看看到底给你的是 JSCExecutorFactory 还是 HermesExecutorFactory
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName, Context applicationContext) {
if (this.jsInterpreter == JSInterpreter.OLD_LOGIC) {
try {
ReactInstanceManager.initializeSoLoaderIfNecessary(applicationContext);
JSCExecutor.loadLibrary();
return new JSCExecutorFactory(appName, deviceName);
} catch (UnsatisfiedLinkError var5) {
if (var5.getMessage().contains("__cxa_bad_typeid")) {
throw var5;
} else {
HermesExecutor.loadLibrary();
return new HermesExecutorFactory();
}
}
} else if (this.jsInterpreter == JSInterpreter.HERMES) {
HermesExecutor.loadLibrary();
return new HermesExecutorFactory();
} else {
JSCExecutor.loadLibrary();
return new JSCExecutorFactory(appName, deviceName);
}
}
}
public abstract class JSBundleLoader {
public JSBundleLoader() {
}
....
public static JSBundleLoader createAssetLoader(final Context context, final String assetUrl, final boolean loadSynchronously) {
return new JSBundleLoader() {
public String loadScript(JSBundleLoaderDelegate delegate) { // 重点参数 loadScriptFromAssets
// 重点 这个就是 loadScriptFromAssets 的方法 。具体实现在 JSCExecutor.cpp 这里不详细扩开了,
// 如果我们知道 rn 中谁在调用这个方法 就知道是如何载入js 的了
delegate.loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously);
return assetUrl;
}
};
}
.....
public abstract String loadScript(JSBundleLoaderDelegate var1);
....
}
// 当我们的步骤执行完之后 mReactInstanceManager 是一个这样的东西
mReactInstanceManager = {
this.mApplication = "当前Application"
this.mCurrentActivity = "当前的Activity"
this.mDefaultBackButtonImpl = "当前硬件返回处理程序"
this.mJavaScriptExecutorFactory = "JSCExecutorFactory 执行器 appName =myrnApp deviceName 小米2s"
this.mBundleLoader= "JSBundleLoader.createAssetLoader(this.mApplication, assets://index.android.bundle, false) "
this.mJSMainModulePath="index"
this.mPackages="Packages 里面包含了dev 的一些包 因为UseDeveloperSupport = true"
this.mUseDeveloperSupport="true"
this.mRequireActivity="false"
this.mBridgeIdleDebugListener="null"
this.mJSExceptionHandler="null"
this.mRedBoxHandler="null"
this.mLazyViewManagersEnabled="false"
this.mDevBundleDownloadListener="null"
this.mMinNumShakes="1"
this.mMinTimeLeftInFrameForNonBatchedOperationMs="-1"
this.mJSIModulesPackage="null"
this.mCustomPackagerCommandHandlers="{}"
this.mTMMDelegateBuilder="null"
this.mSurfaceDelegateFactory="null"
}
// 在 RootView 类中有一个 startApplication 方法 里面有一个 createReactContextInBackground 它属于 ReactInstanceManager 里面分治了两类 dev 和 release 的
class ReactInstanceManager {
....
// 通过调用链 我们找到了最总的调用方法 recreateReactContextInBackgroundInner 和 runCreateReactContextOnNewThread 以及 createReactContext
@ThreadConfined("UI")
private void recreateReactContextInBackgroundInner() {
FLog.d(TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
UiThreadUtil.assertOnUiThread();
if (this.mUseDeveloperSupport && this.mJSMainModulePath != null) { //进入dev
final DeveloperSettings devSettings = this.mDevSupportManager.getDevSettings();
if (!Systrace.isTracing(0L)) {
if (this.mBundleLoader == null) {
this.mDevSupportManager.handleReloadJS(); // reload js
} else {
this.mDevSupportManager.isPackagerRunning(new PackagerStatusCallback() {
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
UiThreadUtil.runOnUiThread(new Runnable() {
public void run() {
if (packagerIsRunning) {
ReactInstanceManager.this.mDevSupportManager.handleReloadJS();
} else if (ReactInstanceManager.this.mDevSupportManager.hasUpToDateJSBundleInCache() && !devSettings.isRemoteJSDebugEnabled() && !ReactInstanceManager.this.mUseFallbackBundle) {
ReactInstanceManager.this.onJSBundleLoadedFromServer();
} else {
devSettings.setRemoteJSDebugEnabled(false);
ReactInstanceManager.this.recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
}
// 正常 release 如何 loader 呢?依据调用链 查找到 runCreateReactContextOnNewThread 函数
this.recreateReactContextInBackgroundFromBundleLoader();
}
// 开启线程 执行 CreateReactContext 这里有很多的线程代码 我们不深入
@ThreadConfined("UI")
private void runCreateReactContextOnNewThread(final ReactInstanceManager.ReactContextInitParams initParams) {
....
reactApplicationContext = ReactInstanceManager.this.createReactContext(initParams.getJsExecutorFactory().create(), initParams.getJsBundleLoader());
.....
ReactInstanceManager.this.setupReactContext(reactApplicationContext); // 更新上去
}
// 找到 createReactContext 函数 我们先 理解一下 他的参数 jsExecutor,jsBundleLoader
// jsExecutor 这个是之前我们找到的 执行器 ,jsBundleLoader就是上述说明的Loader 这个需要重点看看,因为从上述的类来看 最总的加载在它
private ReactApplicationContext createReactContext(JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
....
// 关键代码
com.facebook.react.bridge.CatalystInstanceImpl.Builder catalystInstanceBuilder = (
new com.facebook.react.bridge.CatalystInstanceImpl.Builder()).setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault()).setJSExecutor(jsExecutor).setRegistry(nativeModuleRegistry).setJSBundleLoader(jsBundleLoader).setJSExceptionHandler((JSExceptionHandler)exceptionHandler);
// catalystInstanceBuilder 主要做的事情 是 设置 队列(因为涉及到线程),->设置JS执行器 -> 设置 nativeModuleRegistry -> 设置 jsBundleLoader-> 设置异常捕获器
// catalystInstanceBuilder 这个类身上就有我们的jsbundle 了
CatalystInstanceImpl catalystInstance = catalystInstanceBuilder.build();
// build 就是依据传如的参数 返回一个 CatalystInstanceImpl 实例
// 最后一行就跑去了
catalystInstance.runJSBundle()
// 我们分析一下 catalystInstanceBuilder 类的build 返回了什么。以及它 返回的类上的 runJSBundle 在干什么
....
}
}
public static class Builder {
@Nullable
private ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
@Nullable
private JSBundleLoader mJSBundleLoader;
@Nullable
private NativeModuleRegistry mRegistry;
@Nullable
private JavaScriptExecutor mJSExecutor;
@Nullable
private JSExceptionHandler mJSExceptionHandler;
public Builder() {
}
public CatalystInstanceImpl.Builder setReactQueueConfigurationSpec(ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
this.mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
return this;
}
public CatalystInstanceImpl.Builder setRegistry(NativeModuleRegistry registry) {
this.mRegistry = registry;
return this;
}
public CatalystInstanceImpl.Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
this.mJSBundleLoader = jsBundleLoader;
return this;
}
public CatalystInstanceImpl.Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
this.mJSExecutor = jsExecutor;
return this;
}
public CatalystInstanceImpl.Builder setJSExceptionHandler(JSExceptionHandler handler) {
this.mJSExceptionHandler = handler;
return this;
}
public CatalystInstanceImpl build() {
return new CatalystInstanceImpl((ReactQueueConfigurationSpec)Assertions.assertNotNull(this.mReactQueueConfigurationSpec), (JavaScriptExecutor)Assertions.assertNotNull(this.mJSExecutor), (NativeModuleRegistry)Assertions.assertNotNull(this.mRegistry), (JSBundleLoader)Assertions.assertNotNull(this.mJSBundleLoader), (JSExceptionHandler)Assertions.assertNotNull(this.mJSExceptionHandler));
}
}
public class CatalystInstanceImpl implements CatalystInstance {
public void runJSBundle() {
FLog.d("ReactNative", "CatalystInstanceImpl.runJSBundle()");
Assertions.assertCondition(!this.mJSBundleHasLoaded, "JS bundle was already loaded!");
this.mJSBundleLoader.loadScript(this); // 运行load loadScript这个不深入了,他和一部分的C++代码有关系
// loadScript -> 实际上就是 loadScriptFromAssets(context.getAssets(), assetUrl, loadSynchronously); 返回 assetUrl string
synchronized(this.mJSCallsPendingInitLock) {
this.mAcceptCalls = true;
Iterator var2 = this.mJSCallsPendingInit.iterator();
while(true) {
if (!var2.hasNext()) {
this.mJSCallsPendingInit.clear();
this.mJSBundleHasLoaded = true;
break;
}
CatalystInstanceImpl.PendingJSCall function = (CatalystInstanceImpl.PendingJSCall)var2.next();
function.call(this);
}
}
Systrace.registerListener(this.mTraceListener);
}
}
// 从上述我们可以看到,执行runjs 的 实际上是 CatalystInstance ,这点请你记住
到此为止,我们的前置知识都搞定了!
-
首先我们来看看第一版方案( 直接丢到不同的 acitvy 中运行)
主要的思路:“让多个Native 容器去承载 不同的RN 容器,每一个RN容器都是一个独立的BU业务,在通过RN 的native 桥接 就能够实现 这类的拆包”, 需要注意的 开发阶段 和 build 阶段,
-
在开发阶段:
由于metro build 的末日目录在根目录 ,我们的需要在root 根目录下进行 (我是指每个module 的入口要在根目录 )要不然会有路径问题,metro 实际上是一个 static 文件托管service 它默认监听的是项目根目录, 譬如你请求的是 index.bundle.好,默认就是根目录下的index ,如果你请求的是 a.bundle,那么加载和编译的就是 根目录下的 a.js 文件,这些就是所谓的“入口文件”,这些文件里 有一个 registerComponent 方法,这个就是runtime 的时候 rn 触发的 view 试图绑定的关键代码,在RN 引擎中 ,它的加载顺序是 :js端先运行js代码注册组件---->原生端找到这个组件并关联
-
需要注意我们的这个参数
mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setCurrentActivity(this) .setBundleAssetName("index.android.bundle") // 对应的release 包名称,如果多个业务就是 bu1.android.bundle, bu2.android.bundle ...... .setJSMainModulePath("index") // 根目录下 index.js . 如果同的文件 就是 Bu1.js Bu2.js xxxxx 依次类推 不一定都叫这个名字哈 只在dev 模式下生效 setJSMainModulePath .addPackages(packages) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null); // js 端 的registerComponent name MyReactNativeApp setContentView(mReactRootView);
- 第二版方案 (基础包 common + bu 业务包 = 运行时的 全量包 )
我们的发现上述的分包方案有明显的不足:“每个独立的包 都包含RN 的公共部分”,它会让我们的包体积变大,加载的时候白屏实际也会变长,基于此和市面上主流的方案,我们可以这样玩 :把公共的包提取出来,bu包只包含业务,在实际运行的时候,把它们合成一个 runtime 的bundle 去执行,于是我们就有了这样的东西: common + bu 业务包 = 运行时的 全量包
-
首先我们就要处理 “拆开” 这一个问题,在上述的 cli 源码分析中,我可以所需要用到的东西,只有两个函数 metro 提供的配置 createModuleIdFactory 和 processModuleFilter,前者处理模块命名,后者处理过滤(哪些需要打入bundle 哪些不需要),主要的内容前问已经描述过了,这里不在赘述
我们先看看moduleId 的处理,首先啊,我们还是使用 number 做为 id (而不是使用string string 太大了),为了区分基础包和 bu 包,我们规定 10000000 为业务包的开始自增的 moduleId 初值(每个BU的值不一样,main->10000000 -> bu1 20000000-> bu2 -> 30000000) ,基础包的id 还是从0 -> 开始递增。还需要注意的是,由于我们的moduleId 之间是有相互依赖的 ,所以为了确保,依赖关系的正确性,我们需要为基础包做一个映射(做法是 把基础包的 路径 存到一个json 中,业务包遇到这个路径的时候 去找这个映射中的moduleId 就好了)如果你不这样做,那么你的模块依赖 会乱掉. 而在 bu 打包的时候 只需要过滤掉 基础包映射中的js module就好了
build.js
const fs = require("fs"); const clean = function (file) { fs.writeFileSync(file, JSON.stringify({})); }; const hasBuildInfo = function (file, path) { const cacheFile = require(file); return Boolean(cacheFile[path]); }; const writeBuildInfo = function (file, path, id) { const cacheFile = require(file); cacheFile[path] = id; fs.writeFileSync(file, JSON.stringify(cacheFile)); }; const getCacheFile = function (file, path) { const cacheFile = require(file); return cacheFile[path] || 0; }; const isPwdFile = (path) => { const cwd = __dirname.split("/").splice(-1, 1).toString(); const pathArray = path.split("/"); const map = new Map(); const reverseMap = new Map(); pathArray.forEach((it, indx) => { map.set(it, indx); reverseMap.set(indx, it); }); if (pathArray.length - 2 == map.get(cwd)) { return reverseMap.get(pathArray.length - 1).replace(/\.js/, ""); } return ""; }; module.exports = { hasBuildInfo, writeBuildInfo, getCacheFile, clean, isPwdFile, };
common.metro.js
const { hasBuildInfo, writeBuildInfo, clean } = require("./build"); function createModuleIdFactory() { const fileToIdMap = new Map(); let nextId = 0; clean("./config/bundleCommonInfo.json"); // 如果是业务 模块请以 10000000 来自增命名 return (path) => { let id = fileToIdMap.get(path); if (typeof id !== "number") { id = nextId++; fileToIdMap.set(path, id); !hasBuildInfo("./config/bundleCommonInfo.json", path) && writeBuildInfo( "./config/bundleCommonInfo.json", path, fileToIdMap.get(path) ); } return id; }; } module.exports = { serializer: { createModuleIdFactory: createModuleIdFactory, // 给 bundle 一个id 避免冲突 cli 源码中这个id 是从1 开始 自增的 }, };
mian.metro.js
const { hasBuildInfo, getCacheFile, isPwdFile } = require("./build"); const bundleBuInfo = require("./config/bundleBuInfo.json"); function postProcessModulesFilter(module) { if ( module["path"].indexOf("__prelude__") >= 0 || module["path"].indexOf("polyfills") >= 0 ) { return false; } if (hasBuildInfo("./config/bundleCommonInfo.json", module.path)) { return false; } return true; } // 不要使用 string 会导致 bundle 体积陡增 function createModuleIdFactory() { // 如果是业务 模块请以 10000000 来自增命名 const fileToIdMap = new Map(); let nextId = 10000000; let isFirst = false; return (path) => { if (Boolean(getCacheFile("./config/bundleCommonInfo.json", path))) { return getCacheFile("./config/bundleCommonInfo.json", path); } if (!isFirst && isPwdFile(path)) { nextId = bundleBuInfo[isPwdFile(path)]; isFirst = true; } let id = fileToIdMap.get(path); if (typeof id !== "number") { id = nextId++; fileToIdMap.set(path, id); } return id; }; } module.exports = { serializer: { createModuleIdFactory: createModuleIdFactory, // 给 bundle 一个id 避免冲突 cli 源码中这个id 是从1 开始 自增的 processModuleFilter: postProcessModulesFilter, // 返回false 就不会build 进去 }, };
config/bundleBuInfo.json
{ "index": 10000000, "Bu1": 20000000, "Bu2": 30000000 }
-
执行build 命令就好了, 当然你可以把它们都编如一个shell 中去 打包简化的目的, 我这里没有怎么做,因为我们后续还需针对热更新做优化
# common yarn react-native bundle --platform android --dev false --entry-file ./common.js --bundle-output ./android/app/src/main/assets/common.android.bundle --assets-dest ./android/app/src/main/res --config ./metro.common.config.js --reset-cache # BU yarn react-native bundle --platform android --dev false --entry-file ./Bu2.js --bundle-output ./android/app/src/main/assets/bu2.android.bundle --assets-dest ./android/app/src/main/res --config ./metro.main.config.js --reset-cache
打包之前(假设我们没有 进行压缩🗜️ 参数 --minify false )我们发现,如果不拆包 每个bundle 也得有 将近2.3M的大小,
打包之后(假设我们不对代码 进行压缩🗜️ 参数 --minify false )我们发现common 1.9MB (比较大 因为包含了公共依赖),其余的包 基本不到 50kb
可以看到 效果显著啊,如果进行压缩 处理 common 将不足1kb 每个 bu将不会超过 20kb
特别说明,上述的大小对比仅仅是我的这个项目来说,实际情况还是要以项目实际情况为主
- 好了现在我们把js 的拆分已经完成了,然后重点来了“如何在Android native”,合并这两个包形成一个runtime 的 bundle呢?
// 前文中我们就提到过 android code 执行的流程,现在我们来change 一下啊 ,主要的核心代码是:(具体的完整代码请看 源码) // 我这里把它们抽象 一个公共的类,每个 Activity 加载的时候 重写 getJSBundleAssetName,getJsModulePathPath,getResName 就 可以很方便的加载指定 的 Activity 了 , @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); } } SoLoader.init(this, false); // root 容器 mReactRootView = new ReactRootView(this); if( BuildConfig.DEBUG ){ mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setCurrentActivity(this) .setBundleAssetName(getJSBundleAssetName()) .setJSMainModulePath(getJsModulePathPath()) .addPackages(MainApplication.getInstance().packages) .setUseDeveloperSupport(true) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, getResName(), null); setContentView(mReactRootView); return; } mReactInstanceManager = MainApplication.getInstance().builder.setCurrentActivity(this).build(); if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext context) { //加载业务包 ReactContext mContext = mReactInstanceManager.getCurrentReactContext(); CatalystInstance instance = mContext.getCatalystInstance(); ((CatalystInstanceImpl)instance).loadScriptFromAssets(context.getAssets(), "assets://" + getJSBundleAssetName(),false); mReactRootView.startReactApplication(mReactInstanceManager, getResName(), null); setContentView(mReactRootView); mReactInstanceManager.removeReactInstanceEventListener(this); } }); mReactInstanceManager.createReactContextInBackground(); } return; }
- 但光这样就结束了?远远没有,如果像上述这样做的话,会导致 每个 Activity 都会全量载入 一次 bundle ,如果有一种方法,能够把基础的common 缓存起来,每次 Activity 只加载 bu 包就好了。
市面上对于这一块有不同的做法,网上能搜到的就是 腾讯某团队的 一篇文章了 (https://cloud.tencent.com/developer/article/1005382),但是这....是有局限的 直接缓存 RootView 要仔细处理 Native 的生命周期 和 RN 的生命周期,要不然会导致 缓存的RootView 无法执行 componnetDid 等,因为他执行过一次就不在执行js 了你没有reload js 只是缓存绘制好的View 而且 ,在 native 的 onDestroy 中也要处理,要不然缓存的view 无法相应JS。
基于此我换了一种思路去实现呢它,我把common 缓存起来,动态加载不同的bundle ,目前我现在的做法基本上 是妙进的!
// MainApplication public class MainApplication extends Application { public ReactInstanceManagerBuilder builder; public List<ReactPackage> packages; private ReactInstanceManager cacheReactInstanceManager; private Boolean isload = false; private static MainApplication mApp; @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); mApp = this; packages = new PackageList(this).getPackages(); packages.add(new RNToolPackage()); cacheReactInstanceManager = ReactInstanceManager.builder() .setApplication(this) .addPackages(packages) .setJSBundleFile("assets://common.android.bundle") .setInitialLifecycleState(LifecycleState.BEFORE_CREATE).build(); } public static MainApplication getInstance(){ return mApp; } // 获取 已经缓存过的 rcInstanceManager public ReactInstanceManager getRcInstanceManager () { return this.cacheReactInstanceManager; } public void setIsLoad(Boolean isload) { this.isload = isload; } public boolean getIsLoad(){ return this.isload; } // PreBaseInit (只列出 核心的代码 ) 完整代码请到仓库 自行查看 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(this)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); } } SoLoader.init(this, false); if( BuildConfig.DEBUG ){ mReactRootView = new ReactRootView(this); mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setCurrentActivity(this) .setBundleAssetName(getJSBundleAssetName()) .setJSMainModulePath(getJsModulePathPath()) .addPackages(MainApplication.getInstance().packages) .setUseDeveloperSupport(true) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, getResName(), null); setContentView(mReactRootView); return; } // 重新设置 Activity 和 files mReactInstanceManager = MainApplication.getInstance().getRcInstanceManager(); mReactInstanceManager.onHostResume(this, this); mReactRootView = new ReactRootView(this); mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext context) { MainApplication.getInstance().setIsLoad(true); //加载业务包 ReactContext mContext = mReactInstanceManager.getCurrentReactContext(); CatalystInstance instance = mContext.getCatalystInstance(); ((CatalystInstanceImpl)instance).loadScriptFromAssets(context.getAssets(), "assets://" + getJSBundleAssetName(),false); mReactRootView.startReactApplication(mReactInstanceManager, getResName(), null); setContentView(mReactRootView); mReactInstanceManager.removeReactInstanceEventListener(this); } }); if(MainApplication.getInstance().getIsLoad()){ ReactContext mContext = mReactInstanceManager.getCurrentReactContext(); CatalystInstance instance = mContext.getCatalystInstance(); ((CatalystInstanceImpl)instance).loadScriptFromAssets(mContext.getAssets(), "assets://" + getJSBundleAssetName(),false); mReactRootView.startReactApplication(mReactInstanceManager, getResName(), null); setContentView(mReactRootView); } mReactInstanceManager.createReactContextInBackground(); return; }
至此 基于RN 的拆包 JS 和 Android 端已经完美实现
- 关于热更新 和版本管理
重要说明特(common 包为方便管理 我们不进行热更新)
目前我使用 CodePush 遇到了问题,code push 适合 使用 rn 创建的新项目,如果使用 Android 项目开始的 那么,code push 集成 将会是一个棘手的问题。于是我自己作了一个 简单的热更新.
技术预研
1. 预先调研 (删除问文件夹操作) 是否可以 创建文件夹 + CV文件 + 删除文件 - ✅ 2. 预先调研 是否可以载入 fileSystem 的包 - ✅ 3. common开头独立执行嘛 - ✅4 3. RN 下载 zip 并解包 - ✅
- 集成阶段
- 安装pod 依赖
# Uncomment the next line to define a global platform for your project
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
install! 'cocoapods', :deterministic_uuids => false
platform :ios, '12.4'
target 'myrnapp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
config = use_native_modules!
flags = get_default_flags()
pod 'RNDeviceInfo', path: '../node_modules/react-native-device-info'
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => false,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
# :flipper_configuration => FlipperConfiguration.enabled,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
# Pods for myrnapp
target 'myrnappTests' do
inherit! :search_paths
# Pods for testing
end
target 'myrnappUITests' do
# Pods for testing
end
post_install do |installer|
react_native_post_install(
installer,
# Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => true
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
- 如果你的项目中含有 SceneDelegate 请去掉它
删除方法 -> https://www.jianshu.com/p/6b3f40319877
删除main storyboard https://blog.csdn.net/qq_31598345/article/details/119979791
- 我们不用官方的例子 只是按照它提供的思路 去自己写一个
//
// ViewController.m
// myrnapp
//
// Created by 李仕增 on 2022/10/8.
//
#import "ViewController.h"
#import <React/RCTRootView.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 加一些oc 的code 确保项目上正常的状态
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
view.frame = CGRectMake(100,100, 100, 100);
[self.view addSubview:view];
view.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openRNView)];
[view addGestureRecognizer:tap];
UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor greenColor];
view2.frame = CGRectMake(150,300, 100, 100);
[self.view addSubview:view2];
view2.userInteractionEnabled = YES;
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openRNView2)];
[view2 addGestureRecognizer:tap2];
// 直接开始集成
}
- (void) testClick {
NSLog(@"6666666");
}
- (void)openRNView {
NSLog(@"High Score Button Pressed");
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8082/IOS.bundle?platform=ios"];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
moduleName: @"RNHighScores"
initialProperties: nil
launchOptions: nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
}
- (void)openRNView2 {
NSLog(@"High Score Button Pressed");
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8082/IOS2.bundle?platform=ios"];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
moduleName: @"RNHighScores2"
initialProperties: nil
launchOptions: nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
}
@end
-
确保你的相关权限已经开放 比如网络
确保你的info.plist 包含下面的字段
<key>NSAppTransportSecurity</key>
- build 阶段
-
你可能会遇到的问题 你也许会越到当不限于下面的这些问题
相关的问题都可以去react-native官方的github issue 里有,我最终采取静态连接的办法
关键代码
++++ use_frameworks! :linkage => :static # 使用静态库 连接 不要使用动态库 或者 默认的连接 ,会有问题 ++++
-
解析来 build 环节需要注意的地方
Native 的BUILD 现在解决了,那么RN的build 怎么办呢?
首先是native 代码需要修改 资源路径 不要从远程加载 直接从本地载入
- (void)openRNView2 { NSLog(@"High Score Button Pressed"); // NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8082/IOS2.bundle?platform=ios"]; NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/IOS2.ios" withExtension:@"bundle"]; // RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation // moduleName: @"RNHighScores2" // launchOptions: nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"RNHighScores2" initialProperties:nil launchOptions:nil ]; UIViewController *vc = [[UIViewController alloc] init]; vc.view = rootView; [self presentViewController:vc animated:YES completion:nil]; };
然后关于js 和资源的build ,下面是它们的构建脚本
yarn react-native bundle --entry-file ./IOS2.js --bundle-output ./bundle/IOS2.ios.bundle --platform ios --assets-dest ./bundle --dev false
最后要注意的是 ==> 请你直接把整个文件夹拖拽进入Xcode!中的projext 下
如果发现有问题 跑不通, 需要分析原因 给IOS debug 看看那个环节有问题
- 关于native 包的问题
实际上这个非常的简单,我在这个项目中 ,所有的native 包再 pod install 的时候都自动安装了,如果你需要手动包含,可以参考旧版本的做法. 在 PodFile 中手动+ (比如下面的例子)
++++
pod 'RNDeviceInfo', path: '../node_modules/react-native-device-info'
+++
- 参考
首先我参考了一部分的材料 主要的材料是这两片文章 掘金文章 RN的分包实践 GitHub项目
- 重要的原理
我们先看看 RN 在IOS 中的加载过程 就能明白 我目前采用的方案的原理了
-> 创建 RCTRootView,为 React Native 提供原生 UI 中的根视图。
-> 创建 RCTBridge,提供 iOS 需要的桥接功能。
-> 创建 RCTBatchedBridge,实际上是这个对象为 RCTBridge 提供方法,让其将这些方法暴露出去。 [RCTCxxBridge start],启动 JavaScript 解析进程。 [RCTCxxBridge loadSource],通过 RCTJavaScriptLoader 下载 bundle,并且执行。
-> 建立 JavaScript 和 iOS 之间的 Module 映射。
-> 将模块映射到对应的 RCTRootView 当中。
可以看到 最重要的是 Bridge 所有的script 的加载都可以在这找到一些线索,通过debuger 我们可以找到一个关键的方法 executeSourceCode 这就是执行 js 代码的方法。如果要实现自己的分包我必须 重写这里面的逻辑 所以有了下面的代码
如果是dev 模式的话,可以把这些code 去掉换成 http的方式,当然这些都是后话了
- 实践
- 首先是重载 executeSourceCode 和定义 brige
// ViewController.h
#import <UIKit/UIKit.h>
#import <React/RCTBridge.h>
// 保留出这个 方法
@interface RCTBridge (PackageBundle)
- (RCTBridge *)batchedBridge;
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync;
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) RCTBridge *bridge;
@end
- 其次我们要重新编写一下我们的js 的build 脚本,因为ios 和android 的打出来的包不一样!,😢 之前一只使用android 的 common 包 和 bu(注意我的bu包是 ios 和ios2.js) 包,一直报错 ,找好久才找到原因
{
"build:common-ios": "react-native bundle --platform ios --dev false --entry-file ./common.js --bundle-output ./bundle/common.ios.bundle --config ./metro.common.config.js --minify false --reset-cache",
"build:ios1": "react-native bundle --entry-file ./IOS.js --bundle-output ./bundle/IOS.ios.bundle --platform ios --assets-dest ./bundle --config ./metro.main.config.js --minify false --dev false",
"build:ios2": "react-native bundle --entry-file ./IOS2.js --bundle-output ./bundle/IOS2.ios.bundle --platform ios --assets-dest ./bundle --config ./metro.main.config.js --minify false --dev false"
}
别忘记了!你在build 的时候要把bu的其实 id 搞进去!
{
"index": 10000000,
"Bu1": 20000000,
"Bu2": 30000000,
// 把下面的bu 加上!
"IOS": 40000000,
"IOS2": 50000000
}
- 然后我们来测试一下 使用分包的模式先载入 common 再载入 bu包, 注意啊 我们不采取dev环境下的从 service 载入 bundle 我们从本地文件载入 ,因此有改动 需要先build 再去运行 查看效果
// ViewController.m
-(instancetype) init {
self = [super init];
[self initBridge];
return self;
};
- (void) initBridge {
if(!self.bridge) {
NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/common.ios" withExtension:@"bundle"];
// 初始化 bridge,并且加载主包
self.bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:nil];
}
};
// 在点击load 的时候 让 brige 再执行一次bu 的js
++++
-(void) loadScript {
NSString * bundlePath = @"bundle/IOS2.ios";
NSString * bunldeName = @"IOS2";
NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:bundlePath withExtension:@"bundle"];
if(self.bridge) {
NSError *error = nil;
NSData *sourceBuz = [NSData dataWithContentsOfFile:jsCodeLocation.path
options:NSDataReadingMappedIfSafe
error:&error];
[self.bridge.batchedBridge executeSourceCode:sourceBuz sync:NO];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBridge:self.bridge moduleName:bunldeName initialProperties:nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
};
}
可以看到 ,现在我们单独的一个bu 已经可以完全集成了,为了以后简化 函数调用我们把loadScript 改造成参数的方式
// ViewController.h
@interface ViewController : UIViewController
@property (nonatomic, strong) RCTBridge *bridge;
-(void) loadScript:(NSString *)bundlePath bunldeName: (NSString *)bunldeName;
@end
// ViewController.m
-(void) loadScript:(NSString *)bundlePath bunldeName: (NSString *)bunldeName {
NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:bundlePath withExtension:@"bundle"];
if(self.bridge) {
NSError *error = nil;
NSData *sourceBuz = [NSData dataWithContentsOfFile:jsCodeLocation.path
options:NSDataReadingMappedIfSafe
error:&error];
[self.bridge.batchedBridge executeSourceCode:sourceBuz sync:NO];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBridge:self.bridge moduleName:bunldeName initialProperties:nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
};
}
这就完了?当然没有啦,我们需要在RN中进行bu 的载入 和切换,我们需要一些桥接 的代码桥接到IOS中, 这一点我之前专门有文章讲解 ,如果你不懂请千万 , 同时这里还会设计到一个IOS的 知识比如notifaction 和 GCD,看不懂的话也没有关系 什么不懂google 一下 自己实践code一下就明白了,我们直接放出代码
注册RN 桥接模块,为了和Android 中保持一致,我们使用一样的名字 RNToolsManager,然后我们使用notifation 的方式 去直接调用View 中的code ,当然不要忘记了!一定要把这段代码加到主线程去 ,要不然会有问题
// ViewController.m
-(instancetype) init {
self = [super init];
[self initBridge];
[self addObservers];
return self;
};
- (void)changeView:(NSNotification *)notif{
NSString *bundlePath = @"";
NSString *bunldeName = @"";
bundlePath = [notif.object valueForKey:@"bundlePath"];
bunldeName = [notif.object valueForKey:@"bunldeName"];
// OC 的代码 我是方便调试弄的 如果你不需要可以去掉然后 把 [self presentViewController:vc animated:YES completion:nil]; 也去掉,当然还是看你们的需求吧
[self dismissViewControllerAnimated:YES completion:nil];
[self loadScript:bundlePath bunldeName:bunldeName];
};
// 监听通知
- (void)addObservers {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeView:) name:@"changeBunle" object:nil];
};
// 监听通知
- (void)removeObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self];
};
- (void)dealloc {
[self removeObservers];
};
// RNToolPackage.m
#import "RNToolPackage.h"
@implementation RNToolPackage
RCT_EXPORT_MODULE(RNToolsManager)
// 最简单的一个方法 变更多个bundle
RCT_REMAP_METHOD(changeActivity,
changeActivityWithA:( NSString *)bundlePath bunldeName:( NSString*)bunldeName
){
// 重新设置一个rootView
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"changeBunle" object:@{
@"bundlePath":bundlePath,
@"bunldeName":bunldeName,
}];
});
};
@end
最后要说的,我们需要统一一下android ios rn 的module 跳转方法
// ./common/native/index.js
changeActivity: (value) => {
// 此处可以优化 把名字全部统一,只需要确定一个规则 path 为 [moduleName].[platform].bundle
// 比如 common.ios.bundle, IO2.ios.bundle, common.android.bundle, IO2.android.bundle,
// 参数只需要 传递 IO2 就好了这个IOS2 应该和模块的 registerComponent name 保持一致!
if(Platform.OS === 'ios') {
return NativeModules.RNToolsManager.changeActivity(`bundle/${value}.ios`, value);
}
return NativeModules.RNToolsManager.changeActivity(value, null);
},
项目 | Android | IOS |
---|---|---|
依照官方进行集成 | ✅ 完成 | ✅ 完成 |
dev是否正常运行 | ✅ 完成 | ✅ 完成 |
build 一下是否正常运行 | ✅ 完成 | ✅ 完成 |
Assets 资源加载逻辑 | ✅ 完成 | ✅ 完成 |
native版本的包管理 | ✅ 完成 | ✅ 完成 |
------ | ------ | ------ |
初步的拆包方案 | ✅ 完成 | ✅ 完成 |
优化拆包方案 common + bu = runtime | ✅ 完成 | ✅ 完成 |
容器的缓存复用 | ✅ 完成 | ✅ 完成(bridge 复用) |
------ | ------ | ------ |
热更新的实现 | ✅ 完成 | ✅完成 |
WebView 的实现 | / | / |