混合开发架构|Android工程集成React Native、Flutter、ReactJs
架构设计说明创建安卓原生工程创建Flutter集成嵌入原生工程创建React Native解决RN报错问题集成嵌入原生工程RN集成后,启动报错底部导航栏架构设计原生仿招商银行首页原生Android Socket 即时通讯React Native仿工商银行首页原生传递props初始对象,RN使用原生与RN通信已实现效果介绍开发中注意事项Flutter仿抖音我的页面Flutter热更新多FlutterEngine引擎创建,多dart入口实现结合Flutter源码,分析从原生端启动Flutter原生与Flutter通信ReactJs仿唯品会分类页面工程源码地址架构设计说明
该篇文章,介绍并记录在大前端混合架构开发中的重要细节和流程。通过在安卓原生工程中集成两大主流混合框架React Native、Flutter,以及ReactJs[Vue],集成三类模块module的架构的混合设计。并分别在这些主流技术栈的业务创作中,自己造轮子、使用新颖架构设计及核心技术去实现。并在编码过程中还会创造常用工具,沉浸式状态栏、底部导航栏、Flutter热更新、Flutter多入口、
在原生工程中创建一个首页,在首页中使用五个TAB
。
TAB1
,使用原生Java+Kotlin编码,仿招商银行首页,使用优秀架构设计,完成列表各个模块的独立解耦。TAB2
,使用原生Java+Kotlin编码,仿微信,通过Android Socket实现IM的即时通讯。TAB3
,使用React Native编码,仿工商银行首页。TAB4
,使用Flutter编码,仿抖音我的页面。TAB5
,使用ReactJs编码,仿唯品会分类页面。
创建安卓原生工程
创建Flutter
集成嵌入原生工程
详尽集成介绍,请移步查看
创建React Native
与创建Flutter相比较,React Native工程创建时,复杂很多。创建时候会遇到创建失败问题,成功创建后,启动Metro服务也会遇到报错问题。而这些问题都与nodejs版本
,React Naitve版本
有关。请详细阅读官方搭建环境的文档,
React Native 创建指令:
npx react-native init hibrid_rn --version 0.67.0
Metro服务启动指令:
npx react-native start
Android apk 编译安装指令:
yarn android
解决RN报错问题
若有报错,下面
如下报错信息,则是node版本号使用不当导致~
/node_modules/@react-native-community/cli/build/commands/doctor/healthchecks/index.js:48
} catch {}
^
SyntaxError: Unexpected token {
at createScript (vm.js:80:10)
at Object.runInThisContext (vm.js:139:10)
at Module._compile (module.js:617:28)
at Object.Module._extensions…js (module.js:664:10)
… … …
若有报错,下面
Could not find react-native-0.71.0-rc.0-debug.aar (com.facebook.react:react-native:0.71.0-rc.0).
Could not determine the dependencies of task ':app:lintVitalRelease'.> Could not resolve all artifacts for configuration ':app:debugCompileClasspath'.> Could not find react-native-0.71.0-rc.0-debug.aar (com.facebook.react:react-native:0.71.0-rc.0).
解决方案,指定ReactNative确定版本号!!修改ReactNative中app/build.gradle
下
implementation "com.facebook.react:react-native:+"
改为implementation "com.facebook.react:react-native:0.67.0"
若有报错,下面
Execution failed for task ':app:mergeDebugNativeLibs'.
More than one file was found with OS independent path 'lib/x86_64/libfbjni.so'
解决方案,在app/build.gradle
android{}中添加截图中报错的如lib/x86_64/libfbjni.so
packagingOptions {pickFirst 'lib/x86/libc++_shared.so'pickFirst 'lib/x86_64/libc++_shared.so'pickFirst 'lib/armeabi-v7a/libc++_shared.so'pickFirst 'lib/arm64-v8a/libc++_shared.so'pickFirst 'lib/x86/libfbjni.so'pickFirst 'lib/x86_64/libfbjni.so' // 截图中有这个报错,这里添加该so修复pickFirst 'lib/armeabi-v7a/libfbjni.so'pickFirst 'lib/arm64-v8a/libfbjni.so'}
集成嵌入原生工程
// 【app/build.gradle】下进行配置// 【配置共三步】rn第一步配置:startproject.ext.react = [entryFile : "index.android.js",enableHermes: false,bundleInDebug:true,bundleInBeta:true]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第一步配置:enddependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])......// 【配置共三步】rn第二步配置:startif (enableHermes) {def hermesPath = "../../hibrid_rn/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_modulesimplementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"// 【配置共三步】rn第二步配置:end}// 【project/build.gradle】下进行配置allprojects {repositories {......// 【配置共三步】rn第三步配置:startmaven {// All of React Native (JS, Android binaries) is installed from npmurl "$rootDir/../hibrid_rn/node_modules/react-native/android"}maven {// Android JSC is installed from npmurl("$rootDir/../hibrid_rn/node_modules/jsc-android/dist")}//【配置共三步】rn第三步配置:end}}
参数字段说明:
entryFile : "index.android.js"
,表示配置加载安卓资源入口文件名称。def hermesPath = "../../hibrid_rn/node_modules/hermesvm/android/"
,表示当enableHermes==true
时,引入执行引擎Hermes。否则,使用JavaScriptCore执行引擎。
Hermes 是一个可选的 React Native 功能。如果要启用Hermes,需要确保 React Native项目的版本在
0.60.2版本
以上,并且还需要对android/app/build.gradle
做以下更改。我们这里配置enableHermes: false
project.ext.react = [entryFile: "index.js",enableHermes: true // 配置开启Hermes引擎]
RN集成后,启动报错
在原生工程中集成了RN之后,将React Native作为原生工程Activity下LayoutView的一部分,测试集成状态。发现从Native启动React Native报以下错误~
ReactNative: Exception in native call java.lang.RuntimeException: Unable to load script. Make sure you're either running Metro (run 'npx react-native start') or that your bundle 'rn/index.android.bundle' is packaged correctly for release.
报错信息说,Metro服务未启动,或者说找不到bundle资源包。而事实是当前Metro服务已启动,且直接启动React Native工程是OK的。对此寻到以下两种解决方案 :
对当前React Native
工程代码进行打包,并将打包后的bundle资源
拷贝到Native工程的/main/assets/rn
目录下。然后在Native运行则无问题。VSCode终端执行打包指令:react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../HybridArcPro/app/src/main/assets/rn/index.android.bundle --assets-dest ../HybridArcPro/app/src/main/res/
非打包处理。需要对Native和React Native端同时进行配置。①对安装的debug包APP配置服务IP和端口号。②配置网络权限,application标签
中配置tools:targetApi="28" android:allowBackup="true"
。③启动Metro服务。
底部导航栏架构设计
使用java语言,自定义首页底部导航栏布局控件(下图实现效果+导航源码),自定义UML介绍~
TabBtnLayoutBottomNav
底部导航栏布局自定义View。继承自FrameLayout,实现自接口ITabLayout。底部导航栏布局,内部摆放TabBtnBottom
,TabBtnBottom
底部导航栏布局中的单个Tab。继承自RelativeLayout,实现自I接口ITab(ITab继承了点击事件的监听接口OnTabSelectedListener)。TabBtnLayoutBottomNav
内部封装单个Tab集合List<OnTabSelectedListener>
(包含所有TabBtnBottom和TabBtnLayoutBottomNav添加的监听OnTabSelectedListener),当用户点击Tab时,点击事件通过TabBtnBottom.setOnClickListener
触发集合List的遍历,此时将点击事件传递给每个TabBtnBottomz,同时TabBtnLayoutBottomNav添加的监听回调。由此单个Tab和TabBtnLayoutBottomNav产生了点击事件的关联,并能为集成fragment点击切换显示做下伏笔。
TabBtnFragmentLayout
,显示fragment页面的**自定义布局控件**,放在布局文件TabBtnLayoutBottomNav中。由TabBtnLayoutBottomNav添加的监听回调index,方法setCurrentItem
获得指示并显示相应fragment页面。TabBtnFragmentAdapter
,显示fragment页面的适配器类。具体指示显示fragment页面逻辑,实现在方法instantiateItem
中。
原生仿招商银行首页
原生Android Socket 即时通讯
React Native仿工商银行首页
使用RN仿工商银行首页(图标自己费劲吧啦找的),然后实现StatusBar和TitleBar
随**滑动渐变**
效果。此处由原生启动并打开RN,效果如下~
原生传递props初始对象,RN使用
// React Native中配置bundleval bundle = Bundle()rnBundle.putCharSequence("device-info","设备信息对象")rnBundle.putCharSequence("state","用户登录状态")mReactRootView!!.startReactApplication(mReactInstanceManager, "hibrid_rn", bundle)// 在对应的ReactNative的Coponent中获取,则可通过this.props得到!
原生与RN通信
已实现效果介绍
在Native端桥接类JRNBridge.kt
中定义了一个调用安卓Toast的方法,供ReactNative端调用。效果如下
开发中注意事项
这里以本项目作为示例,介绍下在实现RN和C端通信时,开发步骤逻辑。
1,逻辑源码C端中定义桥接类工具JRNBridge.kt
。桥接类继承自ReactContextBaseJavaModule.java,实现方法getName
—— 获取到的name会在ReactNative端使用,
// bridge/index.jsimport {NativeModules} from 'react-native'module.exports = NativeModules.RNBridge // 这里的 RNBridge 就是getName得到。
2,ReactNative和C端Native进行通信的方法,需要加注解@ReactMethod
。
3,创建JReactPackage.kt 继承ReactPackage.kt
,重写方法createNativeModules和createViewManagers
。重写方法createNativeModules
是将JReactPackage添加到NativeModule列表为之后注册。
4,将MainReactPackage()和JReactPackage()添加注册到ReactInstanceManager中。
其中MainReactPackage()及JReactPackage()必须,否则报错'StatusBarManager' could not be found. Verify that a module by this name is registered in the native binary.
JReactPackage()否则报错,找不到RNBridge.toast({toast:'正在取号中,请稍后...'})
。
5,ReactNative中导出在NativeModules中已注册的JRNBridge。然后在ReactNative各个地方引入并使用。
ReactNative端源码
import RNBridge from '@bridge/index'<TouchableOpacity onPress={()=>{RNBridge.toast({toast:'正在取号中,请稍后...'})}} >
Flutter仿抖音我的页面
Flutter热更新
Flutter热更新,通过动态.so文件的加载实现。
.so文件动态加载实现思路
以反射修改FlutterLoader.java类
中FlutterApplicationInfo.aotSharedLibraryName
的值,从而修改了FlutterLoader将要加载的原libapp.so
为libapp**.so
。之后,将用来替换的libapp**.so
拷贝到原libapp.so
所在的目录即可。拷贝的方式,如首先打包一个新的release-apk,然后解压提取出此时的libapp.so文件(修改名称为libapp**.so),放到目录assets/下。之后,当执行代码拷贝时,会将assets/目录下的so包拷贝到新指定的将会加载的libapp.so
所在的目录,然后则顺理成章完成热更新。
多FlutterEngine引擎创建,多dart入口实现
flutter一个投资理财页,作为第二个dart入口。并通过下面定义的对应引擎启开。
多FlutterEngine引擎创建
通过FlutterEngine
创建引擎实例对象。创建时传入的JFlutterLoader
,重新定义了原FlutterLoader获取dart代码包
的方式。之后,根据给定的DartEntrypoint开始执行Dart代码。并缓存已创建的Flutter引擎
实例。其中传入给DartEntrypoint
的moduleName是dart入口名称
,findAppBundlePath是flutter资产目录flutterAssetsDir
// 初始化,根据moduleName(dart入口名称)创建多个Flutter引擎private fun initFlutterEngine(context: Context, moduleName: String): FlutterEngine? {var flutterEngine:FlutterEngine = FlutterEngine(context, JFlutterLoader.get(), FlutterJNI())flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint(JFlutterLoader.get().findAppBundlePath(), moduleName))FlutterEngineCache.getInstance().put(moduleName, flutterEngine) // 缓存起来return flutterEngine}
多dart入口创建
// 在flutter的dart代码中main.dart// 至少有一个默认入口,如 'main'void main() {runApp(const MyApp());init();}// 此时,可仿照默认入口,通过注解,创建多个不同的dart入口 - 工行的'投资理财'详情页面@pragma('vm:entry-point')void finance() {runApp(FinanceEntryApp());}
结合Flutter源码,分析从原生端启动Flutter
原生与Flutter通信
这里且介绍MethodChannel,在Native与Flutter间如何通信~Flutter侧发送,Native侧接收处理。
Flutter侧发送
,结合源码看,通过创建一个MethodChannel实例
并指定渠道名称name
。且两侧的name
须一致相同。然后使用MethodChannel实例调用执行方法invokeMethod
,该方法传入Native侧将被调用方法名称method
及通信消息内容arguments
。之后,便启动了由Flutter向Native侧传递调用。
Native侧接收处理
,创建一个与Flutter侧渠道名称name
相同的MethodChannel实例
。使用MethodChannel实例调用执行方法setMethodCallHandler
,用以匹配Flutter侧方法名称method
接收处理Flutter侧发送来的信息。详尽设计实现,请移步查看
ReactJs仿唯品会分类页面
详尽开发介绍,请移步查看
工程源码地址
点击进入仓库,查看工程源码
如果觉得《混合开发架构|Android工程集成React Native Flutter ReactJs》对你有帮助,请点赞、收藏,并留下你的观点哦!