混合架构设计与开发<二十二>-------RN混合架构原理剖析与应用1【框架原理剖析、Native中集成RN、RN混合架构开发实战技巧】
大纲:
RN框架原理剖析:
认识RN源代码目录结构:
这里上RN的官网https://github.com/facebook/react-native/tree/v0.63.2:
Libraries:
React:
主要是React相关的一些代码,其中它主要是采用OC来实现的:
ReactAndroid:
这里主要是React Android对应的代码:
ReactCommon:
主要是React对于C++层的实现,实现了主要对于布局引擎,js core相关的封装。
RN框架的整体架构回忆:
首先来回忆一下之前https://www.cnblogs.com/webor2006/p/14670688.html已经认识的RN框架的整体架构:
RN启动流程:
1、打开RN中的Android代码:
这里以Android的视角来分析一下RN应用的启动流程,首先得要用Android Studio来打开RN里面Android相关的代码:
可以看到代码量是比较少的,因为主要实现则是在RN相关的jar中,注意目前我这边分析的RN的版本为:
2、认识ReactActivity:
打开MainActivity,发现它继承的是ReactActivity:
很显然RN在Android中的运行实现都是靠ReactActivity了,打开发现它里面使用了门面模式:
3、ReactActivity.onCreate():
走进它里面的细节,得要查看委托类了:
4、ReactActivityDelegate.loadApp():
其中看一下ReactRootView:
5、ReactRootView.startReactApplication():
在onCreate()中,最终会调用它:
下面则来看一下启动细节:
最终会调到这,就可以看到异步开启的代码了:
6、ReactRootView.attachToReactInstanceManager():
接下来则回到主流程往下:
具体添加的细节就不过多查看了,总之此时RN的界面就被打开了。
总结:
下面对上面所分析的流程用图总结一下:
其实对于整个RN的启动流程可以用debug的方式来进行一下跟踪,直接运行,此时会报如下错的:
因为木有启动Metro,启动一下:
再来运行,发现还是报错。。具体异常可以参考https://blog.csdn.net/qq_25827845/article/details/52974991,我这边处理办法就是重新杀掉adb,然后再到metro中按“r”重新加载一下,此时就会安装js的一些东东,跟之前敲“react-native run android”第一次安装的效果一样,然后退出app再打开就可以正常运行了,然后此时就可以按debug android app一样来在这打个断点:
跟进去就会进入:
跟进去:
貌似跟不到这。。
这里就不过多纠结了,反正是一种学习流程的一个调试方式。
RN组件的生命周期:
关于这块其实已经在之前https://www.cnblogs.com/webor2006/p/14609259.html学习过了,这里就不过多说明了,其整个生命周期的情况可以参考官方的这个来进行学习:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
混合开发场景:
1、在原有项目中加入RN页面:
这种是比较常见的,就是在Native中某个事件点击跳转到一个RN页面。
2、原生嵌套RN页面:
如之前原理分析,其实最终RN会以一个RootView进行渲染,这样就可以作为Native页面的一个元素进行嵌套使用了。
3、RN嵌套原生页面:
以上这些都属于RN混合开发的范畴,那么如何进行RN项目开发呢?
如何在原有的项目中集成RN:
1、创建一个RN项目;
在做混合开发之前我们首先需要创建一个没有Android和IOS模块的RN项目。通过"npx react-native init rn_module"来初始化一个RN项目,注意一下先回到咱们项目主工程的目录下:
接下来创建RN工程:
npx react-native init rn_module --version 0.62.2
上述命令会初始化一个名为rn_module的RN项目,当创建好之后,记得先进入RN目录运行一下
,看是否一切ok,当一切ok之后,然后我们将里面的android和ios目录删除,因为目前我们是要将RN集成到Native工程中,对于这俩目录是用不上的:
另外为啥要把rn的工程创建跟我们的主Android工具平级呢?其实是为了多仓库进行项目管理,因为一般使用到混合开发的项目都是比较大型的,所以可能RN和Android都是单独由不同组来维护的,所以势必会有多个版本仓库来分别进行维护,这样平行存放的目的就在于可以多团队一起来开发。
2、为已存在的Android应用添加RN所需要的依赖;
在上文中我们已经创建了一个RN项目,接下来我们来看一下如何将这个RN项目和我们已经存在的Native项目进行融合。
在进行融合之前我们需要将已经存在的Native项目放到我们创建的RNHybrid下,比如:我有一个名为ASProj的Android项目,将其放到RNHybrid目录下:
1、配置maven:
接下来我们需要为已经存在的ASProj项目添加RN依赖。在RNHybrid/ASProj/common/build.gradle文件中添加如下代码:
接下来咱们来配置一下,先配置RN:
//rn project.ext.react = [ entryFile: "index.android.js", enableHermes: false ] def enableHermes = project.ext.react.get("enableHermes", false); def jscFlavor = 'org.webkit:android-jsc:+' def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } //rn end
然后在dependencies中来配置一下:
//rn if (enableHermes) { def hermesPath = "../../rn_module/node_modules/hermesvm/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(hermesPath + "hermes-release.aar") } else { implementation jscFlavor } implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" //rn end
然后,我们为ASProj项目配置使用的本地React Native maven目录,在RNHybrid/ASProj/build.gradle文件中添加如下代码:
//rn maven { // All of React Native (JS, Android binaries) is installed from npm url "$rootDir/../rn_module/node_modules/react-native/android" } maven { // Android JSC is installed from npm url("$rootDir/../rn_module/node_modules/jsc-android/dist") } //rn end
2、配置权限:
接下来我们为APP运行配置所需要的权限,网络权限,这个就不多说了,另外如果你需要用到RN的Dev Settings功能:
也就是在开发时需要打开“Dev Settings”功能,此时则需要在AndroidManifest.xml中增加这么一个Activity的配置:
此时打开该功能时就会弹出上图的界面,这个界面就是DevSettingsActivity。
3、指定要ndk需要兼容的架构(重要)
Android不能同时加载多种架构的so库,现在很多Android第三方sdks对于abi的支持比较全,可能会包含armeabi、armeabi-v7a、x86、arm64-v8a、x86_64五种abi,如果不加限制直接引用会自动编译出支持5种abi的APK,而Android设备会从这些abi进行优先选择某一个,比如:arm64-v8a,但如果其他sdk不支持这个架构的abi的话就会出现crash,如下图:
怎么解决呢?在app/gradle文件中添加如下代码:
意思就是限制打包的so库只包含armabi-v7a与x86,其实配置的原则就是:
比如我们创建的RN的官方Android工程它所配置的架构如下:
而查看一下我们主工程所配置的so的架构:
刚好是rn所支持的,所以此时就不需要再配置了。
4、允许http明文调试:
从Android28开始,默认情况下Android会限止http链接的通信,所以,当我们创建的Android项目的targetSdkVersion版本是28或以上时就会出现http请求失败的情况。因为在开发模式下RN需要使用http来加载电脑上的js bundle,所以会导致RN加载不到js bundle,为此我们需要添加一下配置:
3、为React Native创建一个Activity来作为容器
经过以上步骤, 我们已经为ASProj项目添加了React Native依赖,并且注册了一个名为run_module的组件,接下来则来学习一下如何在咱们的主项目中使用这个rn_module组件。
1、创建HiRNActivity:
首先我们需要创建一个Activity来作为React Native的容器:
这里就需要用到咱们在之前分析rn启动原理时的东东了,如下:
package org.devio.`as`.proj.common.rn import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.facebook.react.BuildConfig import com.facebook.react.ReactInstanceManager import com.facebook.react.ReactRootView import com.facebook.react.common.LifecycleState import com.facebook.react.shell.MainReactPackage class HiRNActivity : AppCompatActivity() { private var mReactRootView: ReactRootView? = null private var mReactInstanceManager: ReactInstanceManager? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initRN() } private fun initRN() { mReactRootView = ReactRootView(this) mReactInstanceManager = ReactInstanceManager.builder() .setApplication(application) .setCurrentActivity(this) .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .addPackage(MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG)//设置是否开启开发者模式 .setInitialLifecycleState(LifecycleState.RESUMED) .build() } }
其中有一些参数,下面说明一下:
- setBundleAssetName:打包时放在assets目录下的JS Bundle包中的名字,App release之后会从该目录下加载JS bundle;
- setJSMainModulePath:JS bundle中主入口的文件名,也就是我们上文中创建的那个index.js文件;
- addPackage:向RN添加Native Module,在上述代码中我们添加了new MainReactPackage()这个是必须的,另外,如果我们创建一些其他的Native Module也需要通过addPackage的方式将其注册到RN中。需要指出的是RN除了这个方法外,也提供了一个addPackages方法用于批量向RN添加Native Module;
- setUseDeveloperSupport:设置RN是否开启开发模式(debugging,reload,dev menu),比如我们常用的开发者弹框;
- setInitialLifecycleState:通过这个方法来设置RN初始化时所处的生命周期状态,一般设置成LifecycleState.RESUMED就行,和下面讲的Activity容器的生命周期状态关联。
接下来就可以调用启动RN了:
它的第二个参数“rn_module”是我们在RN项目中的index.js中注册的组件的名字,也就是:
而它又来自于app.json,它里面有一个:
一定要一样,不然是加载不出来的。
最后关键一步不要忘了:
4、将RN的Activity添加到playground中进行测试;
先给Activity定义Arouter path:
然后增加点击按钮:
5、启动RN的Packager服务,运行应用;
一切就绪,接下来运行看一下效果,先到rn的工程中启动Packager服务,如下:
然后再运行到手机上看一下,报错了。。
2021-05-29 04:53:19.861 30783-30907/org.devio.as.proj.main E/CrashReport: java.lang.RuntimeException: Unable to load script. Make sure you're either running a Metro server (run 'react-native start') or that your bundle 'index.android.bundle' is packaged correctly for release. at com.facebook.react.bridge.CatalystInstanceImpl.jniLoadScriptFromAssets(Native Method) at com.facebook.react.bridge.CatalystInstanceImpl.loadScriptFromAssets(CatalystInstanceImpl.java:235) at com.facebook.react.bridge.JSBundleLoader$1.loadScript(JSBundleLoader.java:29) at com.facebook.react.bridge.CatalystInstanceImpl.runJSBundle(CatalystInstanceImpl.java:259) at com.facebook.react.ReactInstanceManager.createReactContext(ReactInstanceManager.java:1243) at com.facebook.react.ReactInstanceManager.access$1000(ReactInstanceManager.java:132) at com.facebook.react.ReactInstanceManager$5.run(ReactInstanceManager.java:996) at java.lang.Thread.run(Thread.java:764)
这块坑一定得要注意!!!找了我2天,这里直接给出答案了,是这块出问题了。。
也是醉了,犯了低级错误。。改一下:
运行,此时有可能会报这个异常:
需要执行一下它:
然后此时再运行,发现还是会报错。。
这里也不知道是不是目前RN0.62.2这个版本的bug,如果切到0.63.3这个版本一切妥妥的,这里还是解决一下这个错误,毕竟我这边本地环境如果想要在ios上运行0.63.3这个版本会有点问题,于是乎网上搜一下:https://blog.csdn.net/bzb123321/article/details/88892478,它解决了我有问题:
也就是清除一下缓存就行了,并不是0.62.2的bug,运行效果如下:
RN混合架构开发实战技巧:
为ReactInstanceManager添加生命周期回调:
目前咱们已经成功的将RN集成到了咱们的Android主工程中,由于ReactInstanceManager它会被多个Activity和多个Fragment所共享,所以需要让它能感受到Activity和Fragment整个的生命周期,这样RN页面才能识别Activity和Fragment的生命周期,其处理也比较简单,如下:
另外为了能响应Activity的返回效果,还需要做如下处理,DefaultHardwareBackBtnHandler:
此时运行一下,就能正常的将咱们的RN页面进行返回键的响应了:
加入RN debug menu的支持:
对于一个纯RN项目支持debug菜单肯定不用多说,就是指的它:
在metro窗口中按下d键就可以打开:
但是!!!目前咱们不是一个纯RN的项目,还是一个Native+RN的混合工程,这里可以拦截一下事件主动添加一个这样的功能,如下:
这样当按下菜单键时,就会主动弹调试菜单,不过貌似在我真机上木有啥作用,本身在metro窗口输入"d"和摇手机都支持弹出,这里就当是一个功能扩展吧。另外整个原来学的RN调试技巧都可以在混合开发中得到应用,这里就不过多说明了。
添加更多RN的组件:
我们可以根据需要添加更多的RN组件,如:
然后在Native中根据需要加载指定名字的RN组件既可,目前咱们加载的是只有一个组件,瞅一下:
其实也就是对应RN中这块的注册:
那如果再增加一个组件那该怎么办呢?下面新建一个:
此时则到这块注册一下:
然后咱们在Native使用时就更改一下这:
运行:
打包:
调试:
调试这种混合的RN应用和调试一个纯RN应用是一样的,都是通过上文中所讲到的“RN开发者菜单”。
打包:
虽然通过上述步骤,我们将RN和我们的ASProj项目做了融合,但打包ASProj你会发现里面并不包含JS部分的代码:
其中我们在加载代码中有这么一句:
其实它是需要通过命令来打包的,目前也能发现咱们混合项目的运行是需要依赖于npm 服务器的,也就是它:
那有木有办法不运行这个npm服务器,直接运行Andorid app也可以正常的运行RN呢?答案是可以的,就是将RN的JS代码打包进Android APK包中,如果要将JS代码打包进Android APK包中,可以通过如下命令:
参数说明:
- --platform android:代表打包导出的平台为Android;
- --dev false:代表关闭JS的开发者模式;
- -entry-file index.js:代表js的入口文件为index.js;
- --bundle-output:后面跟的是打包后将JS bundle包导出到的位置 ;
- --assets-dest:后面跟的是打包后的一些资源文件导出到的位置;
提示:JS Bundle一定要正确放到你的Android工程的assets目录下这个和我们在上面配置的进行对应:
咱们来试一下:
(base) xiongweideMacBook-Pro:rn_module xiongwei$ react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../Asproj/app/src/main/assets/index.android.bundle --assets-dest ../Asproj/app/src/main/res/ Welcome to React Native! Learn once, write anywhere info Writing bundle output to:, ../Asproj/app/src/main/assets/index.android.bundle info Done writing bundle output info Copying 2 asset files info Done copying assets
这里看一下咱们Android工程有木有生成相关的JS文件:
下面就可以运行一下,注意此时可以把npm 服务器可以停止了:
貌似没看出啥区别,细看还是有一些小区别的,没有那个js bundle的进度提示了,因为js代码已经打到apk中了,由于此时打包的是一个debug版本的,所有还可以打开调试菜单,但是再reload时,就报错了:
为啥呢?因为此时还是需要依赖于npm 服务器的,但是!!!如果通过android studio打出release包就没有这个调试菜单了,这一点注意。
发布应用:
通过上述步骤我们就完成了将RN代码打包并生成JS Bundle,并放到了asserts目录下,接下来我们就可以来通过Android Studio或者命令的方式来release我们的RN混合Android应用了。