ReactNative混合开发,Facebook官网文档比较精简,坑比较多,根据此文档,作为初学者并不能顺利的将ReactNative引入Android项目,且不能了解ReactNative是以一种怎样的形式存在Native项目中。
此为开篇,解说官方引入方案,附带了我的踩坑经验,引导小诀窍,让你傻瓜式一键集成ReactNative。后续文章会介绍云集项目中React的实践,优化方案。这是一篇入门篇,没有门槛,何来高楼。
希望这一个系列的React文章能够帮助大家快速了解ReactNative的混合开发与掌握云集App中React的使用,快速定位相关问题,人人成为ReactNative小能手。
本文需要有初步的React Native基础,至少有能够运行一个React Native demo的环境与项目。
适用人群:Android工程师 Or 对ReactNative感兴趣的开发者
加载原理
(1)、Facebook有能够将页面的JS,通过自研的Metro工具转化一个bundle的压缩文件;
(2)、同时开发了一套能够解析Bundle文件中JS转化为Native行为的黑盒子工具API;
(3)、集成至于Native的根本原理,就是将Facebook的黑盒API引入到Android项目,同时能够加载Bundle文件;
(4)、集成ReactNative到原生项目分两大步:第一步引入依赖,第二步初始化ReactNative,加载页面;
1、正确引入ReactNative相关依赖
1.1、准备,组建一个React Native项目
- 首先按照开发环境搭建教程来安装 React Native 在 Android 平台上所需的一切依赖软件,创建一个React项目,
//初始化创建项目-AwesomeProject
npm init AwesomeProject
//下载React相关依赖,React Native (JS, Android binaries)等
npm install
//安装项目
react-native run-android
项目中package.json文件,可修改指定的React Native版本号
- 利用Android Studio模式打开AwesomeProject项目,编译Gradle
小技巧:成功编译后,对引入第三方lib,打包成aar,非常便捷
1.2、集成React Native
-
Android项目Lib or App下创建node_modules\react-native\android目录,并从AwesomeProject项目中node_modules\react-native\android目录下所有的文件拷贝至该目录
1.3、集成配置 maven
- 你的 app 中
build.gradle
文件中添加 React Native 依赖:
dependencies {
implementation 'com.android.support:appcompat-v7:27.1.1'
...
//如要指定特定的 React Native 版本,可以用具体的版本号替换 +,当然前提是你从 npm 里下载的是这个版本。
implementation "com.facebook.react:react-native:+" // From node_modules
}
-
在项目的
build.gradle
文件中为 React Native 添加一个 maven 依赖的入口,必须写在 “allprojects” 代码块中:allprojects { repositories { maven { // All of React Native (JS, Android binaries) is installed from npm //上述项目中引入react-native的相对目录位置 url "$rootDir/xxx/node_modules/react-native/android" } ... } ... }
$rootDir
表示项目根目录
1.4、集成React Native的基础Lib
- 你的 app 中
build.gradle
文件中添加-图片依赖
dependencies {
...
//匹配图片框架与对应的版本号
implementation 'com.facebook.fresco:animated-gif:2.0.0'
implementation "com.facebook.fresco:webpsupport:2.0.0"
}
匹配版本号,从引入的ReactNative文件中,找出react-native-0.61.3.pom,查询对应的fresco的版本号
//打开此文件node_modules\...\react-native\react-native-0.61.3.pom查看各个依赖版本号
...
<dependency>
<groupId>com.facebook.fresco</groupId>
<artifactId>imagepipeline-okhttp3</artifactId>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.12.1</version>
<scope>compile</scope>
</dependency>
...
- 引入第三方lib【推荐AAR方式 or 源码方式 】
//app 中 `build.gradle` 文件中添加 React Native 依赖
dependencies {
...
implementation(name: 'react-native-video-5.0.2', ext: 'aar')
implementation(name: 'react-native-vector-icons-6.6.0', ext: 'aar')
..........
}
小技巧:
1、通过Android Studio打开项目,在对应lib中,通过Gradle-assemble指令生成aar
2、第三方依赖某些适合并不完成与你的项目匹配,源码更容易修改调整
2、初始化ReactNative,加载开发页面Bundle
2.1、初始化React
applicatiion中,so初始化,ReactNativeHost初始化
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
/**
* 开发模式开发【红屏模式开关】
*
* @return
*/
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* 自定义React package链接处
*
* @return
*/
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
/**
* 开发模式,地址路径
* @return
*/
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
/**
* React相关so初始化
*/
SoLoader.init(this, /* native exopackage */ false);
}
}
需要在ReactNativeHost下重写getJSBundleFile(),指定bundle路径,否则将加载默认文件【assets://index.android.bundle】
2.2、加载React开发的页面,
public class MainActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
// Js入口AppRegistry.registerComponent中的appKey参数
return "rnBuyer";
}
}
到此为止普通的官方版的ReactNative已经集成完成,是不是很简单;【业务适用版集成方式,请关注后续React文章】
2.3、页面加载流程分解
①、React页面继承ReactActivity,逐步往上查看源码,可以看到ReactActivityDelegate.java中
........
//要么加载bundle入口
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
.....
②、再次深入,facebook内部加载过程,ReactRootView.java呈现
ReactRootView.java
.....
/**
* Schedule rendering of the react component rendered by the JS application from the given JS
* module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the JS
* context of that manager. Extra parameter {@param launchOptions} can be used to pass initial
* properties for the react component.
*/
public void startReactApplication(
ReactInstanceManager reactInstanceManager,
String moduleName,
@Nullable Bundle initialProperties,
@Nullable String initialUITemplate) {
//...无关代码
//子线程加载bundle
mReactInstanceManager.createReactContextInBackground();
//...无关代码
}
.....
③、在深入,ReactInstanceManager类中选择对应的bundle来源,区分开发模式情况,与release情况
-----------------ReactInstanceManager.java
//主线程选择bundle
@ThreadConfined(UI)
private void recreateReactContextInBackgroundInner() {
Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
UiThreadUtil.assertOnUiThread();
//开发者模式,取bundle方式, application中给mUseDeveloperSupport初始化赋值模式
if (mUseDeveloperSupport && mJSMainModulePath != null) {
final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
if (!Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
if (mBundleLoader == null) {
mDevSupportManager.handleReloadJS();
} else {
//mDevSupportManager.isPackagerRunning()后面React加载会优先检查metro服务器是否打通
mDevSupportManager.isPackagerRunning(
new PackagerStatusCallback() {
@Override
public void onPackagerStatusFetched(final boolean packagerIsRunning) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
//metro通,会优先加载服务器的bundle,否则加载本地
if (packagerIsRunning) {
//服务器bundle
mDevSupportManager.handleReloadJS();
} else if (mDevSupportManager.hasUpToDateJSBundleInCache()
&& !devSettings.isRemoteJSDebugEnabled()) {
// If there is a up-to-date bundle downloaded from server,
// with remote JS debugging disabled, always use that.
onJSBundleLoadedFromServer(null);
} else {
// If dev server is down, disable the remote JS debugging.
devSettings.setRemoteJSDebugEnabled(false);
//本地bundle
recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
}
//release模式下,加载bundle路径
recreateReactContextInBackgroundFromBundleLoader();
}
-----------------release模式下继续加载bundle,ReactInstanceManager.java
//......无关代码
@ThreadConfined(UI)
private void runCreateReactContextOnNewThread(final ReactInstanceManager.ReactContextInitParams initParams) {
//.....无关代码
//加载bundle
final ReactApplicationContext reactApplicationContext =createReactContext(initParams.getJsExecutorFactory().create(),initParams.getJsBundleLoader());
//.....无关代码
}
//.....无关代码
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
//.....无关代码
//加载初始化赋值的本地bundle
catalystInstance.runJSBundle();
//.....无关代码
}
//.....无关代码
-----------------DevServerHelper.java meto服务间相关交互检查实践类
....
//通过http://localhost:8081/status 检测metro是否通顺
public void isPackagerRunning(final PackagerStatusCallback callback)
....
④、回顾整个流程,整个流程图如图:
2.4、升级到ReactNative 0.61.3版本采坑
-
仅支持so的armeabi_v7架构
-
当Android 4.4系统中报xx.so找不到,但是在Android高版本中加载正常
解决思路:xx.so与腾讯的xx.so冲突,报xx.so找不到,解析Apk,对比so的Md5后,发现xx.so非facebook提供的so,分析React的so加载流程,走串行链式加载方式
总结:
此次分享内容就是关于快速集成React到Native-Android的方式与踩坑,和解析了bundle在Android APP中是通过怎么流程进行加载的,方便大家快速上手集成,关于在云集项目中ReactNative是怎么封装使用的,见后续分享。
参考文献
2.4、升级到ReactNative 0.61.3版本采坑
-
仅支持so的armeabi_v7架构
-
当Android 4.4系统中报xx.so找不到,但是在Android高版本中加载正常
解决思路:xx.so与腾讯的xx.so冲突,报xx.so找不到,解析Apk,对比so的Md5后,发现xx.so非facebook提供的so,分析React的so加载流程,走串行链式加载方式
总结:
此次分享内容就是关于快速集成React到Native-Android的方式与踩坑,和解析了bundle在Android APP中是通过怎么流程进行加载的,方便大家快速上手集成,关于在云集项目中ReactNative是怎么封装使用的,见后续分享。