Android注解、反射及其应用--自动生成类-精简版ARouter

1. 背景

注解和反射的原理,见https://blog.csdn.net/newchenxf/article/details/90231512,本文主要讲如何自动生成类。
这算是一个高端用法,挺有意思。

首先,要自动生成类,就要用到APT,Annotation-Processing-tool的简写,称为注解处理器
注解处理器在编译期被调用,可以扫描特定注解的信息,你可以为你自己的的注解注册处理器,一个特定的注解处理器以java源码作为输入,然后生成一些文件作(通常为java)为输出这些java文件同样会被编译。这意味着,你可以根据注解的信息和被注解类的信息生成你想生成的代码!

2. 需求目标

利用APT,生成一个string 和 activity的对应关系的类,然后跳转页面,使用string做跳转。这其实就是传说中的ARouter开源代码。只不过,咱这里精简化。

3. 建立注解模块CommonAnnotation

虽然只有一个注解类,但为了解耦,单独放在一个模块,这个模块,是java库。
File -> New -> New Module -> Java Library

package com.chenxf.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 路由定义的基本格式:
 * scheme://host/:i{key1}/path1/:f{key2}
 * 整个路由,对应一个activity
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface RouterMap {
    /**
     * 页面对应的scheme
     */
    String value();

    /**
     * 页面对应的注册制id列表,格式为{biz_id}_{biz_sub_id},例如:"100_109"
     * 可以做到数字,来制定某个activity,这样android & ios可以用数字来代表某个页面
     * 不过本例子,不使用该字段,不然套讨论的多了
     */
    String[] registry() default {};
}

4. 建立注解处理模块CommonProcessor

也是一个java模块。虽然也只有一个类,哈哈。这个类,用到了反射!哎,注解离不开反射呀

package com.example.compiler;

import com.chenxf.annotation.RouterMap;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

/**
这个类,将在编译期间执行,引用本模块的其他模块,如果用了RouterMap注解,则会生成一个类,在
 build/generated/source/apt/debug/...
 生成的类,可以直接被使用
 */
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
    private static final String DEFAULT_REGISTER = "chenxf://router/";//请把chenxf换成你公司的名字,好看多了

    private Map<String, TypeElement> mRouterSchemes = new LinkedHashMap<>();
    private Map<String, TypeElement> mRegistryIds = new LinkedHashMap<>();

    private Filer mFiler;
    private Elements elementUtils;
    private String targetModuleName = "";
    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        elementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        Map<String, String> map = processingEnv.getOptions();
        Set<String> keys = map.keySet();
        for (String key : keys) {
            if ("targetModuleName".equals(key)) {
                this.targetModuleName = map.get(key);
            }
        }
    }

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

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            processRouterMap(roundEnv);
        } catch (ProcessingException e) {
            error(e.getElement(), e.getMessage());
        } catch (IOException e) {
            error(null, e.getMessage());
        }
        return true;
    }

    private void processRouterMap(RoundEnvironment roundEnv) throws IOException, ProcessingException {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(RouterMap.class);
        TypeSpec type = getRouterTableInitializer(elements);

        if (type != null) {
            JavaFile.builder("com.chenxf.router", type).build().writeTo(mFiler);
        } else {
            info("getRouterTableInitializer return type null!");
        }
    }

    //----------generate RouterTableInitializer----------
    private TypeSpec getRouterTableInitializer(Set<? extends Element> elements) throws ProcessingException {
        if (elements == null || elements.size() == 0) {
            return null;
        }

        MethodSpec.Builder initRouterBuilder = moduleInitRouterTableMethodBuilder();//准备创建initRouterTable方法,为了初始化 url -> activity对应关系
        MethodSpec.Builder initMappingBuilder = moduleInitMappingTableMethodBuilder();//准备创建initMappingTable方法,为了让一个数字,对应一个url
        for (Element element : elements) {

            if (element.getKind() != ElementKind.CLASS) {
                continue;
            }
            RouterMap router = element.getAnnotation(RouterMap.class);
            checkValidRouterMap((TypeElement) element, router);
            String routerUrl = router.value();
            String[] registryIds = router.registry();
            initRouterBuilder.addStatement("router.put($S, $T.class)", routerUrl, ClassName.get((TypeElement) element));
            for (String bizId : registryIds) {
                if (routerUrl.startsWith(DEFAULT_REGISTER)) {
                    initMappingBuilder.addStatement("mapping.put($S, $S)", bizId, routerUrl.replace(DEFAULT_REGISTER, ""));
                } else {
                    throw new ProcessingException(element, "scheme for class %s is invalid ! router scheme MUST starts with '%s'",
                            ((TypeElement) element).getQualifiedName(), DEFAULT_REGISTER);
                }
            }
        }
        //本模块不需要依赖IRouterTableInitializer,只需要使用的地方(如模块app,能依赖到IRouterTableInitializer,就可以检索到)
        TypeElement routerInitializerType = elementUtils.getTypeElement("com.chenxf.router.IRouterTableInitializer");

        TypeSpec result = TypeSpec.classBuilder("RouterTableInitializer" + targetModuleName)
                .addSuperinterface(ClassName.get(routerInitializerType))
                .addModifiers(Modifier.PUBLIC)
                .addMethod(initRouterBuilder.build())
                .addMethod(initMappingBuilder.build())
                .build();

        return result;
    }

    private void checkValidRouterMap(TypeElement element, RouterMap annotation) throws ProcessingException {
        String scheme = annotation.value();
        String[] ids = annotation.registry();
        if (mRouterSchemes.containsKey(scheme)) {
            throw new ProcessingException(element, "class %s annotated with scheme='%s' is conflict with class %s",
                    element.getQualifiedName(), scheme, mRouterSchemes.get(scheme).getQualifiedName());
        }
        mRouterSchemes.put(scheme, element);
        for (String id : ids) {
            if (mRegistryIds.containsKey(id)) {
                throw new ProcessingException(element, "class %s annotated with registry='%s' is conflict with class %s",
                        element.getQualifiedName(), id, mRegistryIds.get(id).getQualifiedName());
            }
            mRegistryIds.put(id, element);
        }
    }

    private MethodSpec.Builder moduleInitRouterTableMethodBuilder() {
        TypeElement activityType = elementUtils.getTypeElement("android.app.Activity");
        ParameterizedTypeName mapTypeName = ParameterizedTypeName
                .get(ClassName.get(Map.class), ClassName.get(String.class),
                        ParameterizedTypeName.get(ClassName.get(Class.class),
                                WildcardTypeName.subtypeOf(ClassName.get(activityType))));
        ParameterSpec mapParameterSpec = ParameterSpec.builder(mapTypeName, "router").build();
        return MethodSpec.methodBuilder("initRouterTable")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(mapParameterSpec);
    }

    private MethodSpec.Builder moduleInitMappingTableMethodBuilder() {
        TypeName paramType = ParameterizedTypeName.get(ClassName.get(Map.class),
                ClassName.get(String.class), ClassName.get(String.class));
        return MethodSpec.methodBuilder("initMappingTable")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(paramType, "mapping");
    }

    private void info(String msg, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
    }

    private void error(Element e, String msg, Object... args) {
        mMessager.printMessage(
                Diagnostic.Kind.ERROR,
                String.format(msg, args),
                e);
    }

}

