架构规划阐明

该篇文章,介绍并记录在大前端混合架构开发中的重要细节和流程。经过在安卓原生工程中集成两大干流混合框架React Native、Flutter,以及ReactJs[Vue],集成三类模块module的架构的混合规划。并分别在这些干流技能栈的业务创作中,自己造轮子、运用新颖架构规划及核心技能去完成。并在编码过程中还会创造常用工具,沉溺式状况栏、底部导航栏、Flutter热更新、Flutter多进口、

tab1 tab2 tab3 tab4 tab5
仿招商银行主页 仿即时通讯 仿工商银行主页 仿抖音我的页面 仿唯品会分类

在原生工程中创立一个主页,在主页中运用五个TAB

  • TAB1,运用原生Java+Kotlin编码,仿招商银行主页,运用优异架构规划,完成列表各个模块的独立解耦。
  • TAB2,运用原生Java+Kotlin编码,仿微信,经过Android Socket完成IM的即时通讯。
  • TAB3,运用React Native编码,仿工商银行主页。
  • TAB4,运用Flutter编码,仿抖音我的页面。
  • TAB5,运用ReactJs编码,仿唯品会分类页面。

创立安卓原生工程

Android Studio版别 gradle 插件版别 gradle 版别 kotlin版别 JDK 版别 其他
3.6 3.6.0 gradle-6.7.1-all.zip(原5.6.4) 1.5.31 JDK11 compileSdkVersion 31、buildToolsVersion “30.0.0” 、minSdkVersion 21、targetSdkVersion 30

创立Flutter

Android Studio版别 gradle 插件版别 gradle 版别 JDK 版别 其他
3.6 3.6.0 gradle-5.6.4-all.zip JDK11 compileSdkVersion 31、buildToolsVersion “30.0.0” 、minSdkVersion 21、targetSdkVersion 30

集成嵌入原生工程

翔实集成介绍,请移步检查

创立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

node版别 React Native版别 JDK 版别 阐明
v16.17.0 0.67.0 JDK11 详细版别号,请移步检查代码

处理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确定版别号!!修正ReactNativeapp/build.gradleimplementation "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'

混合开发架构|Android工程集成React Native、Flutter、ReactJs
处理方案,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第一步装备:start
project.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第一步装备:end
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ......
    // 【装备共三步】rn第二步装备:start
    if (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_modules
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
    // 【装备共三步】rn第二步装备:end
}
// 【project/build.gradle】下进行装备
allprojects {
    repositories {
        ......
        // 【装备共三步】rn第三步装备:start
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url "$rootDir/../hibrid_rn/node_modules/react-native/android"
        }
        maven {
            // Android JSC is installed from npm
            url("$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.

混合开发架构|Android工程集成React Native、Flutter、ReactJs
报错信息说,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服务。

混合开发架构|Android工程集成React Native、Flutter、ReactJs

底部导航栏架构规划

运用java语言,自界说主页底部导航栏布局控件(下图完成作用+导航源码),自界说UML介绍~

混合开发架构|Android工程集成React Native、Flutter、ReactJs

  • 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点击切换显现做下伏笔。
    混合开发架构|Android工程集成React Native、Flutter、ReactJs
    混合开发架构|Android工程集成React Native、Flutter、ReactJs
  • TabBtnFragmentLayout,显现fragment页面的**自界说布局控件**,放在布局文件TabBtnLayoutBottomNav中。由TabBtnLayoutBottomNav增加的监听回调index,办法setCurrentItem获得指示并显现相应fragment页面。
  • TabBtnFragmentAdapter,显现fragment页面的适配器类。详细指示显现fragment页面逻辑,完成在办法instantiateItem中。
    混合开发架构|Android工程集成React Native、Flutter、ReactJs

原生仿招商银行主页

原生Android Socket 即时通讯

React Native仿工商银行主页

运用RN仿工商银行主页(图标自己费力吧啦找的),然后完成StatusBar和TitleBar**滑动突变**作用。此处由原生发动并翻开RN,作用如下~

混合开发架构|Android工程集成React Native、Flutter、ReactJs

原生传递props初始目标,RN运用

// React Native中装备bundle
val 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端调用。作用如下

混合开发架构|Android工程集成React Native、Flutter、ReactJs

开发中注意事项

这里以本项目作为示例,介绍下在完成RN和C端通讯时,开发步骤逻辑。 1, 逻辑源码C端中界说桥接类工具JRNBridge.kt。桥接类承继自ReactContextBaseJavaModule.java,完成办法getName —— 获取到的name会在ReactNative端运用,

// bridge/index.js
import {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列表为之后注册。

createNativeModules createViewManagers
ReactNative调用Native办法时重写并增加NativeModule Native UI,作为ReactNative UI是重写并增加ViewManager

4, 将MainReactPackage()和JReactPackage()增加注册到ReactInstanceManager中。 其间MainReactPackage()及JReactPackage()必须, 不然报错'StatusBarManager' could not be found. Verify that a module by this name is registered in the native binary.

混合开发架构|Android工程集成React Native、Flutter、ReactJs

JReactPackage()不然报错, 找不到RNBridge.toast({toast:'正在取号中,请稍后...'})

5, ReactNative中导出在NativeModules中已注册的JRNBridge。然后在ReactNative各个地方引入并运用。

混合开发架构|Android工程集成React Native、Flutter、ReactJs

ReactNative端源码

import RNBridge from '@bridge/index'
<TouchableOpacity onPress={()=>{RNBridge.toast({toast:'正在取号中,请稍后...'})}} >

Flutter仿抖音我的页面

Flutter热更新

Flutter热更新,经过动态.so文件的加载完成。

.so文件动态加载完成思路 以反射修正FlutterLoader.java类FlutterApplicationInfo.aotSharedLibraryName的值,然后修正了FlutterLoader将要加载的原libapp.solibapp**.so。之后,将用来替换的libapp**.so复制到原libapp.so地点的目录即可。复制的方式,如首先打包一个新的release-apk,然后解压提取出此时的libapp.so文件(修正称号为libapp**.so),放到目录assets/下。之后,当履行代码复制时,会将assets/目录下的so包复制到新指定的将会加载的libapp.so地点的目录,然后则顺理成章完成热更新。

混合开发架构|Android工程集成React Native、Flutter、ReactJs

混合开发架构|Android工程集成React Native、Flutter、ReactJs

多FlutterEngine引擎创立,多dart进口完成

flutter一个投资理财页,作为第二个dart进口。并经过下面界说的对应引擎启开。

混合开发架构|Android工程集成React Native、Flutter、ReactJs

多FlutterEngine引擎创立

经过FlutterEngine创立引擎实例目标。创立时传入的JFlutterLoader,重新界说了原FlutterLoader获取dart代码包的方式。之后,依据给定的DartEntrypoint开始履行Dart代码。并缓存已创立的Flutter引擎实例。其间传入给DartEntrypointmoduleName是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通讯

混合开发架构|Android工程集成React Native、Flutter、ReactJs

类型 阐明
MethodChannel 用于传递办法调用invokeMethod一次性通讯:如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仿唯品会分类页

翔实开发介绍,请移步检查

工程源码地址

点击进入仓库,检查工程源码