ARouter路由插件源碼分析

彙總篇章:

ARouter路由源碼分析彙總

前言

前面對ARouter的源碼進行了分析,爲了分析的完整性(潔癖),接下來就對ARouter的插件進行分析,這一篇是基於ARouter的gradle插件及編譯的註解生成部分進行源碼分析,當然也會通過一些小例子來輔助我們理解這塊,順便提高我們擼代碼的逼格

插件啓動

在ARouter源碼的gradle項目中,arouter-gradle-plugin這個module中用來做gradle插件,我們進入gradle插件的啓動類

public class PluginLaunch implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        def isApp = project.plugins.hasPlugin(AppPlugin)
        首先判斷在application模塊下在進行插件生成註冊代碼
        if (isApp) {
            Logger.make(project)
            Logger.i('Project enable arouter-register plugin')
            def android = project.extensions.getByType(AppExtension)
            def transformImpl = new RegisterTransform(project)

            初始化arouter的配置信息
            ArrayList<ScanSetting> list = new ArrayList<>(3)
            list.add(new ScanSetting('IRouteRoot'))
            list.add(new ScanSetting('IInterceptorGroup'))
            list.add(new ScanSetting('IProviderGroup'))
            填充目標配置信息集合
            RegisterTransform.registerList = list
            向application中進行插件註冊
            android.registerTransform(transformImpl)
        }
    }
}

插件Transform轉換

在這裏插入圖片描述

[getName]:指明transform轉換的名稱,在ARouter中聲明爲"com.alibaba.arouter"
[isIncremental]:是否開啓增量編譯,在ARouter中關閉了增量編譯

[getInputTypes]:
	【ContentType的數據類型】: CLASSES和RESOURCES兩種
      CLASSES:包含了項目中.class文件和三方庫中的.class文件
      RESOURCES:僅包含項目中的.class文件
	 在ARouter中inputType爲CLASSES

[getScopes]:要處理的.class文件的範圍        
	【Scope的數據類型】: PROJECT、SUB_PROJECTS、EXTERNAL_LIBRARIES、PROJECT_LOCAL_DEPS、SUB_PROJECTS_LOCAL_DEPS等
   在ARouter中getScope爲Scope.PROJECT,Scope.PROJECT_LOCAL_DEPS,Scope.SUB_PROJECTS,Scope.SUB_PROJECTS_LOCAL_DEPS,Scope.EXTERNAL_LIBRARIES

[transform]: 即轉換的主要邏輯實現
    

接下來就着重分析transform,

  • 第一部分掃描jar文件是:

    在這裏插入圖片描述

    • 獲取jar的絕對路徑的md5值

    • 使用OutputProvider輸出名爲 jar名稱_md5 值的文件

    • 對非如下條件的:
      在這裏插入圖片描述

      進行jar文件掃描

    • ScanUtil的scanJar方法

      在這裏插入圖片描述

      將編譯的jar文件內com/alibaba/android/arouter/routes/所有的內容進行scanClass處理(scanClass放在後面統一分析);

      對於com/alibaba/android/arouter/core/LogisticsCenter.class 在掃描完成後,生成註冊代碼進目標文件,同步RegisterTransform的fileContainsInitClass狀態

      可能有人對這塊有些疑問,其實這裏就牽扯到前面文章分析的LogisticsCenter中的loadRouterMap方法

      這裏就動態的添加registerRouteRoot(目標類)

      在這裏插入圖片描述

  • 第二部分掃描class文件是:

    在這裏插入圖片描述

    這裏就是掃描所有的class內容,對符合條件的class進行scanClass操作

    在這裏插入圖片描述

    在scanClass中藉助ASM的classVisitor將符合條件的class存放在ScanSetting的list中

  • 最後一部分

    若scanJar方法掃描到LogisticsCenter,則執行更新代碼操作

    在這裏插入圖片描述

    遍歷ScanSetting的list去更新loadRouterMap的方法,實現方法在RegisterCodeGenerator中,關鍵代碼如下:

    在這裏插入圖片描述

    藉助ASM的ClassVisitor的visitMethod方法,在RouteMethodVisitor中進行插入代碼:

    在這裏插入圖片描述

    至此插件部分相關的就結束了

Compiler相關

前面贅述了在gradle插件中的工作,接下來就分析ARouter中註解處理部分,在這部分中主要處理三個註解處理類

AutowiredProcessor

