美團開源路由框架(WMRouter)學習——源碼篇

前言

源碼的解讀是痛苦的,這裏我打算帶着幾個問題從各個方面去解讀源碼 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包裏



關鍵代碼

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打包做了哪些優化如何配置混淆 都又在文檔裏講解,這裏就不再描述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章