ARouter原始碼分析(2)-路由節點的動態注入
文章是作者學習 ARouter
的原始碼的重點紀要。 ARouter官方文件 : ofollow,noindex">https://github.com/alibaba/ARouter/blob/master/README_CN.md
我前面其實已經分析過 WMRouter
的路由節點的生成原理: https://www.jianshu.com/p/116adb143970
其實 ARouter
的路由表的生成的步驟和 WMRouter
差不多,也是使用 : 註解處理
, Gradle Transfrom API
, javapoet
, asm
。再貼一下相關文章技術文章以便了解:
註解 : https://blog.csdn.net/jeasonlzy/article/details/74273851
javapoet : https://blog.csdn.net/qq_18242391/article/details/77018155
gradle Transfrom api : https://www.jianshu.com/p/37df81365edf
我們來繼續看 ARouter
的路由節點的生成原理, 我們先來看 @Route
的註解掃描處理器:
public class RouteProcessor extends AbstractProcessor { private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta. private Map<String, String> rootMap = new TreeMap<>();// Map of root metas, used for generate class file in order. @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element element : routeElements) { Route route = element.getAnnotation(Route.class); RouteMeta routeMeta; // 根據 @Route 的註解資訊,生成一個 RouteMeta物件。 categories(routeMeta);//把這個 RouteMeta放到 groupMap 中, key 為組名 } //atlas引數 ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build(); for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) { // 每個組,都要生成 loadInto 方法 MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(“loadInto”) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(groupParamSpec); // 產生 RouteMeta的注入方法 , 比如 -> //atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648)); loadIntoMethodOfGroupBuilder.addStatement( "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))", routeMeta.getPath(), routeMetaCn, routeTypeCn, className, routeMeta.getPath().toLowerCase(), routeMeta.getGroup().toLowerCase()); // 生成對應的組的檔案,比如 :ARouter$$Group$$test.java String groupFileName = "ARouter$$Group$$" + groupName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(groupFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(type_IRouteGroup)) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfGroupBuilder.build()) .build() ).build().writeTo(mFiler); rootMap.put(groupName, groupFileName); //把路由組檔案資訊儲存起來 } //載入每個路由組的類 if (MapUtils.isNotEmpty(rootMap)) { for (Map.Entry<String, String> entry : rootMap.entrySet()) { loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue())); } } //ARouter$$Root$$app.loadInto(Map<String, Class<? extends IRouteGroup>> routes) 方法 MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(rootParamSpec); // 載入每個路由組的類 String rootFileName = “ARouter$$Root$$” + moduleName; JavaFile.builder(PACKAGE_OF_GENERATE_FILE, TypeSpec.classBuilder(rootFileName) .addJavadoc(WARNING_TIPS) .addSuperinterface(ClassName.get(elements.getTypeElement(ITROUTE_ROOT))) .addModifiers(PUBLIC) .addMethod(loadIntoMethodOfRootBuilder.build()) .build() ).build().writeTo(mFiler); } }
這裡我不做詳細解釋了(註釋寫的很清楚了),我們這裡直接舉一個例子,比如我們這樣標記了一個介面:
@Route(path = "/test/activity1") public class Test1Activity extends AppCompatActivity { }
那麼按照 RouteProcessor
處理後將會生成以下類檔案:
ARouter$$Group$$test.java
public class ARouter$$Group$$test implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", null, -1, -2147483648)); } }
ARouter$$Root$$app.java
:
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("test", ARouter$$Group$$test.class); } }
參照上面這兩個檔案和 RouteProcessor
,應該大致可以理解。好,對應註冊 路由表與組
的檔案已經生成,那麼這些檔案是如何在 ARouter
執行的時候載入到記憶體的呢?
猜測:這些產生的類檔案,在執行的時候,應該會被例項化,然後動態註冊到 Warehouse
中。其實實現方式類似於 WMRouter
,也是使用 gradle Transfrom api
和 asm庫
, 下面來看一下 :
開啟 ARouter
找到 gradle 外掛。首先在外掛啟動的時候:
public class PluginLaunch implements Plugin<Project> { @Override public void apply(Project project) { def isApp = project.plugins.hasPlugin(AppPlugin) if (isApp) { .... ArrayList<ScanSetting> list = new ArrayList<>(3) list.add(new ScanSetting('IRouteRoot')) list.add(new ScanSetting('IInterceptorGroup')) list.add(new ScanSetting('IProviderGroup')) RegisterTransform.registerList = list //register this plugin android.registerTransform(new RegisterTransform(project)) } } }
可以看到會註冊 RegisterTransform
,並把 RegisterTransform.registerList
,加入幾個 ScanSetting
定義的介面, 比如 IRouteRoot
。(注意,上面 ARouter$$Root$$app.java
實現的就是這個介面)
class RegisterTransform extends Transform { static ArrayList<ScanSetting> registerList @Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) { inputs.each { TransformInput input -> input.jarInputs.each { JarInput jarInput -> // 掃描jar包 .... File src = jarInput.file // input file File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR) // output file if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) { ScanUtil.scanJar(src, dest) } } input.directoryInputs.each { DirectoryInput directoryInput -> // 掃描指定目錄 ...... ScanUtil.scanClass(file) .... } } } } static void scanJar(File jarFile, File destFile) { if (jarFile) { def file = new JarFile(jarFile) Enumeration enumeration = file.entries() while (enumeration.hasMoreElements()) { String entryName = (JarEntry) enumeration.nextElement().getName() if (entryName.startsWith("com/alibaba/android/arouter/routes/")) {//掃描這個目錄下的檔案 //如果這個檔案實現了`RegisterTransform.registerList`的介面,就把這個類儲存下來(儲存類名) scanClass(File file) // 使用 asm庫,來訪問class檔案,完成這個操作 } .... } } } //scanClass(File file) 最終會呼叫下面的 visit方法 void visit(int version, int access, String name, String signature,String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces) RegisterTransform.registerList.each { ext -> if (ext.interfaceName && interfaces != null) { interfaces.each { itName -> if (itName == ext.interfaceName) { ext.classList.add(name)// 其實就是把 "com/alibaba/android/arouter/routes/" 目錄下的檔案加入到RegisterTransform.registerList集合中 } } } } }
好經過上面的一頓操作, RegisterTransform.registerList
已經儲存了實現了 IRouteRoot
介面的類集合。
if (fileContainsInitClass) { registerList.each { ext -> RegisterCodeGenerator.insertInitCodeTo(ext) } }
RegisterCodeGenerator.insertInitCodeTo(ext)
這裡不仔細追了,其實就是通過 asm
庫最終把路由表的初始化程式碼插入到了 com/alibaba/android/arouter/core/LogisticsCenter.loadRouterMap
方法中:
/** * arouter-auto-register plugin will generate code inside this method * call this method to register all Routers, Interceptors and Providers */ private static void loadRouterMap() { registerByPlugin = false; //auto generate register code by gradle plugin: arouter-auto-register register("com.xxx.ARouter$$Root$$app") }
看一下 register()
方法:
private static void register(String className) { ... if (!TextUtils.isEmpty(className)) { try { Class<?> clazz = Class.forName(className); Object obj = clazz.getConstructor().newInstance(); if (obj instanceof IRouteRoot) { obj.loadInto(Warehouse.groupsIndex); } } } ... }
例項化 ARouter$$Group$$test
這種實現了 IRouteRoot
介面的類,然後把傳入路由表,新增路由資訊。
到這裡 @Route
註冊的頁面,在程式執行時都以 RouteMeta
的形式放到了路由表中。方便路由使用。
一圖勝千言, 我們用一張圖來總結一下上面所做的操作:

路由節點的動態注入.png