重磅消息!Flutter在哈啰出行-B-端创新业务的实践,带你直达巅峰!

页面模块化 1.0 主要提供以下能力:

  • 模块挂载
  • 模块初始化
  • 模块异步初始化

挂载完成之后,初始化 root 模块,会将所有挂载在树上的模块都进行初始化。这个树形结构在叶子节点就是页面,页面的路径天然可作为页面的 url。

模块划分本质上是根据业务域对页面进行组织。不管是单一仓库还是多仓库,都可以通过这种简单的树形结构来实现模块挂载和初始化。

页面间通信 1.0

模块间通信,本质上主要是页面间通信。

移动端很多模块化的方案,都会将模块间通信作为主要能力进行建设,我们在原生端也有一套这类方案,但在 Flutter 嵌入原生应用中,并不能简单复用这套方案,如果生搬硬套会带来很多的编码量,并不是一个很轻量的解决方案。

页面间通信的能力,需要重头开始建设,早期我们抽象了一个状态同步的方案,开发一个插件 topic_center 专门用来给原生和 dart 进行状态同步。

topic_center 提供的能力:

  • 原生模块间的状态同步
  • Flutter 模块间的状态同步
  • Flutter 端按需同步原生状态
  • 三端一致的状态的获取与订阅 API

topic_center Flutter 端按需同步原生状态的数据流:

topic_center 提供如下的 API,topic_center 遵循 Flutter 的多端一致性原则,我们在三端提供了一样的 API,下图仅展示 dart 的 API 定义:

dart
void putValue(T value, String topic);

Future getValue(String topic);

Stream getValueStream(String topic);

void putListValue(E value, String topic);

Future<List> getListValue(String topic);

Stream<List> getListValueStream(String topic);

void putMapValue<K, V>(Map<K, V> value, String topic);

Future<Map<K, V>> getMapValue<K, V>(String topic);

Stream<Map<K, V>> getMapValueStream<K, V>(String topic);

void putTuple2Value<T0, T1>(Tuple2<T0, T1> value, String topic);

Future<Tuple2<T0, T1>> getTuple2Value<T0, T1>(String topic);

Stream<Tuple2<T0, T1>> getTuple2ValueStream<T0, T1>(String topic);

void putTuple3Value<T0, T1, T2>(Tuple3<T0, T1, T2> value, String topic);

Future<Tuple3<T0, T1, T2>> getTuple3Value<T0, T1, T2>(String topic);

Stream<Tuple3<T0, T1, T2>> getTuple3ValueStream<T0, T1, T2>(String topic);

void putTuple4Value<T0, T1, T2, T3>(Tuple4<T0, T1, T2, T3> value, String topic);

Future<Tuple4<T0, T1, T2, T3>> getTuple4Value<T0, T1, T2, T3>(String topic);

Stream<Tuple4<T0, T1, T2, T3>> getTuple4ValueStream<T0, T1, T2, T3>(String topic);

void putTuple5Value<T0, T1, T2, T3, T4>(Tuple5<T0, T1, T2, T3, T4> value, String topic);

Future<Tuple5<T0, T1, T2, T3, T4>> getTuple5Value<T0, T1, T2, T3, T4>(String topic);

Stream<Tuple5<T0, T1, T2, T3, T4>> getTuple5ValueStream<T0, T1, T2, T3, T4>(String topic);

topic_center 是我们在 架构 1.0 时提供的页面间通信解决方案,后面会讲到我们在进行架构升级之后提供的更轻量级的解决方案。

页面栈管理 1.0

如果没有混合栈管理,我们在原生应用上引入 Flutter 将是一个极为麻烦的事情,我们可能为此维护比较混乱的 Channel 通信层。

flutter_boost 是闲鱼开源的优秀的 Flutter 混合栈管理解决方案,也是当时社区唯一可选的解决方案。

flutter_boost 的优势:

  • Flutter 页面的路由与原生页面一样
  • Flutter 页面的交互手势与原生页面一样
  • 提供页面关闭回传参数的能力

如果不使用 flutter_boost,我们的页面结构可能是这样的:

