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

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

起因

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

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

// 注解注釋某個(gè)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)過(guò)程如下圖:

image.png

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

自定義Annotation注解

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

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

自定義AbstractProcessor的子類(lèi)

接著,新建另一個(gè)java library模塊,用來(lái)編寫(xiě)AbstractProcessor的子類(lèi)。該模塊的gradle文件定義如下:

apply plugin: 'java-library'

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

}

sourceCompatibility = "7"
targetCompatibility = "7"

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

getSupportedAnnotationTypes方法返回一個(gè)set集合,用來(lái)保存編譯器支持掃描的注解類(lèi)型。

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

process方法,在編譯期間會(huì)被調(diào)用,此時(shí)我們可以利用javapoet來(lái)生成相應(yīng)的java代碼。具體的解釋在代碼中寫(xiě)的已經(jīng)很清楚,可以對(duì)照著看。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 得到所有被router注解的類(lèi)
    Set<? extends Element> annotatedElement = roundEnv.getElementsAnnotatedWith(Router.class);
    /**
     *   public void handle(Map<String, Class<?>> routeMap) {
     *   }
     *
     *   構(gòu)建map類(lèi)型的參數(shù),Map<String, Class<?>>類(lèi)型
     */
    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類(lèi)型的方法
            .returns(void.class) // 返回值為void
            .addParameter(ParameterSpec.builder(typeName, "routeMap").build()); // 方法參數(shù)名 routeMap

    for (Element element : annotatedElement) {
        // 1、ElementKind.CLASS表示注解注解的類(lèi)型是class
        // 2、ElementKind.FIELD表示注解的類(lèi)型是類(lèi)的屬性
        if (element.getKind() == ElementKind.CLASS) {
            TypeElement typeElement = (TypeElement) element;
            // 獲取注解類(lèi)的包名信息
            PackageElement packageElement = (PackageElement) element.getEnclosingElement();
            // 通過(guò)類(lèi)的全名獲取注解類(lèi)的classname對(duì)象
            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) 這里有三個(gè)占位符$N、$S、$T
            // 3、$N 可代表方法,變量等等;$S表示字符串;$T表示類(lèi),并且會(huì)自動(dòng)import該類(lèi)
            methodSpec.addStatement("$N.put($S,$T.class)", "routeMap", annotation.value(), className);
        }
    }

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

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

    return true;
}

此時(shí),build一下我們的代碼,就會(huì)在如下路徑中

image.png

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

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

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

把字符串與類(lèi)之間的關(guān)系寫(xiě)入到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方法,我這邊為了簡(jiǎn)單起見(jiàn),就默認(rèn)只生成了一個(gè)java文件,按理說(shuō),不同的模塊就會(huì)生成一份不同的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");

    }
}

以上就是整個(gè)實(shí)現(xiàn)過(guò)程,如有問(wèn)題,歡迎討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容