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

栏目: 编程工具 · 发布时间: 5年前

内容简介:上周四美团外卖技术团队开源了一个 Android Router 的框架:美团官方博客介绍说框架有两大功能:URI 分发、ServiceLoader。看似是两个独立的功能,经过翻源码查看,在底层实现上 URI 分发的部分功能是依赖于 ServiceLoader 实现的,所以在接下来的几个代码解析中,都会先讲到 ServiceLoader,然后再说 URI 分发。ServiceLoader 注解有两个:RouterService、RouterProvider。

上周四美团外卖技术团队开源了一个 Android Router 的框架: WMRouter ,博客详细介绍了用法以及设计方案,还不熟悉的同学可以先去看一下。本篇博客将从代码的角度解析框架的设计与实现。

美团官方博客介绍说框架有两大功能:URI 分发、ServiceLoader。看似是两个独立的功能,经过翻源码查看,在底层实现上 URI 分发的部分功能是依赖于 ServiceLoader 实现的,所以在接下来的几个代码解析中,都会先讲到 ServiceLoader,然后再说 URI 分发。

注解器及 gradle 插件

ServiceLoader 注解

ServiceLoader 注解有两个:RouterService、RouterProvider。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RouterService { ... }

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterProvider { ... }

看注解声明中 @Retention,我们可以了解到:RouterService 为编译时注解,RouterProvider 为运行时注解。

我们先看 RouterService 在编译时会做什么操作呢?

// 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

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 函数,来看一下做了什么:

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,这个类中包含了类的获取以及实例的创建。

// 从 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

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

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() 继续分发。

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) 和一个全局的监听

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,也就是官方给出的设计思路图

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

Router 通用实现层

核心层接口默认实现

DefaultRootUriHandler 继承 RootUriHandler,对 RootUriHandler 做了进一步封装处理,并且默认包含了 PageAnnotationHandler、UriAnnotationHandler、RegexAnnotationHandler、StartUriHandler 四种 UriHandler 的支持

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 的方式。

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:

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

activity 跳转

  1. ActivityClassNameHandler 通过 ClassName 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
  2. ActivityHandler 通过 Activity 生成 Intent 交给 DefaultActivityLauncher 执行具体跳转任务
  3. 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 的情况下,执行流程大致如下:

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

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

写给大家看的Web设计书

写给大家看的Web设计书

Robin Williams、John Tollett / 苏金国、刘亮 / 人民邮电出版社 / 201005 / 69.00元

在这个网络时代,Web设计几乎已经成为每个人生活的必备技能。如果你想自力更生创建一个网站,或者认为自己的网站在设计上还不尽如人意,希望它看上去更具创意和专业性,那么本书正是为你准备的! 作者Robin和John先采用通俗易懂的方式将有关基础知识娓娓道来,比如Internet、搜索信息、构建简单网页等,然后为我们奉上了精妙的技巧、技术和设计示例来启发大家的设计灵感,比如Web基本设计原则、实现......一起来看看 《写给大家看的Web设计书》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具