上周四美团外卖技术团队开源了一个 Android Router 的框架: WMRouter,博客详细介绍了用法以及设计方案,还不熟悉的同学可以先去看一下。本篇博客将从代码的角度解析框架的设计与实现。
美团官方博客介绍说框架有两大功能:URI 分发、ServiceLoader。看似是两个独立的功能,经过翻源码查看,在底层实现上 URI 分发的部分功能是依赖于 ServiceLoader 实现的,所以在接下来的几个代码解析中,都会先讲到 ServiceLoader,然后再说 URI 分发。
注解器及 gradle 插件
ServiceLoader 注解
ServiceLoader 注解有两个:RouterService、RouterProvider。
代码语言:javascript复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RouterService { ... }
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterProvider { ... }
看注解声明中 @Retention,我们可以了解到:RouterService 为编译时注解,RouterProvider 为运行时注解。
我们先看 RouterService 在编译时会做什么操作呢?
代码语言:javascript复制// ServiceAnnotationProcessor 类中的部分方法
private HashMap<String, Entity> mEntityMap = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
if (env.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(env);
}
return true;
}
// 获取使用 RouterService 的类以及参数值保存到 mEntityMap 中
private void processAnnotations(RoundEnvironment env) {
for (Element element : env.getElementsAnnotatedWith(RouterService.class)) {
...
Symbol.ClassSymbol cls = (Symbol.ClassSymbol) element;
RouterService service = cls.getAnnotation(RouterService.class);
if (service == null) {
continue;
}
List<? extends TypeMirror> typeMirrors = getInterface(service);
String[] keys = service.key(); // 获取 key
String implementationName = getBinaryName(cls); // 获取类名
boolean singleton = service.singleton(); // 是否为单例
for (TypeMirror mirror : typeMirrors) {
String interfaceName = getClassName(mirror);
Entity entity = mEntityMap.get(interfaceName);
if (entity == null) {
entity = new Entity(interfaceName);
mEntityMap.put(interfaceName, entity);
}
if (keys.length > 0) {
for (String key : keys) {
if (key.contains(":")) {
String msg = String.format("%s: 注解%s的key参数不可包含冒号",
implementationName, RouterService.class.getName());
throw new RuntimeException(msg);
}
entity.put(key, implementationName, singleton);
}
} else {
entity.put(null, implementationName, singleton);
}
}
}
}
// 把 mEntityMap 中的值写入到 assets 文件中
private void generateConfigFiles() {
for (Map.Entry<String, Entity> entry : mEntityMap.entrySet()) {
String interfaceName = entry.getKey();
writeInterfaceServiceFile(interfaceName, entry.getValue().getContents());
}
}
这里通过 RouterService 注解获取到对应的类和注解信息,保存到了 assets 文件夹中,最后随 apk 一起打包,小伙伴们可以解压 apk 看一下,目录 assets/wm-router.services/ 中就会有以接口名称命名的多个文件,每个文件中包含了他们的实例以及其他注解信息。这里也就是解释了这个框架为什么可以支持组件化。
Router 注解
Router 注解有是三种:RouterPage、RouterRegex、RouterUri。
这三种注解都是编译时注解,他们做的工作是大致相同的,我们就用 RouterPage 来举例看一下他对应的注解器:PageAnnotationProcessor
代码语言:javascript复制PageAnnotationProcessor:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
if (annotations == null || annotations.isEmpty()) {
return false;
}
CodeBlock.Builder builder = CodeBlock.builder();
String hash = null;
for (Element element : env.getElementsAnnotatedWith(RouterPage.class)) {
if (!(element instanceof Symbol.ClassSymbol)) {
continue;
}
boolean isActivity = isActivity(element); // 判断使用注解的类是否为 Activity
boolean isHandler = isHandler(element); // 判断使用注解的类是否为 UriHandler
if (!isActivity && !isHandler) {
continue;
}
Symbol.ClassSymbol cls = (Symbol.ClassSymbol) element;
RouterPage page = cls.getAnnotation(RouterPage.class); // 通过类获取注解
if (page == null) {
continue;
}
if (hash == null) {
hash = hash(cls.className());
}
CodeBlock handler = buildHandler(isActivity, cls);
CodeBlock interceptors = buildInterceptors(getInterceptors(page));
// path, handler, interceptors
String[] pathList = page.path();
for (String path : pathList) { // 这里会自动生成 register urihandler 的代码,把每个注解的类注册进去
builder.addStatement("handler.register($S, $L$L)",
path,
handler,
interceptors);
}
}
writeHandlerInitClass(builder.build(), hash, Const.PAGE_CLASS,
Const.PAGE_ANNOTATION_HANDLER_CLASS, Const.PAGE_ANNOTATION_INIT_CLASS);
return true;
}
之后运行到了 BaseProcessor 类的 writeHandlerInitClass 函数,来看一下做了什么:
代码语言:javascript复制public void writeHandlerInitClass(CodeBlock code, String hash,
String genClassName, String handlerClassName, String interfaceName) {
try {
genClassName = Const.SPLITTER hash; // 拼接类名
// 构造方法
MethodSpec methodSpec = MethodSpec.methodBuilder(Const.INIT_METHOD)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(TypeName.get(typeMirror(handlerClassName)), "handler")
.addCode(code)
.build();
// 类型
TypeSpec typeSpec = TypeSpec.classBuilder(genClassName)
.addSuperinterface(TypeName.get(typeMirror(interfaceName)))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpec)
.build();
// 写了一个 java 类
JavaFile.builder(Const.GEN_PKG, typeSpec)
.build()
.writeTo(filer);
String fullImplName = Const.GEN_PKG Const.DOT genClassName;
String config = new ServiceImpl(null, fullImplName, false).toConfig();
// 放 assets 中写配置信息
writeInterfaceServiceFile(interfaceName, Collections.singletonList(config));
} catch (IOException e) {
e.printStackTrace();
}
}
这里我们会发现在编译过程中,URI 注解器会帮我们自动生成一些类的代码(实现对应的 AnnotationInit 接口,并自动注册 urihandler),然后会把自己的实现类生成到 assets 文件中,就像我们之前提到的 RouterService 一样。
ServiceLoader 原理
router 模块的代码中有一个关键类:ServiceLoader,这个类中包含了类的获取以及实例的创建。
代码语言:javascript复制// 从 assets 中根据接口名加载对应的实现类,并保存到缓存
private void loadData() {
InputStream is = null;
BufferedReader reader = null;
try {
try {
// 读取 assets 文件
is = Router.getRootHandler().getContext().getAssets().open(Const.ASSETS_PATH mInterfaceName);
} catch (FileNotFoundException e) {
Debugger.w("assets file for interface '%s' not found", mInterfaceName);
}
if (is == null) {
return;
}
reader = new BufferedReader(new InputStreamReader(is));
String ln;
while ((ln = reader.readLine()) != null) {
ServiceImpl impl = ServiceImpl.fromConfig(ln); // 根据保存的规则读取注解配置的信息
if (impl != null) {
ServiceImpl prev = mMap.put(impl.getKey(), impl);
String errorMsg = ServiceImpl.checkConflict(mInterfaceName, prev, impl);
if (errorMsg != null) {
Debugger.fatal(errorMsg);
}
}
}
}
}
// 通过类名和 IFactory 使用反射创建实例,并保存到缓存
private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
if (impl == null) {
return null;
}
Class<T> clazz = ClassPool.get(impl); // 从 ClassPool 获取 class,看是否有缓存
if (impl.isSingleton()) { // 实现类是否声明为单例,如果是单例则需要在 SingletonPool 中查找实例
try {
return SingletonPool.get(clazz, factory);
} catch (Exception e) {
Debugger.fatal(e);
}
} else {
try {
if (factory == null) {
factory = RouterComponents.getDefaultFactory();
}
T t = factory.create(clazz); // 创建实例
Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
return t;
} catch (Exception e) {
Debugger.fatal(e);
}
}
return null;
}
总结一下 ServiceLoader 的原理及特性:
- 通过接口名称读取之前注解生成的配置文件,得到相关的实现类,并缓存到 ClassPool,这里可在使用时加载
- 可通过无参、context、自己实现 IFactory、注解 RouterProvider 反射创建实例
- 如果实现类注解为单例,则会保存到 SingletonPool 中
- Provider 也会缓存到 ProviderPool 中
Router 核心层
Router 核心层包含4个基础结构:UriHandler、UriRequest、UriInterceptor、UriCallback
UriRequest
代码语言:javascript复制public class UriRequest {
private final Context mContext;
private Uri mUri;
private final HashMap<String, Object> mFields;
private String mSchemeHost = null;
...
}
UriRequest中包含Context、URI和Fields,其中Fields为HashMap<String, Object>,可以通过Key存放任意数据。简单起见,UriRequest类同时承担了Response的功能,跳转请求的结果,也会被保存到Fields中。 存放到Fields中的常见字段举例如下,也可以根据需要自定义,为了避免冲突,建议字段名用完整的包名开头。
- Intent的Extra参数,Bundle类型
- 用于startActivityForResult的RequestCode,int类型
- 用于overridePendingTransition方法的页面切换动画资源,int[]类型
- 本次跳转结果的监听器,OnCompleteListener类型 总结来说,UriRequest用于实现一次URI跳转中所有组件之间的通信功能。
UriInterceptor、UriCallback
代码语言:javascript复制public interface UriInterceptor {
/**
* 调用 {@link UriCallback#onNext()} 进行下一步,不拦截
* 调用 {@link UriCallback#onComplete(int)} 做拦截处理
*/
void intercept(@NonNull UriRequest request, @NonNull UriCallback callback);
}
public interface UriCallback extends UriResult {
/**
* 处理完成,继续后续流程。
*/
void onNext();
/**
* 处理完成,终止分发流程。
* @param resultCode 结果,可参考 {@link UriResult}
*/
void onComplete(int resultCode);
}
UriHandler
UriHandler用于处理URI跳转请求,可以嵌套从而逐层分发和处理请求。UriHandler是异步结构,接收到UriRequest后处理(例如跳转Activity等),如果处理完成,则调用callback.onComplete() 并传入ResultCode;如果没有处理,则调用 callback.onNext() 继续分发。
代码语言:javascript复制public abstract class UriHandler {
protected ChainedInterceptor mInterceptor;
/**
* 处理URI。通常不需要覆写本方法。
*
* @param request URI跳转请求
* @param callback 处理完成后的回调
*/
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (shouldHandle(request)) {
Debugger.i("%s: handle request %s", this, request);
if (mInterceptor != null) {
mInterceptor.intercept(request, new UriCallback() {
@Override
public void onNext() {
handleInternal(request, callback);
}
@Override
public void onComplete(int result) {
callback.onComplete(result);
}
});
} else {
handleInternal(request, callback);
}
} else {
Debugger.i("%s: ignore request %s", this, request);
callback.onNext();
}
}
/**
* 是否要处理给定的URI。在 {@link UriInterceptor} 之前调用。
*/
protected abstract boolean shouldHandle(@NonNull UriRequest request);
/**
* 处理URI。在 {@link UriInterceptor} 之后调用。
*/
protected abstract void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback);
}
UriHandler 设计的很好,感觉有些类似于 Android Touch 事件分发机制,通过 shouldHandle 判断该 UriHandler 是否要处理,如果返回 True,则会优先执行一次拦截器,之后在 handleInternal 中做出对应的处理。
mInterceptor 的类型为 ChainedInterceptor,其中包含一个 List 容器,保存了多个 nterceptor,这个下面会说到。
Chained 相关类
Chained 类:ChainedHandler、ChainedInterceptor,其中分别持有了多个 UriHandler、UriInterceptor。
- ChainedHandler 使用了自定义的 PriorityList (不过内部实现也是 LinkedList),里面做了一些对 UriHandler 优先级的判断,在 handleInternal 函数中对之前排序好的 mHandlers 执行 handle 方法
- UriInterceptor 使用 LinkedList,在 intercept 方法中,判断子拦截器是否需要拦截public class ChainedHandler extends UriHandler { private final PriorityList<UriHandler> mHandlers = new PriorityList<>(); // 按优先级从大到小排列 @Override protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) { next(mHandlers.iterator(), request, callback); } private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) { if (iterator.hasNext()) { UriHandler t = iterator.next(); t.handle(request, new UriCallback() { @Override public void onNext() { next(iterator, request, callback); // 递归遍历所有的 riHandler } @Override public void onComplete(int resultCode) { callback.onComplete(resultCode); } }); } else { callback.onNext(); } } } // ChainedInterceptor 的实现和 ChainedHandler 基本类似,这里就不粘代码了
RootUriHandler
作为入口 UriHandler,继承 ChainedHandler,内部实现了 startUri(UriRequest) 和一个全局的监听
代码语言:javascript复制public class RootUriHandler extends ChainedHandler {
private OnCompleteListener mGlobalOnCompleteListener; // 全局监听
public void startUri(@NonNull UriRequest request) {
// 前面有一系列判空操作
...
handle(request, new RootUriCallback(request)); // 执行 handle,并传入自定义的 RootUriCallback
}
// RootUriCallback 实现对部分回调的重处理,以及全局回调的监听
protected class RootUriCallback implements UriCallback {
@Override
public void onComplete(int resultCode) {
switch (resultCode) {
case CODE_REDIRECT:
// 重定向,重新跳转
Debugger.i("<--- redirect, result code = %s", resultCode);
startUri(mRequest);
break;
case CODE_SUCCESS:
// 跳转成功
mRequest.putField(UriRequest.FIELD_RESULT_CODE, resultCode);
onSuccess(mRequest);
Debugger.i("<--- success, result code = %s", resultCode);
break;
default:
// 跳转失败
mRequest.putField(UriRequest.FIELD_RESULT_CODE, resultCode);
onError(mRequest, resultCode);
Debugger.i("<--- error, result code = %s", resultCode);
break;
}
}
}
}
好,到此为止,从源码角度,我们会发现 URI 分发核心代码将会包含以下功能属性:
- 通过 UriRequest 发出请求,分发给 RootUriHandler,及其子 UriHandler
- ChainedHandler 可持有多个 UriHandler,用于处理URI跳转请求,可以嵌套从而逐层分发和处理请求,也同时具有优先级的功能
- UriHandler 持有 ChainedInterceptor,可对请求进行拦截处理,比如:添加请求参数、拦截处理、弹框、重定向等
- 大概执行过程为 UriRequest –> UriInterceptor –> RootUriHandler,也就是官方给出的设计思路图
Router 通用实现层
核心层接口默认实现
DefaultRootUriHandler 继承 RootUriHandler,对 RootUriHandler 做了进一步封装处理,并且默认包含了 PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler、StartUriHandler 四种 UriHandler 的支持
代码语言:javascript复制public class DefaultRootUriHandler extends RootUriHandler {
public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
...
// 处理RouterPage注解定义的内部页面跳转,如果注解没定义,直接结束分发
addChildHandler(mPageAnnotationHandler, 300);
// 处理RouterUri注解定义的URI跳转,如果注解没定义,继续分发到后面的Handler
addChildHandler(mUriAnnotationHandler, 200);
// 处理RouterRegex注解定义的正则匹配
addChildHandler(mRegexAnnotationHandler, 100);
// 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
addChildHandler(new StartUriHandler(), -100);
...
}
}
DefaultUriRequest 继承 UriRequest,增加了常用参数的辅助方法,方便使用。
DefaultAnnotationLoader 提供了加载加载注解配置的默认实现,通过 ServiceLoader 的方式。
代码语言:javascript复制public class DefaultAnnotationLoader implements AnnotationLoader {
public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();
@Override
public <T extends UriHandler> void load(T handler,
Class<? extends AnnotationInit<T>> initClass) {
List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);
for (AnnotationInit<T> service : services) {
service.init(handler);
}
}
}
四种 UriHandler
- PageAnnotationHandler 处理所有 wm_router://page/* 形式的URI跳转,根据path匹配由 RouterPage 注解配置的节点
- UriAnnotationHandler 根据URI的 scheme host,分发到对应的 PathHandler(如果有),之后 PathHandler 再根据path匹配 RouterUri 注解配置的节点
- RegexAnnotationHandler 根据优先级和正则匹配尝试将URI分发给 RouterRegex 配置的每个节点
- StartUriHandler 尝试直接使用 Android 原生的隐式跳转启动URI,用于处理其他类型的 URI,例如 tel:、 mailto:
activity 跳转
- ActivityClassNameHandler 通过 ClassName 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
- ActivityHandler 通过 Activity 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
- DefaultActivityLauncher 为 startActivity 的默认实现,其中判断了通过 Action 跳转还是使用普通的方式跳转 下面贴部分代码:
public class ActivityClassNameHandler extends AbsActivityHandler {
...
protected Intent createIntent(@NonNull UriRequest request) {
// 通过 ClassName 生成 Intent
return new Intent().setClassName(request.getContext(), mClassName);
}
}
public class ActivityHandler extends AbsActivityHandler {
...
protected Intent createIntent(@NonNull UriRequest request) {
// 通过 Activity 生成 Intent
return new Intent(request.getContext(), mClazz);
}
}
public class DefaultActivityLauncher implements ActivityLauncher {
public int startActivity(@NonNull UriRequest request, @NonNull Intent intent) {
...
return startIntent(request, intent, context, requestCode, false);
}
/**
* 启动Intent
*
* @param internal 是否启动App内页面
*/
protected int startIntent(@NonNull UriRequest request, @NonNull Intent intent,
Context context, Integer requestCode, boolean internal) {
if (!checkIntent(context, intent)) {
return UriResult.CODE_NOT_FOUND;
}
if (startActivityByAction(request, intent, internal) == UriResult.CODE_SUCCESS) {
return UriResult.CODE_SUCCESS;
}
return startActivityByDefault(request, context, intent, requestCode, internal);
}
// 通过 Action 跳转
protected int startActivityByAction(@NonNull UriRequest request,
@NonNull Intent intent, boolean internal) {
try {
final StartActivityAction action = request.getField(
StartActivityAction.class, FIELD_START_ACTIVITY_ACTION);
boolean result = action != null && action.startActivity(request, intent);
} ...
}
// 通过默认方式跳转
protected int startActivityByDefault(UriRequest request, @NonNull Context context,
@NonNull Intent intent, Integer requestCode, boolean internal) {
try {
Bundle options = request.getField(Bundle.class, FIELD_START_ACTIVITY_OPTIONS);
if (requestCode != null && context instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) context, intent, requestCode,
options);
} else {
ActivityCompat.startActivity(context, intent, options);
}
doAnimation(request);
} ...
}
}
Router 框架梳理
按照 demo 给定的流程,不考虑自定义 UriHandler 的情况下,执行流程大致如下:
- 由 DefaultRootUriHandler 按照优先级分发到 PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler,若都没有匹配到,则尝试 StartUriHandler
- PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler 三种 UriHandler 通过 ServiceLoader 加载用户注解的类作为子 UriHandler
- 判断子 UriHandler 是否为 Activity 和 ClassName,如果是则走默认的 activity 跳转,即:DefaultActivityLauncher,不是则继续向下分发
好了,就是这些了,欢迎各位读者提问及意见 ~
作 者:ChanghuiN
原文链接:https://cloud.tencent.com/developer/article/1333363
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。