|
| 1 | +## 在现有App中接入flutter,如何改造路由层代价最小 |
| 2 | + |
| 3 | +我能想到的 |
| 4 | + |
| 5 | +* 与原生模块隔离 |
| 6 | +* 与原生公用一套页面跳转逻辑 |
| 7 | +* flutter如果接入了多个模块的多功能,消息规范可能还不一样,如何兼容。 |
| 8 | + |
| 9 | + |
| 10 | +### 从页面跳转说起 |
| 11 | + |
| 12 | +webView,reactNative,flutter页面与原生交互无非就这两种。 |
| 13 | + |
| 14 | + * flutter打开原生页面 |
| 15 | + * 原生页面跳转flutter页面 |
| 16 | + |
| 17 | +### 现有条件 |
| 18 | + |
| 19 | +* BCRouter路由以及拦截器 |
| 20 | +* flutter端 使用的了flutterBoost组件 |
| 21 | + |
| 22 | +#### BCRouter路由以及拦截器 |
| 23 | + |
| 24 | + |
| 25 | +`BCRouterService`是以URL-Block的方式实现的路由,将URL与页面建立联系,页面跳转的时候通过url找到对应的映射关系。 |
| 26 | + |
| 27 | +主要方法如下: |
| 28 | + |
| 29 | +``` |
| 30 | +//以scheme区分模块或者不同的app |
| 31 | ++ (instancetype)routesForScheme:(NSString *)scheme; |
| 32 | +//以serviceName【URL】与Block绑定,进行页面跳转 |
| 33 | +- (void)registerServiceName:(NSString*)serviceName |
| 34 | + impClass:(Class)impClass |
| 35 | + handler:(nullable BCRouterCustomHandler)handler; |
| 36 | + //URL查询对应的Block,进行页面的跳转 |
| 37 | + + (BOOL)openURL:(NSURL *)URL |
| 38 | + withParams:(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)params; |
| 39 | +
|
| 40 | +``` |
| 41 | + |
| 42 | + |
| 43 | + |
| 44 | +``` |
| 45 | + //URL查询对应的Block,进行页面的跳转 |
| 46 | ++ (BOOL)openURL:(NSURL *)URL |
| 47 | + withParams:(nullable NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)params |
| 48 | + andThen:(nullable void(^)(NSString *pathComponentKey, id obj, id returnValue))then completion:(void(^)(NSError *error, id response))completion{ |
| 49 | + if (![self canOpenURL:URL]) { |
| 50 | + return NO; |
| 51 | + } |
| 52 | + NSString *scheme = URL.scheme; |
| 53 | + BCRouterService *router = [self routesForScheme:scheme]; |
| 54 | + NSString *serviceImpl = [[router servicesDict] objectForKey:[serviceName lowercaseString]]; |
| 55 | + BCServicePathComponent *component = [[router servicesDict] objectForKey:[serviceName lowercaseString]]; |
| 56 | + //找到对应的实现Class |
| 57 | + Class mClass = NSClassFromString(component.impClass); |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +拦截器:通过URL查找的时候,首先询问拦截器是否能够处理 URL,如果能处理就将URL交给拦截器处理。 |
| 64 | +比如登录拦截器,将需要登录的页面对应的URL都丢到loginInterceptor中,页面跳转时,url会在登录拦截器中响应。 |
| 65 | + |
| 66 | + |
| 67 | + |
| 68 | +拦截器部分方法 |
| 69 | + |
| 70 | +``` |
| 71 | +@protocol BCRouterInterceptorProtocol <NSObject> |
| 72 | +
|
| 73 | +//拦截器优先级 越大越优先 |
| 74 | +- (NSInteger)priority; |
| 75 | +//查询是否已经注入到拦截器 |
| 76 | +- (BOOL)canOpenURL:(NSURL *)URL; |
| 77 | +//依赖注入 |
| 78 | +- (void)registerServiceName:(NSString*)serviceName |
| 79 | + impClass:(Class)impClass; |
| 80 | +
|
| 81 | +//处理响应【返回NO代表不需要响应,返回YES说需要进行其他处理】 |
| 82 | +//返回YES之后,会执行内部处理逻辑 |
| 83 | +- (BOOL)dealWithURL:(NSURL*)URL parms:(NSDictionary<NSString *, id>*)parms |
| 84 | + andThen:(void(^)(void))then; |
| 85 | +- (BOOL)dealWithURL:(NSURL*)URL andThen:(void(^)(void))then; |
| 86 | +``` |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | + 拦截器处理URL,不能处理,再交给路由去处理。 |
| 91 | + |
| 92 | +``` |
| 93 | + BOOL canDealURL = BCGlobal_RouteInterceptorListDealURL(URL,allParams); |
| 94 | + if (canDealURL) { |
| 95 | + return YES; |
| 96 | + } |
| 97 | +``` |
| 98 | + |
| 99 | + |
| 100 | +``` |
| 101 | ++ (void)registerInterceptor:(id<BCRouterInterceptorProtocol>)interceptor{ |
| 102 | + BCGlobal_RouteInterceptorListAdd(interceptor); |
| 103 | +} |
| 104 | +static BOOL BCGlobal_RouteInterceptorListDealURL(NSURL *URL,NSDictionary*parms) { |
| 105 | + BCGlobal_RouteInterceptorListInit(); |
| 106 | + BOOL canDeal = NO; |
| 107 | + dispatch_semaphore_wait(BCGlobal_InterCeptorlistLock, DISPATCH_TIME_FOREVER); |
| 108 | + for (id<BCRouterInterceptorProtocol>object in BCGlobal_routeInterceptorList) { |
| 109 | + canDeal = [object dealWithURL:URL parms:parms andThen:^{}]; |
| 110 | + if (canDeal) break; |
| 111 | + } |
| 112 | + dispatch_semaphore_signal(BCGlobal_InterCeptorlistLock); |
| 113 | + return canDeal; |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +#### flutterBoost在iOS端的相关实现 |
| 118 | + |
| 119 | + |
| 120 | +`FlutterBoostDelegate`协议实现,flutter端打开、关闭原生页面,会执行到下面的具体实现。 |
| 121 | + |
| 122 | + |
| 123 | +``` |
| 124 | +///如果框架发现您输入的路由表在flutter里面注册的路由表中找不到,那么就会调用此方法来push一个纯原生页面 |
| 125 | +- (void) pushNativeRoute:(NSString *) pageName arguments:(NSDictionary *) arguments; |
| 126 | +
|
| 127 | +///当框架的withContainer为true的时候,会调用此方法来做原生的push |
| 128 | +- (void) pushFlutterRoute:(FlutterBoostRouteOptions *)options; |
| 129 | +
|
| 130 | +///当pop调用涉及到原生容器的时候,此方法将会被调用 |
| 131 | +- (void) popRoute:(FlutterBoostRouteOptions *)options; |
| 132 | +
|
| 133 | +``` |
| 134 | + |
| 135 | + |
| 136 | +原生打开flutter页面,我们需要借助 ` [[FlutterBoost instance]open:options]`这样的操作。 |
| 137 | + |
| 138 | +``` |
| 139 | + FlutterBoostRouteOptions *options = [[FlutterBoostRouteOptions alloc] init]; |
| 140 | + options.pageName = @"flutter/testPage2"; |
| 141 | + [[FlutterBoost instance]open:options]; |
| 142 | +``` |
| 143 | + |
| 144 | + |
| 145 | +### 思考问题 |
| 146 | + |
| 147 | + |
| 148 | +如果遇到如下问题,我们该怎么办?? |
| 149 | + |
| 150 | +* app已经唤醒,停留在具体的某一页面 推送通知打开app内的任意页面 |
| 151 | +* 某一个具体页面,点击cell时候跳转的链接由后台配置 |
| 152 | + |
| 153 | + |
| 154 | +容易想到的解决方法是这样 |
| 155 | + |
| 156 | +``` |
| 157 | +if (type == 原生) { |
| 158 | + if (id == "页面1"){ |
| 159 | + vc = ... |
| 160 | + }else if (id == "页面2"){ |
| 161 | + vc = ... |
| 162 | + }else if (id == "页面3"){ |
| 163 | + vc = ... |
| 164 | + }else if (id == "..."){ |
| 165 | + vc = ... |
| 166 | + } |
| 167 | + //做页面跳转 |
| 168 | + [self.currentVC.navigationController pushViewController: vc animated:YES]; |
| 169 | +}else{ |
| 170 | + flutter页面跳转 |
| 171 | + FlutterBoostRouteOptions *options = [[FlutterBoostRouteOptions alloc] init]; |
| 172 | + options.pageName = @"flutter/testPage2"; |
| 173 | + [[FlutterBoost instance]open:options]; |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +想想这些代码散在app的各个角落,新增或修改的时候成本只会越来越大。<br> |
| 178 | +但是别忘了我们的路由层可以帮我们解决原生那一堆判断操作。改成下面的代码如下面。 |
| 179 | + |
| 180 | +``` |
| 181 | +if (type == 原生) { |
| 182 | + NSURL *URL = ... |
| 183 | + [BCRouterService openURL: URL] |
| 184 | +}else{ |
| 185 | +// flutter页面跳转 |
| 186 | + FlutterBoostRouteOptions *options = [[FlutterBoostRouteOptions alloc] init]; |
| 187 | + options.pageName = @"flutter/testPage2"; |
| 188 | + [[FlutterBoost instance]open:options]; |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +还是有问题,只要遇到原生与flutter都要这样写还是很麻烦,还没法扩展。<br> |
| 193 | +如何可以写成下面这样的,既做到多端统一,也解决了硬编码的问题。 |
| 194 | + |
| 195 | +``` |
| 196 | + NSURL *URL = ... |
| 197 | + [BCRouterService openURL: URL] |
| 198 | +``` |
| 199 | + |
| 200 | +这样写,就代表着我们需要在router层里面这样写. |
| 201 | + |
| 202 | +``` |
| 203 | +if (URL == 原生) { |
| 204 | + //处理原生部分 |
| 205 | +}else{ |
| 206 | +// flutter页面跳转 |
| 207 | + FlutterBoostRouteOptions *options = [[FlutterBoostRouteOptions alloc] init]; |
| 208 | + options.pageName = @"URL"; |
| 209 | + [[FlutterBoost instance]open:options]; |
| 210 | +} |
| 211 | +``` |
| 212 | +上面的写法,已经解决了我们90%的问题,代码只会在路由层,维护路由层就行。<br> |
| 213 | +但是我们要思考这样写是不是:用到路由组件的应用都要导入flutter组件,显然这违反了一个公共组件上下依赖关系【怎么形容,就是不应该这样写】。那我们又不能省略这段代码,有没有方法把这段代码隔离出来,有的,我们的拦截器。 |
| 214 | + |
| 215 | + |
| 216 | + |
| 217 | +#### flutter拦截器 |
| 218 | + |
| 219 | +我刚开始写的时候想到这个组件肯定要满足下面的条件: |
| 220 | + |
| 221 | + 1. 将flutter业务与原生业务隔离,可插拔 |
| 222 | + 2. flutter业务可继续扩展,flutter中的多个模块可细分,只需要在这个全局的拦截器中做文章 |
| 223 | + |
| 224 | + |
| 225 | +flutter牵涉到原生的逻辑该怎么办??,比如登录逻辑,我要写在那个模块合适? |
| 226 | + |
| 227 | +flutter模块的全部功能原则上都是独立,登录必须写在这个拦截器里面。不能与原生的拦截器搞在一块。当然写在一块功能肯定能实现,但是后续的维护成本可是不小,必须在开始的时候就把规则定好。 |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | + |
0 commit comments