5. 创建Router模块SimpleRouter

这个模块,处理根据url找到activity后,做实际跳转。关键类就一个,ActivityRouter

package com.chenxf.router;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.Map;

public class ActivityRouter {
    private static final String TAG = "ActivityRouter";

    /**
     * 是否初始化
     */
    private boolean isInit = false;

    /**
     * 全局Context
     */
    private Context mBaseContext;

    public static final String DEFAULT_SCHEME = "chenxf";
    /**
     * 单例
     */
    private static class ActivityRouterHolder {
        private static final ActivityRouter INSTANCE = new ActivityRouter();
    }

    public static ActivityRouter getInstance() {
        return ActivityRouterHolder.INSTANCE;
    }

    private ActivityRouter() {
    }

    /**
     * 初始化路由表,此方法适用于通过注解方式生成路由表
     *
     * @param context 全局Context
     */
    public void init(Context context) {
        if (context == null) {
            return;
        }
        if (!isInit) {
            mBaseContext = context.getApplicationContext();
            isInit = true;
        }
    }



    /**
     * 设置映射表
     */
    public void addMappingTable(Map<String, String> table) {
        if (table != null) {
            RouteControlCenter.getInstance().getMappingTable().putAll(table);
        }
    }

    @Deprecated
    public void addMapingTable(Map<String, String> table) {
        addMappingTable(table);
    }

    public Map<String, String> getMappingTable() {
        return RouteControlCenter.getInstance().getMappingTable();
    }

    public Map<String, Class<? extends Activity>> getRouteTable() {
        return RouteControlCenter.getInstance().getRouteTable();
    }

    public Context getContext() {
        return mBaseContext;
    }


    /**
     * 使用指定初始化接口初始化路由表
     *
     * @param routerInitializer
     */
    public void initActivityRouterTable(IRouterTableInitializer routerInitializer) {
        if (routerInitializer != null) {
            Map<String, Class<? extends Activity>> routeTable = getRouteTable();
            routerInitializer.initRouterTable(routeTable);
        }
    }


    /**
     * 通过{@link QYIntent}启动Activity
     *
     * @param fromContext 当前Activity,如果需要从Context跳转,则传入相应的context或者置Null
     * @param qyIntent    qyIntent类似系统Intent,用来选择需要跳转的Activity
     */
    public void start(Context fromContext, QYIntent qyIntent) {
        start(fromContext, qyIntent, null, null);
    }

