ARouter 源码历险记 (一)
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
目錄
ARouter 源碼歷險(xiǎn)記 (一)
ARouter 源碼歷險(xiǎn)記 (二)
ARouter 源碼歷險(xiǎn)記 (三)
ARouter 源碼歷險(xiǎn)記 (四)
ARouter 源碼歷險(xiǎn)記 (五)
總綱
????????這里是云溪社區(qū)關(guān)于ARouter發(fā)布時(shí)候的博文,算得上是ARouter的總綱和指導(dǎo)思想。但是說實(shí)話,如果不接觸源碼,真的很難看懂這篇博文的深刻內(nèi)容,所以筆者會(huì)在研究完ARouter之后再轉(zhuǎn)載這篇博文研讀一番。
????? ? PS: 源碼在這里
? ? ? ??
????? ? 代碼結(jié)構(gòu)如上,其中 app 是唯一的可運(yùn)行的demo。
????????arouter-annotation存放了定義的annotation,并且存放了兩個(gè)類
????????????? ? RouteType ? 一個(gè)枚舉類型,用來表示route的類型。
????????????? ? RouteMeta ?其實(shí)就是一個(gè)Bean ,用來存放route的基本信息,下面會(huì)看到
? ? ? ? aroute-api 核心api和處理代碼
????? ? test-module-1 demo中需要用到的測(cè)試內(nèi)容,沒啥大東西。
RouteProcessor
????? ? 從非常明了作用的類開始讀起,此類的作用當(dāng)然是使用Processor自動(dòng)生成代碼,如果對(duì)processor不熟,可以參看這里和這里。
? ? ? ? 首先是Init方法(log部分和一些不關(guān)鍵部分使用...代替)
public synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mFiler = processingEnv.getFiler(); // Generate class.typeUtil = processingEnv.getTypeUtils(); // Get type utils.elementUtil = processingEnv.getElementUtils(); // Get class meta.logger = new Logger(processingEnv.getMessager()); // Package the log utils.// Attempt to get user configuration [moduleName]Map<String, String> options = processingEnv.getOptions();if (MapUtils.isNotEmpty(options)) {moduleName = options.get(KEY_MODULE_NAME);}if (StringUtils.isNotEmpty(moduleName)) {moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");...} else {...throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");}iProvider = elementUtil.getTypeElement(Consts.IPROVIDER).asType();...}????? ? 總得來說代碼還是比較清晰的,大概做了三件事情
????? ? 1. 獲取工具類
????? ? 2.獲取傳入的 moduleName 參數(shù),并且去掉不合法字符(因?yàn)樾枰唇宇惷?#xff09;,如果沒有傳入直接報(bào)錯(cuò)
????? ? 3.獲取 com.alibaba.android.arouter.facade.template.IProvider?類的 TypeMirror 。該類的作用會(huì)在后續(xù)研讀中一點(diǎn)點(diǎn)揭開面紗。
????? ? 然后是process方法:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (CollectionUtils.isNotEmpty(annotations)) {Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);try {logger.info(">>> Found routes, start... <<<");this.parseRoutes(routeElements);} catch (Exception e) {logger.error(e);}return true;}return false;}????? ? 該方法實(shí)際上就是找出所有使用Route修飾的節(jié)點(diǎn)元素(Element),然后調(diào)用parseRoutes方法。
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {if (CollectionUtils.isNotEmpty(routeElements)) {rootMap.clear();// Fantastic fourTypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY);TypeElement type_Service = elementUtil.getTypeElement(SERVICE);// Interface of ARouter.TypeElement type_IRouteGroup = elementUtil.getTypeElement(IROUTE_GROUP);TypeElement type_IProviderGroup = elementUtil.getTypeElement(IPROVIDER_GROUP);ClassName routeMetaCn = ClassName.get(RouteMeta.class);ClassName routeTypeCn = ClassName.get(RouteType.class);/*Build input type, format as :```Map<String, Class<? extends IRouteGroup>>```*/ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(String.class),ParameterizedTypeName.get(ClassName.get(Class.class),WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))));/*```Map<String, RouteMeta>```*/ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(String.class),ClassName.get(RouteMeta.class));/*Build input param name.*/ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build(); // Ps. its param type same as groupParamSpec!/*Build method : 'loadInto'*/MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(rootParamSpec);.......................}}????? ? 一段一段來解讀,首先獲取android中原生 activity 和 service 的 TypeElement。
????? ? PS,如果你懵了,你可能需要跟著這里走一遍。
? ? ? ? 然后還要獲取 com.alibaba.android.arouter.facade.template.IRouteGroup 以及?com.alibaba.android.arouter.facade.template.IProviderGroup 的 TypeElement。
? ? ? ? 之后就是構(gòu)建java文件的一些前置操作了(使用的是javapoet).
????? ? 創(chuàng)建了 參數(shù)? Map<String, Class<? extends IRouteGroup>> routes
? ? ? ? 創(chuàng)建了 參數(shù) ?Map<String, RouteMeta> atlas
????? ? 創(chuàng)建了參數(shù) ?Map<String, RouteMeta> providers
????? 然后創(chuàng)建了一個(gè)方法 @Override public loadInto(Map<String, Class<? extends IRouteGroup>> routes){} (暫稱為方法 A )方法中內(nèi)容尚未填充。
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {............// Follow a sequence, find out metas of group first, generate java file, then statistics them as root.for (Element element : routeElements) {TypeMirror tm = element.asType();Route route = element.getAnnotation(Route.class);RouteMeta routeMete = null;if (typeUtil.isSubtype(tm, type_Activity.asType())) { // Activitylogger.info(">>> Found activity route: " + tm.toString() + " <<<");// Get all fields annotation by @AutowiredMap<String, Integer> paramsType = new HashMap<>();for (Element field : element.getEnclosedElements()) {if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !typeUtil.isSubtype(field.asType(), iProvider)) {// It must be field, then it has annotation, but it not be provider.Autowired paramConfig = field.getAnnotation(Autowired.class);paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), TypeUtils.typeExchange(field.asType()));}}routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);} else if (typeUtil.isSubtype(tm, iProvider)) { // IProviderlogger.info(">>> Found provider route: " + tm.toString() + " <<<");routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);} else if (typeUtil.isSubtype(tm, type_Service.asType())) { // Servicelogger.info(">>> Found service route: " + tm.toString() + " <<<");routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null);}categories(routeMete);// if (StringUtils.isEmpty(moduleName)) { // Hasn't generate the module name.// moduleName = ModuleUtils.generateModuleName(element, logger);// }}........}? ? ? ? ?循環(huán)遍歷分析route修飾的節(jié)點(diǎn),如果是IProvider的子類或者service的子類,那么直接將注解類型對(duì)象Route,被注解對(duì)象的Element,以及route類型(見RouteType枚舉)存入。
????? ? 如果節(jié)點(diǎn)是activity的子類,那么還需要額外獲取所有該類中被使用Autowired注解的非IProvider類型的字段(應(yīng)該是用來做注入的,沒看完源碼,還不清楚。)存儲(chǔ)為map <字段名,字段類型的枚舉值(見TypeUtils)> ? ?注意,以上Route分類后面將會(huì)頻繁用到
private void categories(RouteMeta routeMete) {if (routeVerify(routeMete)) {logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());if (CollectionUtils.isEmpty(routeMetas)) {Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {@Overridepublic int compare(RouteMeta r1, RouteMeta r2) {try {return r1.getPath().compareTo(r2.getPath());} catch (NullPointerException npe) {logger.error(npe.getMessage());return 0;}}});routeMetaSet.add(routeMete);groupMap.put(routeMete.getGroup(), routeMetaSet);} else {routeMetas.add(routeMete);}} else {logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");}}/*** Verify the route meta** @param meta raw meta*/private boolean routeVerify(RouteMeta meta) {String path = meta.getPath();if (StringUtils.isEmpty(path) || !path.startsWith("/")) { // The path must be start with '/' and not empty!return false;}if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)try {String defaultGroup = path.substring(1, path.indexOf("/", 1));if (StringUtils.isEmpty(defaultGroup)) {return false;}meta.setGroup(defaultGroup);return true;} catch (Exception e) {logger.error("Failed to extract default group! " + e.getMessage());return false;}}return true;}????? ? PS:考慮了下,還是把代碼貼上來吧,至少不會(huì)顯得全文都是空洞的文字,笑
? ? ? ? 首先驗(yàn)證RouteMeta,Route的path不能為空,并且需要以“/”開頭,并且至少需要包含兩個(gè)"/"。如果沒有設(shè)置group,默認(rèn)使用 path 的第一個(gè)單詞(前兩個(gè)"/"之間的內(nèi)容)作為默認(rèn)分組。
????? ? 然后根據(jù)group分組,放入HashMap<String , Set<RouteMeta>> groupMap中。相同的group會(huì)根據(jù)path進(jìn)行字典排序,放入TreeSet中。
? ? ? ? 所以,以上代碼就是遍歷Route注解的Element,獲取基本信息,根據(jù)分組進(jìn)行存放。
????? ? 為了便于理解,接下去的代碼有些穿插,可能會(huì)使你對(duì)源碼的結(jié)構(gòu)產(chǎn)生迷惑,請(qǐng)自行結(jié)合源碼理解。
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {............MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(providerParamSpec);// Start generate java source, structure is divided into upper and lower levels, used for demand initialization.for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {String groupName = entry.getKey();..........// Build group method bodySet<RouteMeta> groupData = entry.getValue();for (RouteMeta routeMeta : groupData) {switch (routeMeta.getType()) {case PROVIDER: // Need cache provider's super classList<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();for (TypeMirror tm : interfaces) {if (typeUtil.isSubtype(tm, iProvider)) {// This interface extend the IProvider, so it can be used for mark providerloadIntoMethodOfProviderBuilder.addStatement("providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",tm.toString().substring(tm.toString().lastIndexOf(".") + 1), // Spite unuseless namerouteMetaCn,routeTypeCn,ClassName.get((TypeElement) routeMeta.getRawType()),routeMeta.getPath(),routeMeta.getGroup());}}break;default:break;}...............}............}.........// Wirte provider into diskString providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;JavaFile.builder(PACKAGE_OF_GENERATE_FILE,TypeSpec.classBuilder(providerMapFileName).addJavadoc(WARNING_TIPS).addSuperinterface(ClassName.get(type_IProviderGroup)).addModifiers(PUBLIC).addMethod(loadIntoMethodOfProviderBuilder.build()).build()).build().writeTo(mFiler);............}}????? ? 創(chuàng)建一個(gè)方法 @Override public void loadInto(Map<String, RouteMeta> providers){} 暫時(shí)稱為 B
? ? ? ? 然后遍歷上一步生成的groupMap中的每一個(gè)RouteMeta , 如果該RoteMeta是用來記錄IProvider信息的(看上文,就是Route注解的類是IProvider的子類),遍歷該類所有直接繼承的接口,如果該接口是IProvider或者其子類(代號(hào) X),那么就在剛剛創(chuàng)建的loadInto B(不是最開始創(chuàng)建的那個(gè)方法A )方法中添加如類似如下語句:
providers.put("X的類名", RouteMeta.build(RouteType.PROVIDER, 被Route注解的類, Route的path, Route的group, null, Route的priority, Route的extra));
? ? ? ? 于是 B 方法的任務(wù)就是將所有被Route注解的IProvider類型類根據(jù)如上語句,存入傳入的參數(shù)中。
? ? 但是以上代碼是可以發(fā)現(xiàn)潛在問題的,如果有一個(gè)IProvider的子類,比如HelloService。HelloServiceImpl1和HelloServiceImpl2都實(shí)現(xiàn)了該接口,并且都是用了@Route注解,那么這里就差插入類似如下代碼:
在同一個(gè)map中put了兩個(gè)key相同的鍵值對(duì)!這回導(dǎo)致RouteMeta的丟失。 所以在實(shí)際使用中,我們需要避免如上這樣的繼承情況!
????????然后生成一個(gè)java文件,包為 ?com.alibaba.android.arouter.routes 類名為 ARouter$$Providers$$XXX (XXX為最開始傳入并處理的參數(shù)moduleName),繼承 IProviderGroup 接口,該類中只有一個(gè)方法,就是 B。
????? ? 繼續(xù)分析代碼段:
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {...................// Start generate java source, structure is divided into upper and lower levels, used for demand initialization.for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {String groupName = entry.getKey();MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(groupParamSpec);// Build group method bodySet<RouteMeta> groupData = entry.getValue();for (RouteMeta routeMeta : groupData) {................// Make map body for paramsTypeStringBuilder mapBodyBuilder = new StringBuilder();Map<String, Integer> paramsType = routeMeta.getParamsType();if (MapUtils.isNotEmpty(paramsType)) {for (Map.Entry<String, Integer> types : paramsType.entrySet()) {mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");}}String mapBody = mapBodyBuilder.toString();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.get((TypeElement) routeMeta.getRawType()),routeMeta.getPath().toLowerCase(),routeMeta.getGroup().toLowerCase());}// Generate groupsString groupFileName = NAME_OF_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);logger.info(">>> Generated group: " + groupName + "<<<");rootMap.put(groupName, groupFileName);}.............}?遍歷groupMap中的所有Group組,創(chuàng)建方法 @Override public void loadInto(Map<String, RouteMeta> atlas) 暫時(shí)稱為C
?接著遍歷該group組中的所有RouteMate,每個(gè)RouteMate?都會(huì)生成一個(gè)如下語句插入方法C中
atlas.put(Route的Path, RouteMeta.build(RouteMate類型, 注解類.class, Route的Path, Route的Group, 記錄Autowired的map, Route的priority, Route的extra));
其中map類似如下創(chuàng)建
?
new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); put("url", 18); }}
? ? ? 然后為該Group生成一個(gè)java文件,包名為?com.alibaba.android.arouter.routes 類名為 ARouter$$Group$$XXX (XXX為 該group組的 groupName),繼承接口 IRouteGroup ,方法就只有 C。
????? ? 最后還需要將 groupName : 該group生成的類名 的鍵值對(duì)存入rootMap中
????? ? 勝利就在眼前了,來看最后一段代碼
if (MapUtils.isNotEmpty(rootMap)) {// Generate root meta by group name, it must be generated before root, then I can findout the class of group.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()));}}.......// Write root meta into disk.String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;JavaFile.builder(PACKAGE_OF_GENERATE_FILE,TypeSpec.classBuilder(rootFileName).addJavadoc(WARNING_TIPS).addSuperinterface(ClassName.get(elementUtil.getTypeElement(ITROUTE_ROOT))).addModifiers(PUBLIC).addMethod(loadIntoMethodOfRootBuilder.build()).build()).build().writeTo(mFiler);? ? ? ?還記得方法A嗎?創(chuàng)建之后被冷落了好久,回上去看下吧!!
????? ? 遍歷rootMap, 在方法A中類似添加如下語句
? ? ? ? routes.put(groupName, group對(duì)應(yīng)的類名.class);
? ? ? ? 最后,生成一個(gè)java文件,包和之前生成的文件一樣,類名為 ARouter$$Root$$XXX (xxx表示moduleName),繼承接口IRouteRoot,添加方法 A!
總結(jié)
????? ? 至此RouteProcessor的內(nèi)容就完全分析完成了,我們知道了該處理器會(huì)生成一些文件,就拿demo來說,它一共會(huì)生成如下文件
????????
????? ? arouter-api模塊中自導(dǎo)生成的類
????????
? ? PS:除了ARouter$$Interceptors$$app
? ? app 模塊生成的類
? PS:除了ARouter$$Interceptors$$testmodule1
? ? test-module-1模塊生成的代碼
? ? 結(jié)合具體生成的代碼內(nèi)容來看源碼就非常容易理解了。
? ? PS:實(shí)際上,我們直接看生成的文件也許就能了解Processor所做的內(nèi)容,但是我還是進(jìn)行了詳細(xì)的記錄,目的還是想通過源代碼潛移默化提高自己,愿好!
? ? 下一篇我們將繼續(xù)在 ARouter 中探險(xiǎn)。
轉(zhuǎn)載于:https://my.oschina.net/zzxzzg/blog/861510
總結(jié)
以上是生活随笔為你收集整理的ARouter 源码历险记 (一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 验收测试 4
- 下一篇: ng-repeat 的重复问题