從0到1實(shí)現(xiàn)跨模塊路由框架的基本原理

讀完本篇能夠了解的內(nèi)容
1精耐、注解的一些基本使用饭玲;
2、gradle 5.4.1版本中如何正確的導(dǎo)入com.google.auto.service:auto-service:1.0-rc7
類庫;
3扣唱、利用javapoet編寫java文件;
4、如何在編譯期生成代碼噪沙;
5炼彪、利用反射執(zhí)行編譯時生成的java類文件。

起因

項目中看到中臺編寫的router路由框架可以利用注解注釋后的值進(jìn)行跳轉(zhuǎn)正歼,于是產(chǎn)生了興趣辐马,探究了里面實(shí)現(xiàn)的基本原理。

具體表現(xiàn)如下代碼:

// 注解注釋某個activity
@Route(path = "/module_mall/my_points_activity")
public class MyPointsActivity 

// 跳轉(zhuǎn)
ARouter.getInstance().build("/module_mall/my_points_activity")
.navigation();

主要實(shí)現(xiàn)過程如下圖:

image.png

實(shí)現(xiàn)過程

自定義Annotation注解

新建一個java library模塊局义,用來編寫注解相關(guān)的代碼@Target(ElementType.TYPE)表示該注解只能作用于類喜爷,@Retention(RetentionPolicy.CLASS)表示該注解作用的生命周期為編譯期間。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
   String value();
}

自定義AbstractProcessor的子類

接著萄唇,新建另一個java library模塊檩帐,用來編寫AbstractProcessor的子類。該模塊的gradle文件定義如下:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' // 需要引入auto-service中的注解另萤,這里要這樣寫湃密。要不然不能正確生成
    compileOnly 'com.google.auto.service:auto-service:1.0-rc7' 
    implementation 'com.squareup:javapoet:1.13.0' // 方便生成java類
    implementation project(':my_annotation') // 這是自定義的模塊
    implementation project(path: ':apt') // 這是自定義的模塊

}

sourceCompatibility = "7"
targetCompatibility = "7"

注意點(diǎn):要用annotationProcessor來引入com.google.auto.service:auto-service:1.0-rc7,這樣才能保證正確生成代碼仲墨,這里對應(yīng)第二個問題勾缭。

getSupportedAnnotationTypes方法返回一個set集合,用來保存編譯器支持掃描的注解類型目养。

@Override
public Set<String> getSupportedAnnotationTypes() {
    LinkedHashSet<String> types = new LinkedHashSet<>();
    types.add(Router.class.getCanonicalName());
    return types;
}

process方法俩由,在編譯期間會被調(diào)用,此時我們可以利用javapoet來生成相應(yīng)的java代碼癌蚁。具體的解釋在代碼中寫的已經(jīng)很清楚幻梯,可以對照著看。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 得到所有被router注解的類
    Set<? extends Element> annotatedElement = roundEnv.getElementsAnnotatedWith(Router.class);
    /**
     *   public void handle(Map<String, Class<?>> routeMap) {
     *   }
     *
     *   構(gòu)建map類型的參數(shù)努释,Map<String, Class<?>>類型
     */
    ParameterizedTypeName typeName = ParameterizedTypeName.get(
            ClassName.get(Map.class),
            ClassName.get(String.class),
            ParameterizedTypeName.get(
                    ClassName.get(Class.class),
                    WildcardTypeName.subtypeOf(Object.class)
            )
    );

    // 方法名 handle
    MethodSpec.Builder methodSpec = MethodSpec.methodBuilder("handle")
            .addAnnotation(Override.class) // 給方法添加override注解
            .addModifiers(Modifier.PUBLIC) // public類型的方法
            .returns(void.class) // 返回值為void
            .addParameter(ParameterSpec.builder(typeName, "routeMap").build()); // 方法參數(shù)名 routeMap

    for (Element element : annotatedElement) {
        // 1碘梢、ElementKind.CLASS表示注解注解的類型是class
        // 2、ElementKind.FIELD表示注解的類型是類的屬性
        if (element.getKind() == ElementKind.CLASS) {
            TypeElement typeElement = (TypeElement) element;
            // 獲取注解類的包名信息
            PackageElement packageElement = (PackageElement) element.getEnclosingElement();
            // 通過類的全名獲取注解類的classname對象
            ClassName className = ClassName.get(packageElement.getQualifiedName().toString(), typeElement.getSimpleName().toString());
            // 獲取該注解
            Router annotation = element.getAnnotation(Router.class);
            // 1伐蒂、annotation.value()注解的值
            // 2煞躬、$N.put($S,$T.class) 這里有三個占位符$N、$S逸邦、$T
            // 3恩沛、$N 可代表方法,變量等等缕减;$S表示字符串雷客;$T表示類,并且會自動import該類
            methodSpec.addStatement("$N.put($S,$T.class)", "routeMap", annotation.value(), className);
        }
    }

    ClassName interfaceName = ClassName.get(RouteTable.class);

    // 動態(tài)生成類桥狡,類名目前是寫死的搅裙,其實(shí)可以利用包名生成特定的Java類
    TypeSpec helloWorld = TypeSpec.classBuilder("PerryRouteTable")
            .addModifiers(Modifier.PUBLIC) // public類型的類
            .addSuperinterface(interfaceName) // 實(shí)現(xiàn)的接口
            .addMethod(methodSpec.build()) // 實(shí)現(xiàn)的方法
            .build();
    
    // 動態(tài)生成文件
    JavaFile javaFile = JavaFile.builder(AptManager.PACKAGE_NAME, helloWorld)
            .build();
    try {
        //來自javapoet  動態(tài)生成方法
        javaFile.writeTo(processingEnv.getFiler());
    } catch (IOException e) {
        e.printStackTrace();
    }

    return true;
}