使用了 flutter_boost 之后可以是这样的:

架构 1.0 的问题

页面间通信 1.0 的问题

  • topic 的管理成本过高

topic_center 插件能解决页面间通信的问题,但有一个不算问题的问题,对 topic 的管理成本过高。为了避免全局 topic 重复的问题,每个页面状态的同步都需要在 topic 上带上各种前缀,一般就是 模块、子模块、功能、页面作为前缀,然后这个 topic 最后长得跟页面的 url 极为相似。为了解决这个问题,需要想办法去掉这个 topic 的管理成本。

  • 源码过于复杂

topic_center 这个库的投入产出比实在是不高,源码过于复杂 带来不只是解决方案的复杂,也带来 维护成本推高 很多。

页面栈管理 1.0 的问题

  • 路由 API 过于简陋

比如,项目上需要实现关闭到某个页面的场景,或者删除当前页面之下的某个页面,我们需要在 flutter_boost 上自行扩展,且难于维护,如何跟官方的 flutter_boost 保持代码同步是一个艰难的事情。

  • 使用的开源库的 API 不再向后兼容

我们在项目上大量使用页面回传参数的能力,但是该 API 在新版本上被移除了。

  • 最大的问题 iOS 内存占用过高

flutter_boost iOS 端的实现方案,在实际项目上使用时,我们只能将每一个 Flutter 页面都套在一个原生的 FlutterViewController 中 ,这直接导致每打开一个 Flutter 页面的内存占用高出 10M 左右。

为了解决这些问题,我们开始了 架构 2.0 的建设。

架构 2.0 的建设

架构 2.0 主要是解决 页面间通信 1.0 和 页面栈管理 2.0 的解决方案存在的一些问题而演变出来的,同时对 页面模块化 做更细致的职能分解。

页面模块化 2.0

方案可以参考 ThrioModule,ThrioModule 的 API 也遵守多端一致性。

相比于 页面模块化 1.0,功能的变迁如下:

  • 模块挂载 1.0
  • 模块初始化 1.0
  • 模块异步初始化 1.0
  • 页面路由注册 2.0
  • 页面路由行为观察 2.0
  • 页面生命周期观察 2.0
  • 页面通知接收 2.0

以上功能均提供三端一致的 API 2.0

页面栈路由 2.0

我们开发了 thrio,主要是解决 页面间通信 1.0 和 页面栈管理 1.0 中存在的问题。

thrio 的页面栈结构

thrio 的原理上改善点是除了复用 FlutterEngine,还复用了原生的页面容器,页面栈结构如下:

thrio 的路由

thrio 提供了三端一致的路由 API

页面的 push

  • dart 端打开页面

dart
ThrioNavigator.push(url: ‘flutter1’);
// 传入参数
ThrioNavigator.push(url: ‘native1’, params: { ‘1’: {‘2’: ‘3’}});
// 是否动画,目前在内嵌的 dart 页面中动画无法取消,原生 iOS 页面有效果
ThrioNavigator.push(url: ‘native1’, animated:true);
// 接收锁打开页面的关闭回调
ThrioNavigator.push(
url: ‘biz2/flutter2’,
params: {‘1’: {‘2’: ‘3’}},
poppedResult: (params) => verbose(‘biz2/flutter2 popped: $params’),
);

  • iOS 端打开页面

objc
[ThrioNavigator pushUrl:@“flutter1”];
// 接收所打开页面的关闭回调
[ThrioNavigator pushUrl:@“biz2/flutter2” poppedResult:^(id _Nonnull params) {
ThrioLogV(@“biz2/flutter2 popped: %@”, params);
}];

  • Android 端打开页面

kotlin
ThrioNavigator.push(this, “biz1/flutter1”,
mapOf(“k1” to 1),
false,
poppedResult = {
Log.e(“Thrio”, “native1 popResult call params $it”)
}
)

页面的 pop

  • dart 端关闭顶层页面

dart
ThrioNavigator.pop(params: ‘popped flutter1’),

  • iOS 端关闭顶层页面

