EasyAnnotation框架界面跳转注解(一)

578 阅读7分钟

如何成为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、解析注解信息

  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;
     }
    
  2. 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、生成样板代码

  1. 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);
    }
    
  2. 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);
    
        }
    }
    
  3. 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、测试代码

  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);
        }
    }
    
  2. 获取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