如何成为T型人才,垂直在一个行业中,必须要有一整套知识体系,在这里,就一个字坚持~
前言
Android中提供了四大组件(Activity/Service/BroadCast Recevicer/Content provider),组件之间存在着交互。在Activity组件中,可以使用startActivityz启动一个Activity视图,并且数据通过Bundle序列化传递给视图,那如何通过APT技术实现Activity视图跳转?接下来直接代码编码,注解理论知识,请移步到 细聊注解知识点。谢谢~
Contents
一、定义界面跳转注解
-
@IntentField:该注解主要修饰成员变量,标记Activity数据交互变量类型。@Retention(RetentionPolicy.CLASS):表示编译时注解,@Target(ElementType.FIELD):表示修饰成员变量,注解参数定义为String类型value值,表示目标Activiy类名。
@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface IntentField { /** * 目标对象标识,如{@code MainActivity} * * @return */ String value() default ""; }
-
@IntentParameter:该注解主要修饰方法形式参数,标记Activity数据交互变量类型。@Retention(RetentionPolicy.CLASS):表示编译时注解,
@Target(ElementType.PARAMETER):表示修饰方法形式参数,注解参数定义为String类型value值,表示目标Activiy类名。
@Retention(RetentionPolicy.CLASS) @Target(ElementType.PARAMETER) public @interface IntentParameter { /** * 目标对象标识,如{@code MainActivity} * * @return */ String value() default ""; }
-
@IntentMethod:该注解主要修饰方法,根据标识获取方法参数用于Activty交互。@Retention(RetentionPolicy.CLASS):表示编译时注解,@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}):表示修饰方法,构造函数,注解参数定义为String类型value值,表示目标Activiy类名。
@Retention(RetentionPolicy.CLASS) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface IntentMethod { /** * 目标对象标识,如{@code MainActivity} * * @return */ String value() default ""; }
二、自定义注解处理器
不同注解类型,对应不同注解处理器,注解处理器主要作用就是注解解析。在定义编译时注解,我们需要通过继承AbstractProcessor这个类来创建注解处理器,并重写父类方法,一般只需要重新几个重要方法分别:
1、注解初始化
public synchronized void init(ProcessingEnvironment processingEnvironment)
2、注解解析
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
3、注解版本
public SourceVersion getSupportedSourceVersion()
4、解析类型
public Set<String> getSupportedAnnotationTypes()
2.1、注解处理器初始化
AbstractProcessor注解处理器每次初始化只会调用一次init(ProcessingEnvironment processingEnvironment)方法,所以可以用来初始化工具类。
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//注解处理器工具类
mProcessorUtils = ProcessorUtils.getInstance(processingEnv);
//解析注解信息类
mJumpIntentGroupedClasses = new JumpIntentGroupedClasses();
//生产模板对象
initGenerated();
}
/**
* 初始化Generated
*/
private void initGenerated() {
mGenerators = new LinkedList<>();
mGenerators.add(new ActivityEnterGenerator());
mGenerators.add(new ActivityOutGenerator());
}
2.2、绑定注解到注解处理器
getSupportedAnnotationTypes()方法是注解处理器能够识别注解类型。这里把注解@ObjectFactory存储到LinkedHashSet数据结构中,并返回集合,标识注解处理器识别目标注解对象。
@Override
public Set<String> getSupportedAnnotationTypes() {
LinkedHashSet<String> types = new LinkedHashSet<>(2);
types.add(IntentField.class.getCanonicalName());
types.add(IntentMethod.class.getCanonicalName());
return types;
}
2.3、注解解析处理
RoundEnvironment这个类中提供#getElementsAnnotatedWith()方法,可以根据注解类型返回该类型注解所有信息,通过Element可以获取注解信息。首先,获取@IntentMethod、@IntentField注解信息,把注解信息交给JumpIntentGroupedClasses#loanAnnotation(),进行注解信息解析封装成一个Map<String,Element> 数据结构,通过循环Map调用不同功能generator()方法,生成样板代码。这里需要定义两种模板代码,ActivityEnterGenerator.java:跳转起始模板生成,ActivityOutGenerator.java:跳转终点模板生成。process()方法生成的数据,在最后需要回收资源,避免循环调用。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取IntentMethod跳转方法所有注解信息
Set<? extends Element> methodElement = roundEnv.getElementsAnnotatedWith(IntentMethod.class);
//获取IntentField跳转成员变量所有注解信息
Set<? extends Element> fieldElement = roundEnv.getElementsAnnotatedWith(IntentField.class);
//解析注解信息
mJumpIntentGroupedClasses.loanAnnotation(fieldElement,methodElement);
Map<String, ExecutableElement> executableElementLists = mJumpIntentGroupedClasses.getExecutableElementLists();
Map<String, List<VariableElement>> variableElementLists = mJumpIntentGroupedClasses.getVariableElementLists();
//根据获取注解信息,进行代码文件生成
ExecutableElement executableElement;
for (Map.Entry<String, List<VariableElement>> entry : variableElementLists.entrySet()) {
if (!mProcessorUtils.isNotEmpty(entry.getKey())) continue;
executableElement = executableElementLists.get(entry.getKey());
for (ActivityGenerator generator : mGenerators) {
generator.generator(entry.getKey(), entry.getValue(),
executableElement, mProcessorUtils, processingEnv);
}
}
//process()多次调用处理,所以配置信息及时释放
variableElementLists.clear();
executableElementLists.clear();
return true;
}
三、解析注解信息,生成样板代码
3.1、解析注解信息
-
loanParameterAnnotation():方法解析@IntentParameter注解信息,通过获取注解value值,跟methodValue字段比较并存储到相应组里。methodValue表示目标Activity类名,parameter表示传递给目标Activity参数。
/** * 获取对应标识方法参数注解信息,并进行判断 * * @param executableElement * @param methodValue */ Map<String, List<VariableElement>> (Map<String, List<VariableElement>> variableElementLists, ExecutableElement executableElement, String methodValue) { if (executableElement != null) { List<? extends VariableElement> parameterElement = executableElement.getParameters(); for (Element parameter : parameterElement) { IntentParameter intentParameter = parameter.getAnnotation(IntentParameter.class); if (intentParameter != null) { String parameterValue = intentParameter.value(); if ("".equals(parameterValue)) continue; //方法同一标识判断 if (parameterValue.equals(methodValue)) { //IntentField是应用在一般成员变量上的注解 variableElementLists.get(methodValue).add((VariableElement) parameter); } } } } return variableElementLists; }
-
loadFieldAnnotation():方法解析@IntentField注解信息,根据注解信息value确定传递目标对象,并根据methodValue标识存储参数。
/** * 获取对应标识中成员变量注解信息,并进行判断 * * @param fieldElement * @param methodValue */ Map<String, List<VariableElement>> loadFieldAnnotation(Map<String, List<VariableElement>> variableElementLists, Set<? extends Element> fieldElement, String methodValue) { if (fieldElement != null && fieldElement.size() > 0) { VariableElement variableElement; for (Element field : fieldElement) { ElementKind fieldKind = field.getKind(); if (fieldKind == ElementKind.FIELD) { //判断Elements类型,并获取Filed参数值 variableElement = (VariableElement) field; String filedValue = variableElement.getAnnotation(IntentField.class).value(); if ("".equals(filedValue)) continue; //方法同一标识判断 if (filedValue.equals(methodValue)) { //IntentField是应用在一般成员变量上的注解 variableElementLists.get(methodValue).add((VariableElement) field); } } } } return variableElementLists; }
3.2、封装注解信息
JumpIntentGroupedClasses.java中对解析注解信息进行封装控制流程。mVariableElementLists中存储了所有不同methodValue类型VariableElement注解信息,mExecutableElementLists中存储了所有不同methodValue类型ExecutableElement注解信息。这里VariableElement代表@IntentField、@IntentParameter注解转换为Element子类,ExecutableElement代表@IntentMethod注解转换为Element子类。首先获取@IntentMethod修饰方法注解value值,根据value来区分注解信息。在解析过程中,value标识对应@IntentField解析数据为空,会切换解析@IntentParameter注解,所有数据解决完成后,全都存储到数据结构Map中,并通过方法暴露出去,在生成样本代码时方便调用。
public class JumpIntentGroupedClasses {
private JumpIntentClasses mJumpIntentClasses;
//保存指定类型,所有VariableElement数据
private Map<String, List<VariableElement>> mVariableElementLists = new HashMap<>();
//保存指定类型,对应ExecutableElement数据
private Map<String, ExecutableElement> mExecutableElementLists = new HashMap<>();
public JumpIntentGroupedClasses(){
mJumpIntentClasses=new JumpIntentClasses();
}
/**
* 获取ExecutableElement集合数据
* @return
*/
public Map<String,ExecutableElement> getExecutableElementLists(){
return mExecutableElementLists;
}
/**
* 获取VariableElement集合数据
* @return
*/
public Map<String,List<VariableElement>> getVariableElementLists(){
return mVariableElementLists;
}
/**
* 获取注解信息
* @param fieldElement
* @param methodElement
*/
public void loanAnnotation(Set<? extends Element> fieldElement, Set<? extends Element> methodElement){
ExecutableElement executableElement = null;
String methodValue = "";
for (Element method : methodElement) {
ElementKind methodKind = method.getKind();
if (methodKind == ElementKind.METHOD) {
//判断Elements类型,并获取Method参数值
executableElement = (ExecutableElement) method;
methodValue = executableElement.getAnnotation(IntentMethod.class).value();
}
//主要获取ElementType 是不是null,即class,interface,enum或者注解类型
Element typeElement = method.getEnclosingElement();
//判断方法参数,classFlag跳转标识判断
if (!"".equals(methodValue)) {
//如果mVariableElementLists的key不存在,则添加一个key
if (mVariableElementLists.get(methodValue) == null) {
mVariableElementLists.put(methodValue, new LinkedList<VariableElement>());
}
mExecutableElementLists.put(methodValue, executableElement);
//获取对应标识中成员变量注解信息,并进行判断
mVariableElementLists=mJumpIntentClasses.loadFieldAnnotation(mVariableElementLists,fieldElement, methodValue);
if (mVariableElementLists.get(methodValue).size() <= 0) {
//获取对应标识方法参数注解信息,并进行判断
mVariableElementLists= mJumpIntentClasses.loanParameterAnnotation(mVariableElementLists,executableElement, methodValue);
}
}
}
}
}
3.3、生成样板代码
-
ActivityGenerator.java:生成样板代码接口,clazzType标识注解修饰目标对象类名。
/** * @Author: WeiShuai * @Time: 2020/4/30 * @Description: Activity生成模板代码接口 */ public interface ActivityGenerator { /** * 根据注解配置信息,{{@link VariableElement}}生成“样板”代码 * * @param clazzType * @param variableElements * @param processingEnv */ void generator(String clazzType, List<VariableElement> variableElements, ExecutableElement executableElement, ProcessorUtils processorUtils, ProcessingEnvironment processingEnv); }
-
ActivityEnterGenerator.java:表示Activity跳转起始模板生成类,主要构建一个启动Activity视图逻辑代码,通过获取Element信息,来确定传递参数类型。
/** * @Author: WeiShuai * @Time: 2020/4/30 * @Description: Activity跳转起始模板生成 */ public class ActivityEnterGenerator implements ActivityGenerator { /** * @param clazzType 类 * @param variableElements 字段 * @param processorUtils 工具 * @param processingEnv */ @Override public void generator(String clazzType, List<VariableElement> variableElements, ExecutableElement executableElement,, ProcessorUtils processorUtils, ProcessingEnvironment processingEnv) { MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_JUMP_ACTIVITY) .addModifiers(Modifier.PUBLIC) .returns(void.class); methodBuilder.addParameter(Object.class, "context"); methodBuilder.addStatement("android.content.Intent intent = new android.content.Intent()"); for (VariableElement element : variableElements) { //Element 只是一种语言元素,本身并不包含信息,所以我们这里获取TypeMirror TypeMirror typeMirror = element.asType(); //获取注解字段变量类型 TypeName typeName = TypeName.get(typeMirror); //获取注解字段变量名称 String fieldName = element.getSimpleName().toString(); methodBuilder.addParameter(typeName, fieldName); methodBuilder.addStatement("intent.putExtra(\"" + fieldName + "\"," + fieldName + ")"); } if (!processorUtils.isNotEmpty(clazzType)) { processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "IntentClass注解定义不明确,无法进行界面跳转!" ); } methodBuilder.addStatement("intent.setClass((android.content.Context)context, " + clazzType + ".class)"); methodBuilder.addStatement("((android.content.Context)context).startActivity(intent)"); TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement(); processorUtils.writeToFile(clazzType + Constant.JUMP_SUFFIX, processorUtils.getPackageName(typeElement), methodBuilder.build(), processingEnv, null); } }
-
ActivityOutGenerator.java:表示Activity跳转终点模板生成类,主要构建一个解析Activity传递参数逻辑代码,通过获取Element信息,来确定传递参数类型。
/** * @Author: WS * @Time: 2020/5/4 * @Description: Activity跳转终点模板生成 */ public class ActivityOutGenerator implements ActivityGenerator { @Override public void generator(String clazzType, List<VariableElement> variableElements, ExecutableElement executableElement, ProcessorUtils processorUtils, ProcessingEnvironment processingEnv) { //存储成员变量信息 ArrayList<FieldSpec> listField = new ArrayList<>(); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_INIT_ACTIVITY) .addModifiers(Modifier.PUBLIC) .returns(Object.class); if (!processorUtils.isNotEmpty(clazzType)) { processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "IntentClass注解定义不明确,无法进行界面跳转!" ); } methodBuilder.addParameter(Object.class, "activity"); methodBuilder.addStatement(clazzType + " nextActivity = (" + clazzType + ") activity"); methodBuilder.addStatement("android.content.Intent intent=nextActivity.getIntent()"); for (VariableElement element : variableElements) { //Element 只是一种语言元素,本身并不包含信息,所以我们这里获取TypeMirror TypeMirror typeMirror = element.asType(); //获取注解字段变量类型 TypeName typeName = TypeName.get(typeMirror); //获取注解字段变量名称 String fieldName = element.getSimpleName().toString(); //注解类型转换为Intent传输类型 String intentTypeName = processorUtils.getIntentTypeName(typeName.toString()); //创建成员变量 FieldSpec fieldSpec = FieldSpec.builder(typeName, fieldName) .addModifiers(Modifier.PUBLIC) .build(); listField.add(fieldSpec); if (processorUtils.isElementNoDefaultValue(typeName.toString())) { methodBuilder.addStatement("this." + fieldName + "=intent.get" + intentTypeName + "Extra(\"" + fieldName + "\")"); } else { if (intentTypeName == null) { processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "the type:" + element.asType().toString() + " is not support" ); } else { String defaultValue = "default" + fieldName; if ("".equals(intentTypeName)) { //序列化数据获取 methodBuilder.addStatement("this." + fieldName + "=(" + typeName + ")intent.getSerializableExtra(\"" + fieldName + "\")"); } else { methodBuilder.addParameter(typeName, defaultValue); methodBuilder.addStatement("this." + fieldName + "= intent.get" + intentTypeName + "Extra(\"" + fieldName + "\", " + defaultValue + ")"); } } } } methodBuilder.addStatement("return this"); TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement(); processorUtils.writeToFile(clazzType + Constant.INIT_SUFFIX, processorUtils.getPackageName(typeElement), methodBuilder.build(), processingEnv, listField); } }
四、功能点测试
4.1、测试代码
-
定义启动Activity代码,参数类型。
public class OneActivity extends AppCompatActivity { //@IntentField("MainActivity") String name = "Activity"; //@IntentField("MainActivity") int count = 0; @IntentField("TwoActivity") String title = "OneActivity"; @IntentField("TwoActivity") UserInfo userInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_one); } public void click(View view) { int id = view.getId(); switch (id) { case R.id.mBtOneMain: jumpOneToMainActivity(name, count, "title"); break; case R.id.mBtOneTwo: jumpOneToTwoActivity(); break; } } @IntentMethod("MainActivity") public void jumpOneToMainActivity(@IntentParameter("MainActivity") String name, @IntentParameter("MainActivity") int count, String title) { new MainActivity$Jump().jumpActivity(OneActivity.this, name, count); } @IntentMethod("TwoActivity") public void jumpOneToTwoActivity() { userInfo = new UserInfo("username", "password"); new TwoActivity$Jump().jumpActivity(OneActivity.this, title, userInfo); } }
-
获取Activity传递参数信息。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); } /** * 获取数据 */ private void initData() { MainActivity$Init activity = (MainActivity$Init) new MainActivity$Init().initActivity(this, 0); int count = activity.count; String name = activity.name; Toast.makeText(this, "count=" + count + ";name=" + name, Toast.LENGTH_SHORT).show(); } } public class TwoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_two); initData(); } /** * 获取数据 */ private void initData() { TwoActivity$Init activity = (TwoActivity$Init) new TwoActivity$Init().initActivity(this); String title = activity.title; UserInfo userInfo = activity.userInfo; Toast.makeText(this, "title=" + title + ";username=" + userInfo.username + ";password" + userInfo.password, Toast.LENGTH_LONG).show(); } }
4.2、样板代码
public final class MainActivity$Init {
public String name;
public int count;
public Object initActivity(Object activity, int defaultcount) {
MainActivity nextActivity = (MainActivity) activity;
android.content.Intent intent=nextActivity.getIntent();
this.name=intent.getStringExtra("name");
this.count= intent.getIntExtra("count", defaultcount);
return this;
}
}
public final class MainActivity$Jump {
public void jumpActivity(Object context, String name, int count) {
android.content.Intent intent = new android.content.Intent();
intent.putExtra("name",name);
intent.putExtra("count",count);
intent.setClass((android.content.Context)context, MainActivity.class);
((android.content.Context)context).startActivity(intent);
}
}
public final class TwoActivity$Init {
public String title;
public UserInfo userInfo;
public Object initActivity(Object activity) {
TwoActivity nextActivity = (TwoActivity) activity;
android.content.Intent intent=nextActivity.getIntent();
this.title=intent.getStringExtra("title");
this.userInfo=(com.linwei.annotation.bean.UserInfo)intent.getSerializableExtra("userInfo");
return this;
}
}
public final class TwoActivity$Jump {
public void jumpActivity(Object context, String title, UserInfo userInfo) {
android.content.Intent intent = new android.content.Intent();
intent.putExtra("title",title);
intent.putExtra("userInfo",userInfo);
intent.setClass((android.content.Context)context, TwoActivity.class);
((android.content.Context)context).startActivity(intent);
}
}
About me
-
Email:linwei9605@gmail.com
-
Blog: offer.github.io/
-
Github: github.com/WeiShuaiDev