美团开源路由框架(WMRouter)学习——源码篇

作者: 文艺的程序狗 | 来源:发表于2018-10-19 14:50 被阅读16次

    前言

    源码的解读是痛苦的,这里我打算带着几个问题从各个方面去解读源码 36164aab1652529611d3e899b586b97f145c70f7

    结构介绍

    如下图是源码的结构图,源码相关的库有三个java库comiler,interfaces,plugin,一个android的lib router,简单介绍一下各个库的作用

    • compiler:根据javax.annotation.processing.Processor提供处理注解的功能来处理interfaces定义的注解,解析生成相应的源码文件,引入需要在app的build.gradle dependencies 添加annotationProcessor project(path: ':compiler')
    • interfaces:定义了五种注解,分别是RouterPager、RouterRegex、RouterUri、RouterProvider、RouterService、另外ServiceImpl用来存储Service的实现类
    • plugin:自定义一个名为WMRouter 的gradle插件,将注解生成器生成的初始化类汇总到ServiceLoaderInit,运行时直接调用ServiceLoaderInit。引用的时候需要在项目根目录build.gradle dependencies添加classpath "com.sankuai.waimai.router:plugin:$VERSION_NAME",然后在app里面的build.gradle引入该plugin apply plugin: 'WMRouter
    • router:核心库


      源码结构

    如何识别并处理注解

    java注解是在5.0开始提供的支持,可以参考文章。开发者可以使用java提供的一些内置的注解,也可以自定义注解,自定义注解的处理需要继承javax.annotation.processing.AbstractProcessor,BaseProcessor继承自该类,对其进行了一次封装。我们以对RouterUri注解识别为例来讲解
    打开compiler库的UriAnnotationProcessor类,可以看到类被注解了AutoService,点击跳转到源码,发现是一个引入的第三方库com.google.auto.service:auto-service:1.0-rc2,这个库很简单就三个类,主要的作用是注解 processor 类,并对其生成 META-INF 的配置信息 。关键的处理代码在process方法

     @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(RouterUri.class)) {
                if (!(element instanceof Symbol.ClassSymbol)) {
                    continue;
                }
                boolean isActivity = isActivity(element);
                boolean isHandler = isHandler(element);
                if (!isActivity && !isHandler) {
                    continue;
                }
    
                Symbol.ClassSymbol cls = (Symbol.ClassSymbol) element;
                RouterUri uri = cls.getAnnotation(RouterUri.class);
                if (uri == null) {
                    continue;
                }
    
                if (hash == null) {
                    hash = hash(cls.className());
                }
    
                CodeBlock handler = buildHandler(isActivity, cls);
                CodeBlock interceptors = buildInterceptors(getInterceptors(uri));
    
                // scheme, host, path, handler, exported, interceptors
                String[] pathList = uri.path();
                for (String path : pathList) {
                    builder.addStatement("handler.register($S, $S, $S, $L, $L$L)",
                            uri.scheme(),
                            uri.host(),
                            path,
                            handler,
                            uri.exported(),
                            interceptors);
                }
            }
            buildHandlerInitClass(builder.build(), "UriAnnotationInit" + Const.SPLITTER + hash,
                    Const.URI_ANNOTATION_HANDLER_CLASS, Const.URI_ANNOTATION_INIT_CLASS);
            return true;
        }
    

    这里引入了另外一个库com.squareup:javapoet:1.7.0,这个库的主要作用就是帮助我们通过类调用的形式来生成代码。
    在process生成了被RouterUri注解的代码,最后调用buildHandlerInitClass方法写入到com.sankuai.waimai.router.generated目录下。
    至此生成了代码,形如

    public class UriAnnotationInit_72565413b8384a4bebb02d352762d60d implements IUriAnnotationInit {
      public void init(UriAnnotationHandler handler) {
        handler.register("", "", "/show_toast_handler", new ShowToastHandler(), false);
        handler.register("", "", "/advanced_demo", "com.sankuai.waimai.router.demo.advanced.AdvancedDemoActivity", false);
        handler.register("", "", "/nearby_shop_with_location", "com.sankuai.waimai.router.demo.advanced.location.NearbyShopActivity", false, new LocationInterceptor());
        handler.register("", "", "/home_ab_test", new HomeABTestHandler(), false);
        handler.register("", "", "/browser", new BrowserSchemeHandler(), false);
       ...
      }
    }
    
    生成的目录如下

    其中被UriService注解的生成在service目录下。RouterUri、RouterPage、RouterRegex生成规则相似
    UriService注解生成的规则和其他的不同,生成的代码如下

    public class ServiceInit_b57118238b4f9112ddd862e55789c834 {
      public static void init() {
        ServiceLoader.put(Context.class, "/application", DemoApplication.class, true);
        ServiceLoader.put(ILocationService.class, "/singleton", FakeLocationService.class, true);
        ServiceLoader.put(Func0.class, "/method/get_version_code", GetVersionCodeMethod.class, true);
       ...
      }
    }
    

    接下来就是如何调用生成的注解类呢,这里也分成了两类
    调用RouterService注解的类比较麻烦,需要首先用plugin库的自定义gradle插件循环读取com.sankuai.waimai.router.generated.service下面类文件在com.sankuai.waimai.router.generated下生成ServiceLoaderInit类,如下

    WMRouterTransform.class
     private void generateServiceInitClass(String directory, Set<String> classes) {
    
            if (classes.isEmpty()) {
                WMRouterLogger.info(GENERATE_INIT + "skipped, no service found");
                return;
            }
    
            try {
                WMRouterLogger.info(GENERATE_INIT + "start...");
                long ms = System.currentTimeMillis();
    
                ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
                ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, writer) {
                };
                String className = Const.SERVICE_LOADER_INIT.replace('.', '/');
                cv.visit(50, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
    
                MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                        Const.INIT_METHOD, "()V", null, null);
    
                mv.visitCode();
    
                for (String clazz : classes) {
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, clazz.replace('.', '/'),
                            "init",
                            "()V",
                            false);
                }
                mv.visitMaxs(0, 0);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitEnd();
                cv.visitEnd();
    
                File dest = new File(directory, className + SdkConstants.DOT_CLASS);
                dest.getParentFile().mkdirs();
                new FileOutputStream(dest).write(writer.toByteArray());
    
                WMRouterLogger.info(GENERATE_INIT + "cost %s ms", System.currentTimeMillis() - ms);
    
            } catch (IOException e) {
                WMRouterLogger.fatal(e);
            }
        }
    

    生成类文件代码如下

    public class ServiceLoaderInit {
        public static void init() {
            ServiceInit_aea7f96d0419b507d9b0ef471913b2f5.init();
            ServiceInit_f3649d9f5ff15a62b844e64ca8434259.init();
            ServiceInit_eb71854fbd69455ef4e0aa026c2e9881.init();
            ServiceInit_b57118238b4f9112ddd862e55789c834.init();
            ServiceInit_e694d982fb5d7a3a8c6b7085829e74a6.init();
            ServiceInit_ee5f6404731417fe1433da40fd3c9708.init();
            ServiceInit_9482ef47a8cf887ff1dc4bf705d5fc0a.init();
            ServiceInit_36ed390bf4b81a8381d45028b37cc645.init();
        }
    }
    

    然后在ServiceLoader的初始化利用反射调用ServiceLoaderInit类,然后Router调用ServiceLoader的lazyInit方法,最后在Application调用Router.lazyInit();完成将注解信息识别并且读取。

    ServiceLoader.class
    
     private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {
            @Override
            protected void doInit() {
                try {
                    // 反射调用Init类,避免引用的类过多,导致main dex capacity exceeded问题
                    Class.forName(Const.SERVICE_LOADER_INIT)
                            .getMethod(Const.INIT_METHOD)
                            .invoke(null);
                    Debugger.i("[ServiceLoader] init class invoked");
                } catch (Exception e) {
                    Debugger.fatal(e);
                }
            }
        };
    
    Router.class
     public static void lazyInit() {
            ServiceLoader.lazyInit();
            getRootHandler().lazyInit();
        }
    
    DemoApplication.class
      // 懒加载后台初始化(可选)
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    Router.lazyInit();
                    return null;
                }
            }.execute();
    

    另外一种,RouterUri、RouterPager、RouterRegex调用,我们以RouterUri举例

    DefaultAnnotationLoader.class 
     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);
            }
        }
    
    RouterComponents.class
        public static <T extends UriHandler> void loadAnnotation(T handler, Class<? extends AnnotationInit<T>> initClass) {
            sAnnotationLoader.load(handler, initClass);
        }
    
    UriAnnotationHandler.class
      protected void initAnnotationConfig() {
            RouterComponents.loadAnnotation(this, IUriAnnotationInit.class);
        }
    
      public void lazyInit() {
            mInitHelper.lazyInit();
        }
    
    DefaultRootUriHandler.class
      public void lazyInit() {
            mPageAnnotationHandler.lazyInit();
            mUriAnnotationHandler.lazyInit();
            mRegexAnnotationHandler.lazyInit();
        }
    
    Router.class
      public static void lazyInit() {
            ServiceLoader.lazyInit();
            getRootHandler().lazyInit();
        }
    

    UriHandler解读

    与注解连接的UriHandler

    先看一下UriHandler继承结构(通过快捷键 Control + H )


    红线圈起来的对应四个配合注解实现代码的注入,具体相关的方法是register,这个在前面解释注解说过(这里以UriAnnotationHandler举例)。

    UriAnnotationHandler.class
    
    public void register(String scheme, String host, String path,
                             Object handler, boolean exported, UriInterceptor... interceptors) {
            // 没配的scheme和host使用默认值
            if (TextUtils.isEmpty(scheme)) {
                scheme = mDefaultScheme;
            }
            if (TextUtils.isEmpty(host)) {
                host = mDefaultHost;
            }
            String schemeHost = RouterUtils.schemeHost(scheme, host);
            PathHandler pathHandler = mMap.get(schemeHost);
            if (pathHandler == null) {
                pathHandler = createPathHandler();
                mMap.put(schemeHost, pathHandler);
            }
            pathHandler.register(path, handler, exported, interceptors);
        }
    

    然后Application 通过DefaultRootUriHandler初始化来加载注解类放入对应的集合里

    DefaultRootUriHandler.class
    
    public DefaultRootUriHandler(Context context,
                                     @Nullable String defaultScheme, @Nullable String defaultHost) {
            super(context);
            mPageAnnotationHandler = createPageAnnotationHandler();
            mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);
            mRegexAnnotationHandler = createRegexAnnotationHandler();
    
            // 按优先级排序,数字越大越先执行
    
            // 处理RouterPage注解定义的内部页面跳转,如果注解没定义,直接结束分发
            addChildHandler(mPageAnnotationHandler, 300);
            // 处理RouterUri注解定义的URI跳转,如果注解没定义,继续分发到后面的Handler
            addChildHandler(mUriAnnotationHandler, 200);
            // 处理RouterRegex注解定义的正则匹配
            addChildHandler(mRegexAnnotationHandler, 100);
            // 添加其他用户自定义Handler...
    
            // 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
            addChildHandler(new StartUriHandler(), -100);
            // 全局OnCompleteListener,用于输出跳转失败提示信息
            setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);
        }
    

    那么,发起一个请求,如何知道是由哪个UriHandler处理呢,在UriRequest 请求里有Uri字段,就是根据该字段来识别的,具体描述如下。

    • UriAnnotationHandler
      根据scheme+host找出PathHandler,PathHandler都是存放拥有共同scheme+host,path不同的UriHandler,然后通过Path来获取指定已经注入的UriHandler
    UriAnnotationHandler.class
    
       private final Map<String, PathHandler> mMap = new HashMap<>();
    
     /**
         * 通过scheme+host找对应的PathHandler,找到了才会处理
         */
        private PathHandler getChild(@NonNull UriRequest request) {
            return mMap.get(request.schemeHost());
        }
    
    PathHandler.class
    
     private UriHandler getChild(@NonNull UriRequest request) {
            String path = request.getUri().getPath();
            if (TextUtils.isEmpty(path)) {
                return null;
            }
            if (TextUtils.isEmpty(mPathPrefix)) {
                return mMap.get(path);
            }
            if (path.startsWith(mPathPrefix)) {
                return mMap.get(path.substring(mPathPrefix.length()));
            }
            return null;
        }
    
    • RegexAnnotationHandler
      RegexAnnotationHandler和DefaultRootUriHandler一样都继承自ChainedHandler,同样调用register方法加入一个UriHandler集合,只不过这里利用代理模式将UriHandler封装成RegexWrapperHandler,主要复写shouldHandle方法。最久在handleInternal循环去读取正则表达式匹配的UriHandler
    RegexWrapperHandler.class 
       @Override
        protected boolean shouldHandle(@NonNull UriRequest request) {
            return mPattern.matcher(request.getUri().toString()).matches();
        }
    
    ChainedHandler.class
    
     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);
                    }
    
                    @Override
                    public void onComplete(int resultCode) {
                        callback.onComplete(resultCode);
                    }
                });
            } else {
                callback.onNext();
            }
        }
    
    • PageAnnotationHandler
      由于被RouterPage注解的scheme+host是固定的,所以相对于RouterUri少了一步根据scheme+host获取PathHandler步骤,原理和UriAnnotationHandler相似,这里不在讲解

    解析成Intent完成跳转的UriHandler

    解析成Intent

    相关的类在activity包里


    WX20181019-134849@2x.png

    关键代码

    AbsActivityHandler.class
    
     protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
            // 创建Intent
            Intent intent = createIntent(request);
            if (intent == null || intent.getComponent() == null) {
                Debugger.fatal("AbsActivityHandler.createIntent()应返回的带有ClassName的显式跳转Intent");
                callback.onComplete(UriResult.CODE_ERROR);
                return;
            }
            intent.setData(request.getUri());
            UriSourceTools.setIntentSource(intent, request);
            // 启动Activity
            request.putFieldIfAbsent(ActivityLauncher.FIELD_LIMIT_PACKAGE, limitPackage());
            int resultCode = RouterComponents.startActivity(request, intent);
            // 回调方法
            onActivityStartComplete(request, resultCode);
            // 完成
            callback.onComplete(resultCode);
        }
    
    PathHandler.class
    
     public void register(String path, Object target, boolean exported,
                UriInterceptor... interceptors) {
            if (!TextUtils.isEmpty(path)) {
                path = RouterUtils.appendSlash(path);
                UriHandler parse = UriTargetTools.parse(target, exported, interceptors);
                UriHandler prev = mMap.put(path, parse);
                if (prev != null) {
                    Debugger.fatal("[%s] 重复注册path='%s'的UriHandler: %s, %s", this, path, prev, parse);
                }
            }
        }
    
    RegexAnnotationHandler.class
    
     public void register(String regex, Object target, boolean exported, int priority,
                             UriInterceptor... interceptors) {
            Pattern pattern = compile(regex);
            if (pattern != null) {
                UriHandler innerHandler = UriTargetTools.parse(target, exported, interceptors);
                if (innerHandler != null) {
                    RegexWrapperHandler handler = new RegexWrapperHandler(pattern, priority,
                            innerHandler);
                    addChildHandler(handler, priority);
                }
            }
        }
    

    这里构造Intent完成Activity的跳转,具体的构造方式支持两种,一种是Class,一种是ClassName。实现分别对应的是ActivityHandler和ActivityClassNameHandler
    被调用的地方如下

    UriTargetTools.class
    
     private static UriHandler toHandler(Object target) {
            if (target instanceof UriHandler) {
                return (UriHandler) target;
            } else if (target instanceof String) {
                return new ActivityClassNameHandler((String) target);
            } else if (target instanceof Class && isValidActivityClass((Class) target)) {
                //noinspection unchecked
                return new ActivityHandler((Class<? extends Activity>) target);
            } else {
                return null;
            }
        }
    
     public static UriHandler parse(Object target, boolean exported,
                UriInterceptor... interceptors) {
            UriHandler handler = toHandler(target);
            if (handler != null) {
                if (!exported) {
                    handler.addInterceptor(NotExportedInterceptor.INSTANCE);
                }
                handler.addInterceptors(interceptors);
            }
            return handler;
        }
    

    有没有有种“回到最初的地点”的感觉(register之前讲解过)

    activity跳转

    上面AbsActivityHandler handleInternal有一段代码

    int resultCode = RouterComponents.startActivity(request, intent);
    

    跟踪源码最终跳转到DefaultActivityLauncher#startActivity

    DefaultActivityLauncher.class
    
     public int startActivity(@NonNull UriRequest request, @NonNull Intent intent) {
    
            if (request == null || intent == null) {
                return UriResult.CODE_ERROR;
            }
    
            Context context = request.getContext();
    
            // Extra
            Bundle extra = request.getField(Bundle.class, FIELD_INTENT_EXTRA);
            if (extra != null) {
                intent.putExtras(extra);
            }
    
            // Flags
            Integer flags = request.getField(Integer.class, FIELD_START_ACTIVITY_FLAGS);
            if (flags != null) {
                intent.setFlags(flags);
            }
    
            // request code
            Integer requestCode = request.getField(Integer.class, FIELD_REQUEST_CODE);
    
            // 是否限制Intent的packageName,限制后只会启动当前App内的页面,不启动其他App的页面,bool型
            boolean limitPackage = request.getBooleanField(FIELD_LIMIT_PACKAGE, false);
    
            // 设置package,先尝试启动App内的页面
            intent.setPackage(context.getPackageName());
    
            int r = startIntent(request, intent, context, requestCode, true);
    
            if (limitPackage || r == UriResult.CODE_SUCCESS) {
                return r;
            }
    
            // App内启动失败,再尝试启动App外页面
            intent.setPackage(null);
    
            return startIntent(request, intent, context, requestCode, false);
        }
    

    具体其它的调用可以看DefaultActivityLauncher.class源码

    至此源码解析就告一段落了,有关UriInterceptor如何调用调用时,如何保证初始化已经完成根据接口如何构造出类的实例如何判断不同来源的跳转降级策略Gradle打包做了哪些优化如何配置混淆 都又在文档里讲解,这里就不再描述。

    相关文章

      网友评论

        本文标题:美团开源路由框架(WMRouter)学习——源码篇

        本文链接:https://www.haomeiwen.com/subject/yaurzftx.html