這個類中,處理的就是我們在使用過程中@Autowired 的註解,幫我們對我們@Autowired註解修飾的變量進行賦值

在該註解處理器中總共可分爲註解分類識別和代碼生成兩個功能

  • 註解分類識別

    在這裏插入圖片描述

    在該部分中,將roundEnvironment中獲取的所有的Autowired進行分類

    將需要注入的成員變量的相關信息存放在名爲parentAndChild的容器內

  • 代碼生成generateHelper

    在這裏插入圖片描述

    首先獲取如下類信息:
    com.alibaba.android.arouter.facade.template.ISyringe
    com.alibaba.android.arouter.facade.service.SerializationService
    com.alibaba.android.arouter.facade.template.IProvider
    android.app.Activity
    android.app.Fragment
    android.support.v4.app.Fragment
    

    在這裏插入圖片描述

    創建一個方法帶Override註解名爲inject且帶有一個Object類型參數的Public方法
    
    創建一個實現了ISyringe接口名爲[註解所在類名稱$$ARouter$$Autowired]的Public類
    
    

    在這裏插入圖片描述

    添加一個名爲SerializationService的private成員變量
    
    在剛纔創建的inject方法中添加如下兩行代碼:
    	serializationService = ARouter.getInstance().navigation(SerializationService.class);
    	 註解所在類 substitute = (註解所在類)target;
    	 
    
    • 情況一:

      在這裏插入圖片描述

      遍歷當前類內所有帶Autowired註解的成員變量
      當成員變量的類型是IProvider時
      如果Autowired註解的name沒有賦值則用類型跳轉,語句爲:
      	substitute.成員變量名稱 = ARouter.getInstance().navigation(當前類.class);
      否則添加如下語句:
      	substitute.成員變量名稱 = ARouter.getInstance().build(Autowired註解的name).navigation(當前類.class);
      	
      該註解參數是否必傳,如果是必傳,則在inject添加如下語句:
      	if (substitute.成員變量名稱 == null) {
      		throw new RuntimeException("The field 成員變量名稱 is null, in class 當前類名稱 !");
      	}
      	
      
    • 情況二

      在這裏插入圖片描述

      buildStatement方法體過長,這裏只羅列一部分:

      在這裏插入圖片描述

      正常進入時
      如果是Activity的,添加:
      	substitute.成員變量名稱 = [如果是Serializable類型的則加上類型轉換]substitute.getIntent().getBooleanExtra(如果註解的name存在則用name沒有則用substitute.成員變量名稱)
      如果是fragment的,則添加:
      	substitute.成員變量名稱 = [如果是Serializable類型的則加上類型轉換]substitute.getArguments().getBoolean(如果註解的name存在則用name沒有則用substitute.成員變量名稱)
      	
      	
      如果是OBJECT,則statement爲serializationService.parseObject
      此時會添加如下代碼:
      	if (null != serializationService) {
            substitute.成員變量名稱 = serializationService.parseObject(substitute.getIntent().getStringExtra(如果註解的name存在則用name沒有則用substitute.成員變量名稱), new com.alibaba.android.arouter.facade.model.TypeWrapper<當前歸屬類>(){}.getType());
          } else {
            Log.e("ARouter::", "You want automatic inject the field 'substitute.成員變量名稱' in class 當前歸屬類 , then you should implement 'SerializationService' to support object auto inject!");
          }
      

      在這裏插入圖片描述

      與之前的必傳項相同,以上就是對AutoProcessor的註解處理

RouteProcessor

核心註解處理類,之所以成爲核心註解處理類,ARouter的跳轉的關鍵實現就是這裏,廢話不多說直接開大

在這裏插入圖片描述

