前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >美团外卖开源路由框架 WMRouter 源码分析

美团外卖开源路由框架 WMRouter 源码分析

作者头像
haifeiWu
发布2018-09-11 10:26:48
2K0
发布2018-09-11 10:26:48
举报
文章被收录于专栏:haifeiWu与他朋友们的专栏

上周四美团外卖技术团队开源了一个 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

  1. PageAnnotationHandler 处理所有 wm_router://page/* 形式的URI跳转,根据path匹配由 RouterPage 注解配置的节点
  2. UriAnnotationHandler 根据URI的 scheme + host,分发到对应的 PathHandler(如果有),之后 PathHandler 再根据path匹配 RouterUri 注解配置的节点
  3. RegexAnnotationHandler 根据优先级和正则匹配尝试将URI分发给 RouterRegex 配置的每个节点
  4. StartUriHandler 尝试直接使用 Android 原生的隐式跳转启动URI,用于处理其他类型的 URI,例如 tel:、 mailto:

activity 跳转

  1. ActivityClassNameHandler 通过 ClassName 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
  2. ActivityHandler 通过 Activity 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
  3. DefaultActivityLauncher 为 startActivity 的默认实现,其中判断了通过 Action 跳转还是使用普通的方式跳转 下面贴部分代码:
代码语言:javascript
复制
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 的情况下,执行流程大致如下:

  1. 由 DefaultRootUriHandler 按照优先级分发到 PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler,若都没有匹配到,则尝试 StartUriHandler
  2. PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler 三种 UriHandler 通过 ServiceLoader 加载用户注解的类作为子 UriHandler
  3. 判断子 UriHandler 是否为 Activity 和 ClassName,如果是则走默认的 activity 跳转,即:DefaultActivityLauncher,不是则继续向下分发

好了,就是这些了,欢迎各位读者提问及意见 ~

作 者:ChanghuiN

原文链接:https://cloud.tencent.com/developer/article/1333363

版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注解器及 gradle 插件
    • ServiceLoader 注解
      • Router 注解
      • ServiceLoader 原理
      • Router 核心层
        • UriRequest
          • UriInterceptor、UriCallback
            • UriHandler
              • Chained 相关类
                • RootUriHandler
                • Router 通用实现层
                  • 核心层接口默认实现
                    • 四种 UriHandler
                      • activity 跳转
                      • Router 框架梳理
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档