注解和反射的原理,见https://blog.csdn.net/newchenxf/article/details/90231512,本文主要讲如何自动生成类。
这算是一个高端用法,挺有意思。
首先,要自动生成类,就要用到APT,Annotation-Processing-tool的简写,称为注解处理器。
注解处理器在编译期被调用,可以扫描特定注解的信息,你可以为你自己的的注解注册处理器,一个特定的注解处理器以java源码作为输入,然后生成一些文件作(通常为java)为输出,这些java文件同样会被编译。这意味着,你可以根据注解的信息和被注解类的信息生成你想生成的代码!
利用APT,生成一个string 和 activity的对应关系的类,然后跳转页面,使用string做跳转。这其实就是传说中的ARouter开源代码。只不过,咱这里精简化。
虽然只有一个注解类,但为了解耦,单独放在一个模块,这个模块,是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 {};
}
也是一个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);
}
}
这个模块,处理根据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);
}
}
}
首先,app模块,要依赖以上3个模块。
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是编译期间生成
}
}
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);
}
}
}
刚写代码时,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");
}
}
https://github.com/newchenxf/AnnotationTest
https://www.jianshu.com/p/1b261d7b0834