摘要在 Kotlin Flutter 混合开发的应用中我们设计了一套优雅、解耦、可扩展的跨平台路由系统。本文从架构优势、核心能力出发深入解析面向接口设计、配置驱动、页面栈管理等关键技术实现。 背景与痛点混合架构的挑战在现代移动应用开发中Native Flutter的混合架构越来越流行Native 负责高性能、系统级功能Flutter 负责快速迭代、跨平台 UI两者需要无缝跳转和通信但在实际项目中我们遇到了以下痛点 四大痛点1. 跳转方式不统一Native → NativeTheRouter.build().navigation() / ARouter…… Native → FlutterstartActivity(intent) Flutter → NativeEventHub.post(event) / GoRouter Flutter → FlutterNavigator.push()……研发人员需要记住多种 API容易出错代码风格不一致。2. 路由管理混乱路由路径分散在各个模块硬编码在业务代码中难以维护和重构3. 页面栈不可控Native 和 Flutter 各自维护页面栈无法统一管理返回逻辑复杂场景如 A→B→CC 直接返回 A难以实现4. 框架耦合严重更换路由框架成本高测试困难✨ 架构优势与核心能力 五大架构优势1. 统一 API降低认知负担无论 Native 还是 Flutter都使用相同的跳转方式valurlMyRouter.buildUrl(page/product/detail,paramsmapOf(idto123),launchModeLaunchMode.STANDARD)MyRouter.push(url)研发人员只需记住一个 API无需关心底层实现细节。2. 完全解耦框架无关SDK 通过接口定义导航器不依赖具体路由框架Native 侧可使用 TheRouter、ARouter 等Flutter 侧可自定义实现更换框架只需修改导航器实现业务代码零改动3. 配置驱动动态管理路由表通过page_config.json集中管理支持动态下发或通过自定义注解方式{routes:[{page:/home/main,type:NATIVE},{page:/product/detail,type:FLUTTER}]}新增路由只需修改配置文件支持远程下发配置避免硬编码易于维护4. 统一页面栈跨平台管理Native 和 Flutter 的页面跳转统一记录到 PageStackManager实时查询当前页面、上一页支持 push、pop、replace、clearTo 操作线程安全CopyOnWriteArrayList5. 灵活扩展面向未来支持 5 种启动模式STANDARD、REPLACE、CLEAR_TOP、SINGLE_TOP、NEW_TASK预留 MethodChannel 通信接口可扩展拦截器、监控埋点等功能 核心能力清单能力说明应用场景统一跳转Native/Flutter 使用相同 API所有跳转场景路由分发自动判断页面类型并分发跨平台跳转页面栈管理统一记录和管理页面历史返回逻辑、页面统计LaunchMode5种启动模式支持复杂跳转场景参数传递JSON 格式承载任意参数数据传递错误处理完善的异常捕获和日志问题排查配置管理JSON 文件集中管理路由表路由维护日志追踪详细的跳转链路日志调试监控️ 架构设计分层架构图┌─────────────────────────────────────────┐ │ 业务层Business Layer │ │ MainActivity, SplashActivity, etc. │ └──────────────┬──────────────────────────┘ │ MyRouter.push(url) ┌──────────────▼──────────────────────────┐ │ SDK 核心层SDK Core Layer │ │ ┌─────────────────────────────────┐ │ │ │ MyRouter统一入口 │ │ │ ├─────────────────────────────────┤ │ │ │ RouteRegistry路由注册中心 │ │ │ │ RouteDispatcher路由分发器 │ │ │ │ PageStackManager页面栈管理 │ │ │ │ RouteConfigLoader配置加载器 │ │ │ └─────────────────────────────────┘ │ └──────────────┬──────────────────────────┘ │ 依赖注入 ┌──────────────▼──────────────────────────┐ │ 导航器实现层Navigator Layer │ │ ┌──────────────┐ ┌────────────────┐ │ │ │ INativeNav │ │ IFlutterNav │ │ │ │ (TheNativeRouter) (TheFlutterRouter) │ │ │ └──────────────┘ └────────────────┘ │ └─────────────────────────────────────────┘三层架构说明第一层业务层Business Layer职责调用 MyRouter API 进行页面跳转特点不关心底层实现只关注业务逻辑示例MainActivity、SplashActivity、Flutter Pages第二层SDK 核心层SDK Core Layer职责路由解析、分发、页面栈管理特点框架无关面向接口编程核心组件MyRouter统一入口负责 URL 构建/解析、路由分发RouteRegistry路由注册中心维护路由表Mappage, RouteTypePageStackManager页面栈管理器记录所有跳转历史RouteConfigLoader配置加载器从 JSON 文件加载路由表第三层导航器实现层Navigator Layer职责具体执行页面跳转特点宿主 App 自定义实现SDK 通过接口调用接口定义interfaceINativeNavigator{funnavigate(context:Context,path:String,params:MapString,String):NavigationResult}interfaceIFlutterNavigator{funnavigate(context:Context,path:String,params:MapString,String,launchMode:LaunchMode):NavigationResult}核心组件职责MyRouter - 统一入口URL 构建和解析路由类型判断分发到对应导航器记录页面栈RouteRegistry - 路由注册中心维护路由表Mappage, RouteType提供精确匹配查询SDK 不硬编码任何业务路径PageStackManager - 页面栈管理器记录每次路由跳转支持 push、pop、replace、clearTo线程安全CopyOnWriteArrayListRouteConfigLoader - 配置加载器从 assets 读取 page_config.json解析并验证路由配置初始化时一次性加载 关键实现1. 统一跳转URL Scheme标准格式myrouter://myrouter?page{pageId}info{URLEncoded_JSON}launchMode{MODE}参数说明page页面路径标识必填必须与配置完全一致infoURL Encode 后的 JSON 字符串可选承载所有业务参数launchMode启动模式可选默认 STANDARD重要规范✅ pageId 必须精确匹配配置✅ 参数统一放 info不得拼接到 page 路径中❌ 禁止动态路径如/product/detail/:id2. URL 构建与解析构建 URLfunbuildUrl(page:String,params:MapString,Any,launchMode:LaunchMode):String{// 手动构建 JSON避免 kotlinx.serialization 对 Any 类型的限制valjsonStringbuildJsonString(params)returnUri.Builder().scheme(myrouter).authority(myrouter).appendQueryParameter(page,page).appendQueryParameter(info,Uri.encode(jsonString)).appendQueryParameter(launchMode,launchMode.name).build().toString()}解析 URLfunparseUrl(url:String):RouteRequest?{valuriUri.parse(url)valpageuri.getQueryParameter(page)valinfoEncodeduri.getQueryParameter(info)?:{}valinfoJsonUri.decode(infoEncoded)valparamsparseJsonParams(infoJson)// 简单 JSON 解析vallaunchModeStruri.getQueryParameter(launchMode)?:STANDARDvallaunchModeLaunchMode.fromValue(launchModeStr)returnRouteRequest(page,params,launchMode)}关键点手动构建/解析 JSON避免序列化框架对Any类型的限制支持 String、Number、Boolean 类型URL Encode/Decode 保证特殊字符安全传输3. 路由分发流程funpush(url:String):Boolean{// 1. 解析 URLvalrequestparseUrl(url)?:returnfalse// 2. 查询路由类型valtargetTypeRouteRegistry.getRouteType(request.page)if(targetTypeRouteType.UNKNOWN){Log.e(TAG,Unknown route:${request.page})returnfalse}// 3. 分发到对应导航器returnwhen(targetType){RouteType.NATIVE-navigateToNative(context,request)RouteType.FLUTTER-navigateToFlutter(context,request)RouteType.UNKNOWN-false}}流程图用户调用 MyRouter.push(url) ↓ 解析 URL → 获取 page、params、launchMode ↓ 查询 RouteRegistry → 获取 RouteType ↓ 根据 RouteType 分发 ├─ NATIVE → INativeNavigator.navigate() └─ FLUTTER → IFlutterNavigator.navigate() ↓ 记录页面栈 → PageStackManager.push() ↓ 返回结果4. 页面栈管理数据结构dataclassPageRecord(valid:String,valpath:String,valtype:RouteType,valparams:MapString,String,vallaunchMode:LaunchMode,valtimestamp:Long)核心操作objectPageStackManager{privatevalpageStackCopyOnWriteArrayListPageRecord()// 压栈funpush(record:PageRecord){if(pageStack.sizeMAX_SIZE){pageStack.removeAt(0)// 移除最旧的记录}pageStack.add(record)}// 弹栈funpop():PageRecord?{returnif(pageStack.isNotEmpty()){pageStack.removeAt(pageStack.lastIndex)}elsenull}// 替换栈顶funreplace(record:PageRecord){if(pageStack.isNotEmpty()){pageStack.removeAt(pageStack.lastIndex)}push(record)}// 清空到指定页面funclearTo(path:String):Boolean{valindexpageStack.indexOfLast{it.pathpath}if(index!-1){while(pageStack.sizeindex1){pageStack.removeAt(pageStack.lastIndex)}returntrue}returnfalse}}线程安全使用CopyOnWriteArrayList保证读写安全适合读多写少的场景5. LaunchMode 实现模式Native 实现Flutter 实现STANDARD默认 IntentnavigationChannel.pushRoute()REPLACE无特殊处理popRoute() pushRoute()CLEAR_TOPFLAG_ACTIVITY_CLEAR_TOP清空栈后 pushSINGLE_TOPFLAG_ACTIVITY_SINGLE_TOP检查栈顶复用NEW_TASKFLAG_ACTIVITY_NEW_TASK新任务栈REPLACE 模式示例// Flutter 侧when(launchMode){LaunchMode.REPLACE-{flutterEngine?.navigationChannel?.popRoute()// 先弹出当前页flutterEngine?.navigationChannel?.pushRoute(route)// 再压入新页}else-{flutterEngine?.navigationChannel?.pushRoute(route)}}6. 导航器实现示例Native和Flutter分别实现INativeNavigator接口根据不同技术栈策略进行具体跳转实现。Native侧导航策略实现TheRouterNativeNavigatorclassTheRouterNativeNavigator:INativeNavigator{overridefunnavigate(context:Context,path:String,params:MapString,String):NavigationResult{returntry{valbuilderTheRouter.build(path)params.forEach{(key,value)-builder.withString(key,value)}builder.navigation(context)NavigationResult.Success}catch(e:Exception){NavigationResult.Failed(e.message?:Unknown error)}}}Flutter导航策略实现TheFlutterNavigatorclassTheFlutterNavigator:IFlutterNavigator{overridefunnavigate(context:Context,path:String,params:MapString,String,launchMode:LaunchMode):NavigationResult{returntry{valintentIntent(context,MallFlutterActivity::class.java)intent.putExtra(route,path)intent.putExtra(params,HashMap(params))intent.putExtra(launchMode,launchMode.name)// ⚠️ 非 Activity Context 需添加 NEW_TASK 标志if(context!isActivity){intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)}context.startActivity(intent)NavigationResult.Success}catch(e:Exception){NavigationResult.Failed(e.message?:Unknown error)}}}关键点面向接口编程SDK 不关心具体实现处理非 Activity Context 的边界情况统一的 NavigationResult 返回类型 效果对比改造前// Native → Flutter方式 1valintentMallFlutterActivity.buildIntent(this,/product/detail)startActivity(intent)// Native → Flutter方式 2EventHub.post(AppEvent.NavigateToFlutter(/product/detail))// Native → NativeTheRouter.build(/login/main).navigation()// Flutter → Nativechannel.invokeMethod(navigateToNative,mapOf(pathto/home/main))❌问题API 不统一研发记忆成本高改造后// 所有跳转统一使用 MyRoutervalurlMyRouter.buildUrl(page/product/detail,paramsmapOf(idto123),launchModeLaunchMode.STANDARD)MyRouter.push(url)✅优势统一 API降低团队学习成本自动处理路由类型判断完整的日志追踪页面栈自动记录 总结架构设计的核心价值统一性一套 API 覆盖所有跳转场景解耦性面向接口设计框架无关可维护性配置驱动集中管理可扩展性预留扩展点面向未来经验教训避免硬编码最初设计了前缀匹配导致歧义改为精确匹配 参数分离更清晰线程安全很重要RouteRegistry 使用 ConcurrentHashMapPageStackManager 使用 CopyOnWriteArrayList日志是调试的利器详细的日志帮助快速定位问题统一的标记便于过滤渐进式迁移保留原有 API向后兼容逐步替换降低风险路由表的管理可以考虑动态下发或自定义注解生成如期望热更新则选择动态化路由表方案如期望低侵入式则选择注解形式自动收集路由组件源码稍后上传或 wx搜索TheWorldOne快速查看互动话题你在混合架构中是如何管理路由的欢迎在评论区分享你的经验和困惑