背景
路由库Walle的存在很好的解除了业务层和底层库的直接耦合,但是随着各个底层库的接入,这时发现一些问题。比如:接入定位库后,实现异步回调功能变得复杂;之后在设计上,因为封装返回数据Bean和异步逻辑处理,导致多产生一个库WalleExtLib。而我们对这个库的定义自始是不明确的,最后影响了项目中的整个依赖关系结构。
路由职能
- 解耦
在两个库不依赖的前提下实现两者之间的页面和功能调用,各个业务库调用底层库逻辑时非直接依赖关系。典型的例子:58ClientHybridLib中存在很多触发登陆模块的代码,但是看依赖关系,这个库是没有依赖WubaLoginLib的。这就是Walle路由的作用。WubaClientHybridLib = [ switchs['58ClientHybridLib'] ? "com.wuba.wuxian.lib:58ClientHybridLib:" + "$WubaClientHybridLibVersion" : findProject(':58ClientHybridLib'), rootProject.ext.WubaAppDependenciesLib, rootProject.ext.WubaWebBusinessLib, rootProject.ext.WubaBasicBusinessLib, rootProject.ext.WubaRNLib, rootProject.ext.WubaRNExt, rootProject.ext.LoginSDK, rootProject.ext.WubaIMLib, rootProject.ext.WubaLocationLib, rootProject.ext.WubaVideoLib, rootProject.ext.WPush, rootProject.ext.WubaMultiDexLib, rootProject.ext.WubaShareLib, rootProject.ext.AndroidPlatformService, "com.wuba.wmda:wmda:$WmdaVersion", 'com.facebook.fbui.textlayoutbuilder:textlayoutbuilder:1.0.0', 'com.squareup.okhttp3:okhttp-urlconnection:3.11.0', 'javax.inject:javax.inject:1', rootProject.ext.RecyclerView, rootProject.ext.DeviceID, 'com.getui:gisdk:3.1.6.0', 'com.alibaba:arouter-api:1.4.0', 'com.wuba.wuxian.sdk:ttsdk:2.8.0.3-58', 'com.bun.miitmdid:miitmdid:1.0.10', //MSA SDK 补充设备标识符 rootProject.ext.WubaNetMonitorSDK, ]
- 页面跳转
对接了现有的跳转中心模块,此模块支持按跳转协议直接跳转到落地页。WubaWalleLib = [ switchs['WubaWalleLib'] ? "com.wuba.wuxian.lib:WubaWalleLib:" + "$WubaWalleLibVersion" : findProject(':WubaWalleLib'), rootProject.ext.JumpCenterSDK, rootProject.ext.WubaLogLib ]
- 模块调用
我们的工程中有很多中间库,业务线和中间件实现平行开发,互不依赖,主要是因为Walle的作用。hybrid和login是完全两条并行的业务,它们在业务上是强依赖的,但是在代码层没有产生依赖。业务线和底层库也应该是平行概念,但是目前的实现上没有完全解耦合,hybrid强依赖location;hybrid虽然没有依赖login,然而直接依赖了passport,这是不合理的。
Walle缺陷
-
异步调用实现逻辑复杂
以定位模块为例,我们知道定位功能基本都是异步处理的,而且持续输出位置信息,通常需要注册观察者并触发定位模块实现定位全流程。具体生命周期如下:
目前Walle解决异步调用是在为服务层提供了对称的注册和反注册方法,同时在WalleExtLib中创建对应的LocationReceiver去伸入LocationHandle里监听数据变化从而实现异步回调的。反注册的话,Walle中缓存了这些观察者,在不需要时业务层调用Walle的反注册方法解除监听。
-
依赖问题
直接表现形式就是WalleExtLib其实是多余设计的产物。整个Walle的核心功能在WalleLib中,它基本承担了解耦的单一职责,这个时候功能还比较单纯;但是因为业务上需要异步操作,Walle需要支持异步数据回调,随后其引入了观察者对象和注册、反注册以及通知机制。Walle是业务和底层库的中间件,而观察者对象是Walle和业务层的过渡产物,由此这个无处安放的对象出现,导致一个新的库随即产生——WalleExtLib。
WalleExtLib产生后,整个Walle的层级发生了变化,原来从WalleLib第一入口变成了WalleExtLib,中心发生了偏移,由此工程中的整个结构发生了微妙变化。
-
方法的入口参数设置过于灵活,导致API设计聚合力差
方法参数的设计是一个对象,对象中的变量作为参数设计可配性很强,这样直接导致了向外提供的API参数上很松散。passport目前也是这样的设计,所以每次有API变更需要修改参数的地方遍布很多库和很多地方。
//im Request request = Request.obtain() .setPath("im/startChatDetail") .addQuery("protocol", content); Walle.route(context, request); //location Request request = Request.obtain() .setPath("location/getLocationCityIsAbroad"); Response data = Walle.route(request);
-
避讳的设计
- 通知
- 注册和反注册实现(因为有更好的机制可以代替,比如RxJava)
优化目标
- 统一Walle的调用方法,仅保留一个创建接口的方法。
ILocationService iLocationService = Walle.create(ILocationService.class); Response response = iLocationService.getLocationCityIsAbroad();
- 通过接口定义底层库方法入口,暴露给业务的是接口而不是普通类和松散方法。方法聚合,而非松散。
public interface ILocationService { @Action(uri = "location/getLocationCityIsAbroad") Response getLocationCityIsAbroad(); }
- 优化异步调用实现,通过RxJava实现异步调用更加简洁,废除注册、反注册以及通知。
public interface ILocationService { @Action(uri = "location/observeLocation") Observable<Response> observeLocation(); }
引入RxJava直接给业务层返回Observable对象,从而取消了注册和反注册以及通知机制,简化了整个异步调用逻辑。
- 废除WalleExtLib库,还原依赖关系,重心回到Walle本身,依赖结构更清晰。
方案设计
- 动态代理
1)利用反射机制在运行时创建代理类,通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例。
2)针对不同的代理类,传入相应的代理程序控制器InvocationHandler。
动态代理实现步骤:
- 通过实现InvocationHandler接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
public static <T> T create(Class<T> service, final Context context) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Action action = method.getAnnotation(Action.class); Request request = Request.obtain().setPath(action.uri()); Annotation[][] annotations = method.getParameterAnnotations(); if (annotations != null && args != null && args.length > 0) { ... request.addQuery(map); } return Walle.route(context, request, ...); } }); }
利用动态的代理的目的:1)利用Proxy的能力构造出一个接口实例;2)通过InvocationHandler获取参数配置。
- 注解,实现参数配置。
1)通过定义方法注解,使接口方法和底层库提供的API通过uri参数进行配对。
2)通过定义参数注解,明确参数的key,实现参数与参数值可传递。
```
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
String uri();
String authority() default AUTH_COM;
}
```
```
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
String value();
}
```
```
public interface ILocationService {
@Action(uri = "location/getLocationCityIsAbroad")
Response getLocationCityIsAbroad();
@Action(uri = "location/requestLocation")
Response requestLocation(@Query("location.force_locate") boolean isForceLocate);
@Action(uri = "location/resumeLocation")
Response resumeLocation();
@Action(uri = "location/stopLocation")
Response stopLocation();
@Action(uri = "location/observeLocation")
Observable<Response> observeLocation();
}
```
-
反射
1)Proxy动态代理中其实已经用到了反射去实例化接口。 2)Walle框架中利用反射也是一样的道理。通过反射,可以动态创建对应的业务底层库的Handle类;比如,通过定位接口方法上的uri前缀(uri = “location/getLocationCityIsAbroad”),就能明确此时调用Walle的业务方是location,那么walle反射要实现的handle类便是LocationHandle。 -
Rxjava,替代注册和反注册实现
Rxjava提供了Observable对象,它把注册和反注册过程封装在了对象内,对外提供单纯的回吐数据、注册和反注册方法,这样Walle这一层则不再关注注册和通知的问题。Walle职责得到了还原。 Handle处理回调结果时,只需要根据接口方法定义的返回结果类型,向上返回不同的结果对象。如果是同步,则直接返回Response数据对象;如果是异步,则返回Observable包装的Response对象。 ``` method.getReturnType() == Observable.class ? ASYNC : SYNC ```
整体架构图如下:
优化后的效果
- 调用方式
public interface ILocationService { @Action(uri = "location/getLocationCityIsAbroad") Response getLocationCityIsAbroad(); @Action(uri = "location/observeLocation") Observable<Response> observeLocation(); }
public static boolean getLocationCityIsAbroad() { ILocationService iLocationService = Walle.create(ILocationService.class); Response response = iLocationService.getLocationCityIsAbroad(); return response.getBoolean("result"); }
public static Observable<ILocation.WubaLocationData> getPosition(boolean isUpdate) { ILocationService iLocationService = Walle.create(ILocationService.class); return iLocationService.observeLocation().map(new Func1<Response, ILocation.WubaLocationData>() { @Override public ILocation.WubaLocationData call(Response response) { return (ILocation.WubaLocationData) response.getParcelable("location.result"); } }); }
- 依赖关系图对比