    /**
     * 通过{@link QYIntent}启动Activity,并设置跳转回调
     *
     * @param fromContext   当前Activity,如果需要从Context跳转,则传入相应的context或者置Null
     * @param qyIntent      qyIntent类似系统Intent,用来选择需要跳转的Activity
     * @param routeCallBack 跳转回调
     */
    public void start(Context fromContext, QYIntent qyIntent, @Nullable IRouteCallBack routeCallBack) {
        start(fromContext, qyIntent, routeCallBack, null);
    }

    public void start(Context fromContext, QYIntent qyIntent, @Nullable IRouteCallBack routeCallBack, @Nullable Bundle options) {
//        RouterLazyInitializer.init();
        if (qyIntent == null) {
            Log.e(TAG, "start failed, qyIntent is null !");
            if (routeCallBack != null) {
                routeCallBack.error(mBaseContext, "", new NullPointerException("qyIntent is null"));
            }
            return;
        }

        try {
            Activity fromActivity = null;
            if (fromContext instanceof Activity) {
                fromActivity = (Activity) fromContext;
            }
            enterActivity(fromActivity, qyIntent, routeCallBack, options);
        } catch (Exception e) {
            Log.e(TAG, "start error, exception=", e);
            if (routeCallBack != null) {
                routeCallBack.error(mBaseContext, qyIntent.getUrl(), e);
            }
        }
    }


    /**
     * 对应startActivity
     */
    private void enterActivity(Activity fromActivity, QYIntent qyIntent,
                               @Nullable IRouteCallBack routeCallBack, @Nullable Bundle options) {
        //QYIntent转换为Intent
        Intent intent = RouteControlCenter.getInstance().queryIntent(mBaseContext, qyIntent);
        if (intent == null) {
            Log.e(TAG, "Route Not Found ! " + qyIntent.getUrl());
            if (routeCallBack != null) {
                routeCallBack.notFound(mBaseContext, qyIntent.getUrl());
            }
            return;
        }
        if (routeCallBack != null) {
            routeCallBack.beforeOpen(mBaseContext, qyIntent.getUrl());
        }
        if (fromActivity == null) {
            Log.d(TAG, "startActivity  fromActivity is null !");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | qyIntent.getFlags());
            startActivity(mBaseContext, intent, options);
        } else {
            Log.d(TAG, "startActivity  fromActivity is not null !");
            intent.setFlags(qyIntent.getFlags());
            startActivity(fromActivity, intent, options);
        }
        Log.d(TAG, "startActivity success ! "+ qyIntent.getUrl());
        if (routeCallBack != null) {
            routeCallBack.afterOpen(mBaseContext, qyIntent.getUrl());
        }
    }

    private void startActivity(@NonNull Context context, Intent intent, @Nullable Bundle options) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            context.startActivity(intent, options);
        } else {
            context.startActivity(intent);
        }
    }

}

6. 业务模块app

首先,app模块,要依赖以上3个模块。

6.1 application初始化的地方,引用生成的类,初始化ActivityRouter

package com.chenxf.app;

import android.app.Application;
import android.content.Context;

import com.chenxf.router.ActivityRouter;
import com.chenxf.router.RouterTableInitializerapp;

public class MyApplication extends Application {
    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        //初始化列表
        ActivityRouter.getInstance().init(this);
        ActivityRouter.getInstance().initActivityRouterTable(new RouterTableInitializerapp());//RouterTableInitializerapp是编译期间生成
    }
}

6.2 业务方,调用ActivityRouter做跳转

package com.chenxf.app;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.chenxf.router.ActivityRouter;
import com.chenxf.router.QYIntent;
import com.chenxf.simplebutterknife.BindView;
import com.chenxf.simplebutterknife.ButterKnife;
import com.example.annotationtest.R;

public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
    @BindView(R.id.router_btn)
    Button mRouterBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        ButterKnife.bind(this);
        mRouterBtn.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.router_btn) {
            QYIntent intent = new QYIntent("chenxf://router/test");
            ActivityRouter.getInstance().start(this, intent);
        }
    }

}

6.3 RouterTableInitializerapp 的结果

刚写代码时,RouterTableInitializerapp是找不到的,但是执行编译后,会生成这个类,就可以找到了。
生成结果如下:

package com.chenxf.router;

import android.app.Activity;
import java.lang.Class;
import java.lang.Override;
import java.lang.String;
import java.util.Map;

public class RouterTableInitializerapp implements IRouterTableInitializer {
  @Override
  public void initRouterTable(Map<String, Class<? extends Activity>> router) {
    router.put("chenxf://router/test", RouterTestActivity.class);
  }

  @Override
  public void initMappingTable(Map<String, String> mapping) {
    mapping.put("10_1", "test");
  }
}

7. 模块间关系图

Android注解、反射及其应用--自动生成类-精简版ARouter_第1张图片

8. 源码

https://github.com/newchenxf/AnnotationTest

9. 参考文献

https://www.jianshu.com/p/1b261d7b0834

你可能感兴趣的:(android应用)