混合架构设计与开发<二十二>-------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应用了。

posted on 2021-06-01 11:09  cexo  阅读(566)  评论(0编辑  收藏  举报

导航