此時皱卓,build一下我們的代碼,就會在如下路徑中

image.png

自動生成如下代碼內(nèi)容:

public class PerryRouteTable implements RouteTable {
  @Override
  public void handle(Map<String, Class<?>> routeMap) {
    routeMap.put("third_activity",ThirdActivity.class);
  }
}

利用反射調(diào)用build中的代碼

把字符串與類之間的關(guān)系寫入到map集合中去部逮。

public class AptManager {
    public static final String PACKAGE_NAME = "com.perry.router";
    private static Map<String, Class<?>> sRouteTable = new HashMap<>();

    public static void registerModule(String moduleName) {
        String routeTableName = PACKAGE_NAME + "." + moduleName;
        try {
            Class<?> clazz = Class.forName(routeTableName);
            Constructor constructor = clazz.getConstructor();
            RouteTable instance = (RouteTable) constructor.newInstance();
            instance.handle(sRouteTable);

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Class<?> getClazzFromKey(String routerKey) {
        if (sRouteTable.isEmpty()) return null;
        return sRouteTable.get(routerKey);
    }
}

在我們的主模塊中利用反射調(diào)用handle方法娜汁,我這邊為了簡單起見,就默認(rèn)只生成了一個java文件甥啄,按理說存炮,不同的模塊就會生成一份不同的java文件,然后循環(huán)遍歷并且利用反射調(diào)用handle方法就可以了蜈漓。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AptManager.registerModule("PerryRouteTable");

    }
}

以上就是整個實(shí)現(xiàn)過程穆桂,如有問題,歡迎討論融虽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末享完,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子有额,更是在濱河造成了極大的恐慌般又,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巍佑,死亡現(xiàn)場離奇詭異茴迁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萤衰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門堕义,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脆栋,你說我怎么就攤上這事倦卖。” “怎么了椿争?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵怕膛,是天一觀的道長。 經(jīng)常有香客問我秦踪,道長褐捻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任椅邓,我火速辦了婚禮舍扰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘希坚。我一直安慰自己,他們只是感情好陵且,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布裁僧。 她就那樣靜靜地躺著个束,像睡著了一般。 火紅的嫁衣襯著肌膚如雪聊疲。 梳的紋絲不亂的頭發(fā)上茬底,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音获洲,去河邊找鬼阱表。 笑死,一個胖子當(dāng)著我的面吹牛贡珊,可吹牛的內(nèi)容都是我干的最爬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼门岔,長吁一口氣:“原來是場噩夢啊……” “哼爱致!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寒随,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤糠悯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妻往,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體互艾,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年讯泣,在試婚紗的時候發(fā)現(xiàn)自己被綠了纫普。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡判帮,死狀恐怖局嘁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晦墙,我是刑警寧澤悦昵,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站晌畅,受9級特大地震影響但指,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抗楔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一棋凳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧连躏,春花似錦剩岳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晓铆。三九已至,卻和暖如春绰播,著一層夾襖步出監(jiān)牢的瞬間骄噪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工蠢箩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留链蕊,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓谬泌,卻偏偏與公主長得像滔韵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呵萨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345