Flutter混合开发中的Flutter栈管理与goBack返回键方案解析(YCBlogs)
教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载导读在混合开发中原生 Activity 作为容器承载 Flutter 页面其关系类似 Android 中 WebView 与 url一个 Flutter 页面对应一个 route。随之而来的问题是当用户在 Flutter 页面内继续跳转多个 Flutter 页面后点击系统返回键是直接退出承载的 Activity还是先回到上一个 Flutter 页面本文基于 Flutter栈的管理.md 的完整思路结合仓库中 系统FlutterChannel.md、Channel详细介绍.md 与 混合路由跳转操作.md 的源码级佐证讲解 Flutter 栈的管理机制并给出可落地的 goBack类 WebView 回退栈完整实现方案。一、问题场景原生容器里的 Flutter 页面与返回键在原生页面Activity中引入 Flutter 页面与 Android 开发中使用 WebView 加载 url 高度类似容器 Activity 相当于 WebViewroute 相当于 url该结论在 Flutter容器开发实践.md 的Android引入flutter本质一节中有明确表述。在这种结构下自然会产生一个疑问在 Flutter 页面中继续跳转到其他 Flutter 页面后点击手机的返回键是否会直接返回到上一个 Activity而不是返回上一个 Flutter 页面问题的本质是原生侧有一个 Activity 栈Flutter 侧有一个 Navigator 路由栈两个栈是独立存在的。如果不对返回键做任何干预返回行为很可能直接作用于外层容器导致 Flutter 内部的多级页面被一笔带过用户直接回到了上一个 Activity这显然不符合 WebView 那种逐级回退的交互预期。因此需要实现的能力是如果 Flutter 侧还有上一页栈内还有路由返回键先回到上一个 Flutter 页面如果 Flutter 侧已经是第一个页面栈内没有可 pop 的路由再直接返回到上一个 Activity。即做到类似 WebView 回退栈的功能。二、Flutter 栈是如何管理的2.1 Navigator 与路由栈Flutter 端对页面栈的管理核心是Navigator。它内部维护了一个Route对象的栈结构Navigator.push(context, route)将新路由压入栈顶入栈新路由见 Element树原理分析.mdNavigator.pop(context)弹出栈顶路由回到上一页命名路由可配合Navigator.pushNamed(context, new_page)使用见 Flutter跳转问题.md。Navigator的静态方法与实例方法一一对应例如Navigator.push(context, route)等价于Navigator.of(context).push(route)。2.2 Android 返回键驱动 Flutter 弹栈的底层链路在混合开发中Android 侧的系统返回键并非直接结束 Activity而是通过一个系统级 Channel 通知 Dart 层。仓库 系统FlutterChannel.md 给出了完整链路**NavigationChannelChannel 名flutter/navigation**负责与 Dart 进行路由通讯底层实现是 MethodChannel发送数据格式为 JSON核心方法如下public class NavigationChannel { NonNull public final MethodChannel channel; public NavigationChannel(NonNull DartExecutor dartExecutor) { // 注意NavigationChannel 使用的编解码器是 JSONMethodCodec this.channel new MethodChannel(dartExecutor, flutter/navigation, JSONMethodCodec.INSTANCE); } // 初始化路由数据给 Dart public void setInitialRoute(NonNull String initialRoute) { ... } // 加载一个 dart 页面 public void pushRoute(NonNull String route) { ... } // 弹出一个 dart 页面 public void popRoute() { Log.v(TAG, Sending message to pop route.); channel.invokeMethod(popRoute, null); } ... }返回键的触发链路由源码可以清晰看到来源系统FlutterChannel.md// FlutterActivity 中 Override public void onBackPressed() { if (!eventDelegate.onBackPressed()) { super.onBackPressed(); } } // FlutterActivityDelegate 中 Override public boolean onBackPressed() { if (flutterView ! null) { flutterView.popRoute(); return true; } return false; } // FlutterView 类中 public void popRoute() { navigationChannel.popRoute(); }即按返回键 → FlutterActivity.onBackPressed() → FlutterView.popRoute() → navigationChannel.popRoute() → 通过 flutter/navigation Channel 向 Dart 发送 popRoute 消息。Dart 侧对应在SystemChannels.navigation与widgets/binding.dart中处理该消息。原始笔记对该行为的描述是当 Activity 单击 back 键就通知 Flutter 层要退出 App——可见返回键的最终处理权实际在 Flutter 侧的路由栈手中这也正是我们可以在 Flutter 端做栈管理的底层依据。2.3 混合开发中的栈形态理解了双栈结构后再看混合开发中常见的栈形态源自 混合路由跳转操作.md单一 Native 栈Native → Native → Native → ...传统业务形态单一 Flutter 栈Flutter → Flutter → Flutter → ...纯 Flutter 架构形态简单混合栈Flutter 集中式单混合栈Activity → Flutter → Flutter → Activity → ActivityFlutter 页面集中出现、无交叉同一时间 App 只存在一个 Flutter 容器复杂混合栈完全混合栈Activity → Flutter → Flutter → Activity → Flutter → ActivityFlutter 与 Native 页面随机交叉App 中可能存在多个 Flutter 容器属于业界难点。本文讨论的 goBack 方案最典型的适用场景是简单混合栈一个 Flutter 容器内存在多个 Flutter 页面返回时需要先走完 Flutter 内部栈再退出容器回到 Native 栈。三、goBack 功能的设计思路原文档给出的解决思路非常清晰分两步在 Flutter 端利用Navigator.canPop(context)判断是否可以返回上一页如果可以栈内还有路由调用Navigator.of(context).pop()返回上一个 Flutter 页面反之说明当前显示的 Flutter 页面已经是第一个页面了直接返回上一个 Activity 即可。返回上一个 Activity 需要借助 MethodChannel因为关闭 Activity / 结束容器属于原生能力Flutter 端无法直接操作必须通过 Platform Channel 通知原生。整个判断流程可以用伪代码概括用户点击返回 ├─ Navigator.canPop(context) true → Navigator.of(context).pop() // 返回上一个 Flutter 页面 └─ Navigator.canPop(context) false → MethodChannel.invokeMethod(goBack, ...) └─ Native 收到后 finish() 结束容器 Activity四、goBack 功能的完整实现4.1 Flutter 端实现首先定义 MethodChannelChannel 名称必须与原生端保持一致参考 Channel详细介绍.md 的写法import package:flutter/services.dart; // Channel 名称要和 Android 端定义的相同 static const MethodChannel method MethodChannel(com.ycbjie.android/method);核心返回逻辑Futurevoid _goBack() async { // 1. 判断 Flutter 栈中是否还有上一页 if (Navigator.canPop(context)) { // 2. 还有上一页返回上一个 Flutter 页面 Navigator.of(context).pop(); } else { // 3. 已经是第一个 Flutter 页面通知原生关闭容器 Activity try { final String result await method.invokeMethod( goBack, {message: 我从Flutter页面回来了}); print(goBack result: $result); } on PlatformException catch (e) { print(goBack error: ${e.message}); } } }4.2 Android 端实现原生端创建 MethodChannel 并注册回调需要拿到binaryMessenger在 FlutterEngine 容器场景下通过flutterEngine.getDartExecutor().getBinaryMessenger()获取参见 Flutter容器开发实践.md 与 Channel详细介绍.mdprivate void createChannel() { nativeChannel new MethodChannel(binaryMessenger, METHOD_CHANNEL, StandardMethodCodec.INSTANCE); nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { Override public void onMethodCall(NonNull MethodCall methodCall, NonNull MethodChannel.Result result) { if (goBack.equals(methodCall.method)) { // 接收来自 flutter 的指令关闭承载 Flutter 的容器 Activity finish(); } } }); }核心要点源自 Channel详细介绍.mdmethodCall.method用来区分不同方法名执行不同的业务逻辑methodCall.argument(key)获取对应 key 的参数值methodCall.hasArgument(key)判断是否存在某个 keyresult.success(object)把处理结果返回给事件发送方Flutter 端。4.3 携带返回数据goBackWithResult 版本实际业务中关闭当前 Flutter 页面时往往需要给上一个原生页面回传数据类似 Android 的startActivityForResultonActivityResult()语义。仓库 Channel详细介绍.md 第 15 节给出了完整实现Flutter 端点击返回并携带数据RaisedButton( textColor: Colors.black, child: Text(返回上一界面并携带数据), onPressed: () async { MapString, dynamic map {message: 我从Flutter页面回来了}; String result await method.invokeMethod(goBackWithResult, map); }, )Android 端接收并返回结果nativeChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() { Override public void onMethodCall(NonNull MethodCall methodCall, NonNull MethodChannel.Result result) { if (goBackWithResult.equals(methodCall.method)) { // 返回上一页携带数据 Intent backIntent new Intent(); backIntent.putExtra(message, (String) methodCall.argument(message)); setResult(RESULT_OK, backIntent); finish(); } } });注意该场景下finish()后页面即将销毁无需也不应再调用result.success()回传。4.4 反向场景原生页面返回 Flutter 页面并回调数据与 goBack 对称的另一个场景是Flutter 页面先跳转原生页面原生页面返回时把数据带回 Flutter。仓库 Channel详细介绍.md 第 15.2 节给出了完整链路Flutter 端通过method.invokeMethod(doubi, ...)通知原生原生onMethodCall中执行startActivityForResult(intent, RESULT_OK2)打开新原生页面新原生页面setResult(RESULT_OK2, intent)后finish()承载容器在onActivityResult()中取到数据后调用nativeChannel.invokeMethod(onActivityResult, result, ...)反向调用 Flutter 方法Flutter 端setMethodCallHandler中按methodCall.method匹配onActivityResult分支处理数据。// 原生承载容器的 onActivityResult Override protected void onActivityResult(int requestCode, int resultCode, Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (data ! null resultCode RESULT_OK2) { String message data.getStringExtra(message); MapString, Object result new HashMap(); result.put(message, message); // 调用 Flutter 端定义的方法 nativeChannel.invokeMethod(onActivityResult, result, new MethodChannel.Result() { Override public void success(Nullable Object result) { tvContent.setText(测试内容2 result); } Override public void error(String errorCode, Nullable String errorMessage, Nullable Object errorDetails) { } Override public void notImplemented() { } }); } }Futuredynamic handler(MethodCall call) async { switch (call.method) { case onActivityResult: // 获取原生页面传递的参数 print(call.arguments[message]); return 你好这个是从flutter传递过来的数据; } } flutterChannel.setMethodCallHandler(handler);将 goBack 与 onActivityResult 两条链路结合即可完整覆盖Flutter 栈内回退 跨栈Native/Flutter返回与传参的全部混合开发场景。五、实现中的注意事项与踩坑5.1 Channel 名称与编解码器必须一致MethodChannel 的 Channel 名称两端必须完全相同否则消息无法匹配。此外编解码器也必须一致。仓库 混合路由跳转操作.md 记录了一个真实踩坑案例系统NavigationChannel构造时使用JSONMethodCodec.INSTANCE而 Dart 侧默认创建方式const MethodChannel(flutter/navigation)使用的是StandardMethodCodec()两者编解码器不一致导致 Flutter 通过flutter/navigation调用原生方法时解析失败、跳转无效。修正方式是在 Flutter 端显式指定相同的编解码器static const MethodChannel method1 MethodChannel(flutter/navigation, JSONMethodCodec());5.2 Android 端 Channel 调用必须在主线程Channel 通信虽然是异步的但 Android 侧发送消息有线程约束。仓库 Channel详细介绍.md 第 12 节实测在子线程中调用nativeChannel.invokeMethod(...)会直接崩溃java.lang.RuntimeException: Methods marked with UiThread must be executed on the main thread. Current thread: Thread-2574 at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(FlutterJNI.java:992) at io.flutter.embedding.engine.FlutterJNI.dispatchPlatformMessage(FlutterJNI.java:736) at io.flutter.embedding.engine.dart.DartMessenger.send(DartMessenger.java:72) ...因此在原生端触发invokeMethod如 onActivityResult 回调里通知 Flutter时要确保处于主线程。5.3 大数据传递的耗时权衡返回场景往往要携带参数Channel 对数据量有实际性能约束。仓库 Channel详细介绍.md 第 14 节给出了实测数据耗时单位为 ms具体数值与机型环境相关仅作量级参考传递方向数据量数据构造耗时 total1Channel 通信耗时 total2Flutter → NA1M12194Flutter → NA4M37659Flutter → NA10M823252NA → Flutter1M14918NA → Flutter4M353460NA → Flutter10M8310231从实测可以看出1M 以内的数据传递很快日常业务几十 k基本无感4M 以上耗时明显增加10M 会有明显卡顿。goBack 回传数据时应避免一次性传递超大 payload必要时应拆包或改用文件路径等间接方式。5.4 canPop 判断的边界Navigator.canPop(context)判断的是Navigator 栈顶路由之下是否还有可返回的路由。在使用Navigator.push/pushNamed正常压栈的场景下它能准确反映 Flutter 内部栈的深度。需要留意如果容器 Activity 在 Flutter 栈为空时就被系统返回键直接结束依赖 系统FlutterChannel.md 所述的 popRoute 链路则 Flutter 端可能拿不到最后一个页面的返回事件此时应在原生容器层onBackPressed/ 系统返回键配合拦截把事件先交给 Flutter 端 goBack 逻辑处理多 Flutter 容器复杂混合栈场景下context对应的 Navigator 可能与用户感知的页面栈不一致需要结合容器维度做栈管理而不仅是单容器内的 canPop。六、进阶返回键的主动拦截除了在返回事件触发后做 canPop 判断还可以在 Flutter 端用WillPopScope主动拦截返回键。仓库 Widget归类汇总.md 将WillPopScope归类为导航返回拦截组件典型用法是return WillPopScope( onWillPop: () async { if (Navigator.canPop(context)) { Navigator.of(context).pop(); return false; // 已消费返回事件不再向上传递 } // 通知原生 finish() await method.invokeMethod(goBack); return false; }, child: childWidget, );将onWillPop与 goBack 逻辑结合可以保证无论用户点系统返回键还是应用内返回按钮都走同一套先 Flutter 栈、再原生栈的回退策略实现真正意义上的 WebView 式回退栈体验。总结Flutter 混合开发中的栈管理核心在于理解双栈结构原生 Activity 栈与 Flutter Navigator 路由栈相互独立。系统返回键通过flutter/navigation这个 NavigationChannel 以popRoute消息驱动 Dart 侧弹栈这为 Flutter 端接管返回行为提供了底层基础。实战中用Navigator.canPop(context)判断 Flutter 栈深度可 pop 则Navigator.of(context).pop()回退 Flutter 页面不可 pop 则通过 MethodChannel 通知原生finish()结束容器 Activity即可实现类 WebView 的 goBack 回退栈功能配合goBackWithResult与onActivityResult链路还能在跨栈返回时无缝传递数据。实现时务必注意 Channel 名称与编解码器一致性、主线程约束以及大数据传递的耗时问题。进一步阅读Flutter栈的管理.md本文主题的原始笔记系统FlutterChannel.mdNavigationChannel 与返回键触发链路源码Channel详细介绍.mdMethodChannel/EventChannel/BasicMessageChannel 完整使用与踩坑混合路由跳转操作.md混合路由架构模式与混合栈形态Flutter容器开发实践.mdFlutterView/FlutterFragment/FlutterActivity 容器承载方式赞分享教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载相关推荐Kimi Code kap-server 能力演进解析从会话标题生成到配置诊断推送的 v2 服务端实践Kimi Code kap server 能力演进解析从会话标题生成到配置诊断推送的 v2 服务端实践 本文以 moonshot ai/kap server教程技术博客文档HowToGraphQL Ember 教程详解用 ember-apollo-client 发送 GraphQL Mutation 创建 LinkHowToGraphQL Ember 教程详解用 ember apollo client 发送 GraphQL Mutation 创建 Link 本文基于 H教程技术博客文档YOLO_tensorflow常见问题解决训练过程中的10个典型错误与修复方法YOLO_tensorflow常见问题解决训练过程中的10个典型错误与修复方法 YOLO_tensorflow是一个基于TensorFlow实现的目标检测框架上一篇SwiftUIRefresh API参考.pullToRefresh()修饰符参数详解、版本演进与使用注意事项下一篇Awesome Meta Tags Manifest Properties打造终极PWA体验的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考