objc
[ThrioNavigator popParams:@{@“k1”: @3}];

  • Android 端关闭顶层页面

kotlin
ThrioNavigator.pop(this, params, animated)

页面的 popTo

  • dart 端关闭到页面

dart
ThrioNavigator.popTo(url: ‘flutter1’);

  • iOS 端关闭到页面

objc
[ThrioNavigator popToUrl:@“flutter1” animated:NO];

  • Android 端关闭到页面

kotlin
ThrioNavigator.popTo(context, url, index)

页面的 remove

  • dart 端关闭特定页面

dart
ThrioNavigator.remove(url: ‘flutter1’, animated: true);

  • iOS 端关闭特定页面

objc
[ThrioNavigator removeUrl:@“flutter1” animated:NO];

  • Android 端关闭特定页面

kotlin
ThrioNavigator.remove(context, url, index)

thrio 的页面通知

页面通知作为解决页面间通信的一个能力被引入 thrio,以一种非常轻量的方式解决了 topic_center 所要解决的问题,而且不需要管理 topic。

发送页面通知

  • dart 端给特定页面发通知

dart
ThrioNavigator.notify(url: ‘flutter1’, name: ‘reload’);

  • iOS 端给特定页面发通知

objc
[ThrioNavigator notifyUrl:@“flutter1” name:@“reload”];

  • Android 端给特定页面发通知

kotlin
ThrioNavigator.notify(url, index, params)

接收页面通知

  • dart 端接收页面通知

使用 NavigatorPageNotify 这个 Widget 来实现在任何地方接收当前页面收到的通知。

dart
NavigatorPageNotify(
name: ‘page1Notify’,
onPageNotify: (params) =>
verbose(‘flutter1 receive notify: $params’),
child: Xxxx());

  • iOS 端接收页面通知

UIViewController实现协议NavigatorPageNotifyProtocol,通过 onNotify 来接收页面通知:

objc

  • (void)onNotify:(NSString *)name params:(NSDictionary *)params {
    ThrioLogV(@“native1 onNotify: %@, %@”, name, params);
    }
  • Android 端接收页面通知

Activity实现协议OnNotifyListener,通过 onNotify 来接收页面通知:

kotlin
class Activity : AppCompatActivity(), OnNotifyListener {
override fun onNotify(name: String, params: Any?) {
}
}

因为 Android activity 在后台可能会被销毁,所以页面通知实现了一个懒响应的行为,只有当页面呈现之后才会收到该通知,这也符合页面需要刷新的场景。

架构 2.0 的优势

在我们的业务上存在很多模块,进去之后是,首页 -> 列表页 -> 详情页 -> 处理页 -> 结果页,大致会是连续打开 5 个 Flutter 页面的场景。

这里会对 架构 1.0 和 架构 2.0 我们所使用的解决方案做一些优劣对比,仅表示我们业务场景下的结果,不一样的场景不具备可参考性。

在此仅列出两个比较明显的改善措施,这些改善主要是原理层面的优势带来的,不代表 thrio 的实现比 flutter_boost 高明,另外数据仅供参考,只是为了说明原理带来的优势。

thrio 在 iOS 上的内存占用

同样连续打开 5 个页面的场景,boost 的方案会消耗 91.67M 内存,thrio 只消耗 42.76 内存,模拟器上跑出来的数据大致如下:

thrio 在 Android 上的页面打开速度

同样连续打开 5 个页面的场景,thrio 打开第一个页面跟 boost 耗时是一样的,因为都需要打开一个新的 Activity,之后 4 个页面 thrio 会直接打开 Flutter 页面,耗时会降下来,以下单位为 ms:

总结

总的来说,引入 Flutter 是一个很明智的选择,人效提升是非常明显的。如果你的 App 对包大小不敏感,那完全可以尝试在项目中引入 Flutter。

最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

以下是今天给大家分享的一些独家干货:

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
Android 开发人员,请集中精力,对基础和重要的事情做深度研究**。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

以下是今天给大家分享的一些独家干货:

[外链图片转存中…(img-995BuhwE-1715420412368)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值