直接對Route方法進行parse處理

  • 在這裏插入圖片描述

    獲取Activity/Service/fragment/IRouteGroup/IRouteRoot類型
    

    在這裏插入圖片描述

    創建一個Map<String, Class<? extends IRouteGroup>>和一個Map<String, RouteMeta>
    

    在這裏插入圖片描述

    構建參數名爲:
      Map<String, Class<? extends IRouteGroup>> routes
      Map<String, RouteMeta> atlas
      Map<String, RouteMeta> providers
     
    創建一個帶Override名爲loadInfo且帶有"Map<String, Class<? extends IRouteGroup>> routes"參數的public方法
    

    在這裏插入圖片描述

    根據element的類型,進行填充routeMeta
    

    在這裏插入圖片描述

    將RouteMeta放到對應的分組中
    

    在這裏插入圖片描述

    創建一個帶Override名爲loadInfo且帶有"Map<String, RouteMeta> providers"參數的public方法
    創建一個存放route文檔的容器
    
    對分組數據的處理
    //遍歷分組數據
    for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                    String groupName = entry.getKey();
    								//創建一個帶Override名爲loadInfo且帶有"Map<String, RouteMeta> atlas"參數的public方法
                    MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                            .addAnnotation(Override.class)
                            .addModifiers(PUBLIC)
                            .addParameter(groupParamSpec);
    								//創建route信息的容器
                    List<RouteDoc> routeDocList = new ArrayList<>();
    
                    // 按組內的信息存放對應的組內
                    Set<RouteMeta> groupData = entry.getValue();
                    for (RouteMeta routeMeta : groupData) {
                      	//生成對應的routeMeta詳細內容
                        RouteDoc routeDoc = extractDocInfo(routeMeta);
    
                        ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
    
                        switch (routeMeta.getType()) {
                            case PROVIDER: 
                            //獲取provider類型的父類信息
                                List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                                for (TypeMirror tm : interfaces) {
                                    routeDoc.addPrototype(tm.toString());
    																	//找到iProvider類型
                                      //在provider中添加如下語句:
                                        //" providers.put(掃描當前類信息, RouteMeta.build(RouteType.PROVIDER, 當前類.class, route路徑, 分組名稱, 等));"
                                    if (types.isSameType(tm, iProvider)) {
                                        loadIntoMethodOfProviderBuilder.addStatement(
                                                "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                                (routeMeta.getRawType()).toString(),
                                                routeMetaCn,
                                                routeTypeCn,
                                                className,
                                                routeMeta.getPath(),
                                                routeMeta.getGroup());
                                    } else if (types.isSubtype(tm, iProvider)) {
                                        loadIntoMethodOfProviderBuilder.addStatement(
                                                "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                                tm.toString(),    // So stupid, will duplicate only save class name.
                                                routeMetaCn,
                                                routeTypeCn,
                                                className,
                                                routeMeta.getPath(),
                                                routeMeta.getGroup());
                                    }
                                }
                                break;
                            default:
                                break;
                        }
    
                        // Make map body for paramsType
                        StringBuilder mapBodyBuilder = new StringBuilder();
                        Map<String, Integer> paramsType = routeMeta.getParamsType();
                        Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                        if (MapUtils.isNotEmpty(paramsType)) {
                            List<RouteDoc.Param> paramList = new ArrayList<>();
    
                            for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                                mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
    
                                RouteDoc.Param param = new RouteDoc.Param();
                                Autowired injectConfig = injectConfigs.get(types.getKey());
                                param.setKey(types.getKey());
                                param.setType(TypeKind.values()[types.getValue()].name().toLowerCase());
                                param.setDescription(injectConfig.desc());
                                param.setRequired(injectConfig.required());
    
                                paramList.add(param);
                            }
    
                            routeDoc.setParams(paramList);
                        }
                        String mapBody = mapBodyBuilder.toString();
    										//在參數名爲atlas的loadinfo方法中添加如下語句:
                        //atlas.put(route路徑, RouteMeta.build(routeMeta類型, Test2Activity.class, route路徑", "test", new java.util.HashMap<String, Integer>(){{如果有參數}}等));
                        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());
    
                        routeDoc.setClassName(className.toString());
                        routeDocList.add(routeDoc);
                    }
    

    在這裏插入圖片描述

    向參數名爲routes的loadInto方法中添加
    	routes.put(分組名稱, 分組生成類.class);)
    如果生成routeDoc,則輸出routeDoc
    
    生成文件

    在這裏插入圖片描述

    將provider/root寫進本地文件
    
    名稱爲:[ARouter$$Providers$$分組名稱]
    
    名稱爲:[ARouter$$Root$$分組名稱]
    
    

    到這裏就結束了

InterceptorProcessor

在這裏插入圖片描述

查找所有的Interceptor註解方法,按照其優先級存放

在這裏插入圖片描述

創建一個名爲[ARouter$$Interceptors$$分組名稱]
創建一個帶Override名爲loadInfo且帶有"Map<Integer, Class<? extends IInterceptor>> interceptors"參數的public方法

將掃描到所有interceptor在loadInfo方法中添加:
interceptors.put(interceptor優先級, intercepter所在類.class);

小結

以上就是所有註解部分和插件部分的內容了,請按照大綱中的流程來瀏覽不迷路

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