• Android的View绑定实现----编译时注解实现findViewById和setOnClickListener方式


    标题有点长,相信用过xUtils和ButterKnife框架的都知道啥意思,他们都可以通过注解的方式省去繁琐的findViewById和setOnClickListener代码的编写。他们2者的实现原理不一样,前者用的是运行时注解,后者用的是编译时注解,对于不同的注解,会有不同的注解处理器, 针对运行时注解会采用反射机制来处理,针对编译时注解会采用AbstractProcessor来处理,相对来说后者的性能会比前者好点,今天就来介绍下编译时注解的解释器该如何编写。

    先来看看注解的使用

    public class MainActivity extends AppCompatActivity {

        @InjectView(R.id.btn_title)
        TextView mTitleBtn;

        @InjectView(R.id.btn_desc)
        TextView mDescBtn;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            //注入
            ViewInjector.inject(this);

            //注入后就可以使用了
            mTitleBtn.setText("我是标题");
            mDescBtn.setText("我是描述");


        }
        
        //设置点击事件
        @InjectMethod({R.id.btn_title, R.id.btn_desc})
        public void showToast(View v) {
            switch (v.getId()) {
                case R.id.btn_title:
                    Toast.makeText(this, "标题被点击了", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.btn_desc:
                    Toast.makeText(this, "描述被点击了", Toast.LENGTH_SHORT).show();
                    break;
            }
        }

    }


    编译工程后,会在项目的\build\generated\ap_generated_sources\debug\out目录下自动生成一份java代码,效果如下所示:

    // Generated code from ViewInjector. Do not modify!
    package blog.csdn.net.mchenys.essayjoke;
    import mchenys.ViewInjector.ViewBinder;
    import blog.csdn.net.mchenys.essayjoke.MainActivity;
    import android.view.View;
    public class MainActivityInjector implements ViewBinder {
        @Override
        public void bind(MainActivity args){
            args.mTitleBtn=(android.widget.TextView)args.findViewById(2131165219);
            args.mDescBtn=(android.widget.TextView)args.findViewById(2131165218);
            args.mTitleBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    args.showToast(v);
                }
            });
            args.mDescBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    args.showToast(v);
                }
            });
        }
    }

    效果图:

    先从注解开始介绍,@InjectView和@InjectMethod定义如下:

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.CLASS)
    public @interface InjectView {
        int value();
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface InjectMethod {
        int[] value();
    }

    非常简单,@Target用于声明作用的范围,ElementType.FIELD表示只能用在成员变量上,ElementType.METHOD表示只能用在方法上,除了这里用到的2个,它还可以有以下类型:

    ElementType.TYPE:只能用在类、接口或者枚举类型。
    ElementType.PARAMETER:只能用在参数上。
    ElementType.CONSTRUCTOR:只能用在构造方法上。
    ElementType.LOCAL_VARIABLE:只能用在局部变量上。
    ElementType.ANNOTATION_TYPE:只能用在注解上。
    ElementType.PACKAGE:只能用在包上。
    ElementType.TYPE_PARAMETER:只能用在类型参数上。
    而@Retention表示注解的保留策略,它一共有3种:

    RetentionPolicy.SOURCE:源码级注解,只会把注解信息保留在.java文件中,源码在编译时会被丢弃。
    RetentionPolicy.CLASS:编译时注解,默认级别,注解信息会保留在.java和.class文件中,当运行java程序时,JVM会丢弃该注解信息。
    RetentionPolicy.RUNTIME:运行时注解,当运行java程序时,JVM会保留改注解信息,可以通过反射获取该注解信息。
    我这里用的就是编译时注解,下面开始介绍编译时注解处理器的开发步骤。

    首先,你得新建一个java Library,注意是一定是java Library而不是android Library,否则会找不到AbstractProcessor类,我这里建的库的名字就叫processor,工程项目如下所示:

    其中processor包内的ViewBindProcessor就是注解处理器类,它是继承至AbstractProcessor类的,继承后需要实现process抽象方法:

    public abstract boolean process(Set annotations,
                                        RoundEnvironment roundEnv);

    该方法相当于每个处理器的主函数,在这里可以编写处理注解的代码,以及生成java文件,参数annotations表示需要给该处理器解释的注解的集合,roundEnv参数可以用来查找使用了特定注解的元素。

    除此之外,还有3个方法会用到,分别是:

    public synchronized void init(ProcessingEnvironment processingEnv) {}

    public Set getSupportedAnnotationTypes() {}

    public SourceVersion getSupportedSourceVersion() {}   

    init:该方法是初始化的地方,我们可以通过ProcessingEnvironment获取到很多有用的工具类,比如Elements(提供了一些和元素相关的操作,如获取所在包的包名等)、Types(提供了和类型相关的一些操作,如获取父类、判断两个类是不是父子关系等)、Filer(用于文件操作,用它去创建生成的代码文件)、Messager(用来打印信息的,它会打印出Element所在的源代码)等等。
    getSupportedAnnotationTypes:该方法必须指定,它是指定哪些注解需要被该处理器解释的,需要将要处理的注解的全名放到Set中返回。
    getSupportedSourceVersion:这个方法用来指定支持的java版本,通常这里返回SourceVersion。latestSupported().
    在java7之后,也可以使用注解来代替getSupportedAnnotationTypes和getSupportedSourceVersion方法。例如:

    @SupportedAnnotationTypes({"mchenys.annotations.InjectView",
            "mchenys.annotations.InjectMethod"})//指定这个注解处理器是给哪些注
    @SupportedSourceVersion(SourceVersion.RELEASE_7)//设置java版本
    public class ViewBindProcessor extends AbstractProcessor {
    }

    如何注册Processor
    编写完我们的Processor之后需要将它注册给java编译器,为了能使用注解处理器,需要用一个服务文件来注册它,关于服务文件的创建有2种方式,先来介绍比较麻烦的一种,步骤如下:
    1.首先,在processor库的src/main目录下创建resources/META-INF/services/javax.annotation.processing.Processor文件(即创建resources目录,在resources目录下创建META-INF目录,继续在META-INF目录下创建services目录,最后在services目录下创建javax.annotation.processing.Processor文件)

    2.然后编辑javax.annotation.processing.Processor文件,写入自定义的Processor的全路径名称,如果有多个Processor的话,每一行写一个。
    例如:mchenys.processor.ViewBindProcessor

    如果你觉得这种方式太过繁琐,别急,我马上介绍一种比较简便的方式,使用Google开源的AutoService库来生成服务文件,这个需要在processor库的gradle文件中添加2行依赖:

    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    //gradle 5.0会忽略annotationProcessor,因此需要手动添加
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

    然后在自定义的注解处理器类上添加@AutoService(Processor.class)注解,这样就搞定了。编译processor库,你会发现在\build\classes目录下自动生成了服务文件:


    ViewInjector工具类介绍
    在正式介绍ViewBindProcessor之前,我先介绍下这个工具类,代码如下:

    /**
     * 注解注入工具类
     */
    public class ViewInjector {

        //定义注解生成类实现的接口
        public interface ViewBinder {
            void bind(T t);
        }

        //存储已实例化过的注解生成类
        static final Map BINDERS = new LinkedHashMap<>();

        //给注解生成类注入Activity
        public static void inject(Object target) {
            Class clazz = target.getClass();
            ViewBinder viewBinder = BINDERS.get(clazz);
            if (viewBinder == null) {
                try {
                    viewBinder = (ViewBinder) Class.forName(clazz.getName() + ViewBindProcessor.GEN_CLASS_SUFFIX)
                            .newInstance();
                    BINDERS.put(clazz, viewBinder);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (null != viewBinder) {
                viewBinder.bind(target); //执行绑定
            }

        }
    }


    代码也很简单,我的思路就是在调用inject的方法时候给说有实现了ViewBinder接口的类注入Activity的实例,拿到了Activity的实例,我就可以操作findViewById得到对应的View了,进而又可以给View设置点击监听了,至于ViewBinder接口的实现类从哪里来?当然是通过注解处理器动态生成啦,因此ViewBindProcessor的作用就是找到使用了指定注解类的元素,然后生成对应的代码。

    ViewBindProcessor
    直接贴代码,看注释也能看懂啥意思了

    /**
     * 自定义编译时注解解释器
     */
    @AutoService(Processor.class) //自动的注册解释器
    @SupportedAnnotationTypes({"mchenys.annotations.InjectView",
            "mchenys.annotations.InjectMethod"})//指定这个注解处理器是给哪些注解使用的.
    @SupportedSourceVersion(SourceVersion.RELEASE_7)//设置java版本
    public class ViewBindProcessor extends AbstractProcessor {

        public static final String GEN_CLASS_SUFFIX = "Injector";

        private Types mTypeUtils;//提供了和类型相关的一些操作,如获取父类、判断两个类是不是父子关系等
        private Elements mElementUtils;//提供了一些和元素相关的操作,如获取所在包的包名等
        private Filer mFiler;//Filer用于文件操作,用它去创建生成的代码文件
        private Messager mMessager;//用来打印信息的,它会打印出Element所在的源代码

        //init方法是初始化的地方,我们可以通过ProcessingEnvironment获取到很多有用的工具类
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            mTypeUtils = processingEnv.getTypeUtils();
            mElementUtils = processingEnv.getElementUtils();
            mFiler = processingEnv.getFiler();
            mMessager = processingEnv.getMessager();

        }

        //该方法相当于处理器的main函数,在这里写你的扫描/评估和处理逻辑,以及生成java代码,RoundEnvironment可以查询包含
        //特定注解的被注解元素
        @Override
        public boolean process(Set annotations, RoundEnvironment roundEnv) {


            //获取与InjectView注解相关的所有元素
            Set elements = parse2Set(annotations, roundEnv);

            //获取分类后的集合
            Map> elementMap = parse2Map(elements);

            //遍历map,生成代码
            for (Map.Entry> entry : elementMap.entrySet()) {
                //生成注入代码
                generateInjectorCode(entry.getKey(), entry.getValue());
            }
            return true;
        }

        /**
         * 整合所有使用了注解的元素
         *
         * @param annotations
         * @param roundEnv
         * @return
         */
        private Set parse2Set(Set annotations, RoundEnvironment roundEnv) {
            Set set = new LinkedHashSet<>();
           /* for (TypeElement annotation : annotations) {
                Set elements = roundEnv.getElementsAnnotatedWith(annotation);
                set.addAll(elements);
            }*/
            //为保证字段先解析,这里手动添加
            set.addAll(roundEnv.getElementsAnnotatedWith(InjectView.class));
            set.addAll(roundEnv.getElementsAnnotatedWith(InjectMethod.class));
            return set;
        }

        /**
         * 将元素按TypeElement进行分类
         *
         * @param elements
         * @return
         */
        private Map> parse2Map(Set elements) {

            Map> elementMap = new LinkedHashMap<>();

            //遍历所有被InjectView注释的元素
            for (Element element : elements) {
                //获取所在类的信息(一般指的是Activity/Fragment)
                TypeElement clazz = (TypeElement) element.getEnclosingElement();
                //按类存入map中,key相同会被覆盖,key代表的是元素所在的类信息,value是一个List
                //这样就可以把某个类和该类上使用了特定注解所有元素进行关联
                addElement(elementMap, clazz, element);
            }
            return elementMap;
        }


        //递归判断android.view.View是不是其父类
        private boolean isView(TypeMirror type) {
            //获取所有父类类型
            List supers = mTypeUtils.directSupertypes(type);
            if (supers.size() == 0) {
                return false;
            }
            for (TypeMirror superType : supers) {
                if (superType.toString().equals("android.view.View") || isView(superType)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 添加元素到map集合中
         *
         * @param elementMap key=clazz value=List
         * @param clazz      指定元素所在类的信息
         * @param element    指定元素
         */
        private void addElement(Map> elementMap,
                                TypeElement clazz, Element element) {
            List list = elementMap.get(clazz);
            if (list == null) {
                list = new ArrayList<>();
                elementMap.put(clazz, list);
            }
            list.add(element);
        }

        /**
         * 生成注入代码
         *
         * @param typeElement 元素所在类的Element
         * @param elements    需要注入的元素
         */
        private void generateInjectorCode(TypeElement typeElement, List elements) {

            //取出所在的类名
            String className = typeElement.getSimpleName().toString();
            String qualifiedName = typeElement.getQualifiedName().toString();
            //该类所在的包名
            String packageName = mElementUtils.getPackageOf(typeElement).asType().toString();
            //存储所有的字段名
            Map fieldMap = new HashMap<>();

            //开始编写java类
            StringBuilder builder = new StringBuilder();
            builder.append("// Generated code from ViewInjector. Do not modify!\n");
            builder.append("package " + packageName + ";\n"); //声明包
            builder.append("import mchenys.ViewInjector.ViewBinder;\n");//导包
            builder.append("import " + qualifiedName + ";\n");//导包
            builder.append("import android.view.View;\n");//导包
            builder.append("public class " + className + GEN_CLASS_SUFFIX + " implements ViewBinder<" + className + "> {\n");//声明类实现ViewBinder接口
            builder.append("\t@Override\n");
            builder.append("\tpublic void bind(" + className + " args" + "){\n");//定义方法


            //解析注解
            for (Element element : elements) {
                if (element.getKind() == ElementKind.FIELD) {
                    //如果不是View的子类则报错
                    if (!isView(element.asType())) {
                        mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a View", element);
                    }
                    //处理字段的注入
                    //获取变量类型
                    String type = element.asType().toString();
                    //获取变量名
                    String fieldName = element.getSimpleName().toString();
                    //id
                    int resourceId = element.getAnnotation(InjectView.class).value();

                    //将id和字段名关联存储
                    fieldMap.put(resourceId, fieldName);

                    //开始findViewById并赋值
                    builder.append("\t\targs." + fieldName + "=(" + type + ")args.findViewById(" + resourceId + ");\n");


                } else if (element.getKind() == ElementKind.METHOD) {
                    //处理方法
                    int[] ids = element.getAnnotation(InjectMethod.class).value();
                    String methodName = element.getSimpleName().toString();
                    for (int id : ids) {
                        //获取对应id的字段名
                        String fieldName = fieldMap.get(id);
                        if (null != fieldName) {
                            builder.append("\t\targs." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
                        } else {
                            builder.append("\t\targs.findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
                        }
                        builder.append("\t\t\t@Override\n")
                                .append("\t\t\tpublic void onClick(View v) {\n")
                                .append("\t\t\t\targs." + methodName + "(v);\n")
                                .append("\t\t\t}\n")
                                .append("\t\t});\n");
                    }

                } else {
                    mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a FIELD or METHOD ", element);
                }
            }
            //添加结尾
            builder.append("\t}\n").append("}");

            //生成代码
            generateCode(className + GEN_CLASS_SUFFIX, builder.toString());

        }

        /**
         * 生成代码
         *
         * @param className java文件名
         * @param code      java代码
         */
        private void generateCode(String className, String code) {
            try {
                JavaFileObject file = mFiler.createSourceFile(className);
                Writer writer = file.openWriter();//拿到输出流
                writer.write(code);
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    上面类中出现了很多Element接口,在process方法中使用getElementsAnnotatedWith获取到的都是Element接口,其实我们用Element.getKind获取到类型之后可以将他们强转成对应的子接口,这些子接口提供了一些针对性的操作,这些子接口有:

    TypeElement:表示一个类或接口元素。
    PackageElement:表示一个包元素。
    VariableElement:表示一个属性、enum 常量、方法或构造方法参数、局部变量或异常参数。
    ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
    ok,注解处理器弄好了,该如何使用呢?在Android项目的app的gradle文件中添加下依赖:

     annotationProcessor project(path: ':processor')
     implementation project(path: ':processor')
    1
    2
    很简单,简单说一下这里为什么要用同时使用annotationProcessor 和implementation 来添加依赖,前者是专门用来添加注解处理器的,后者的添加是因为,我的ViewInjector工具类以及InjectMethod和InjectView注解都放在了processor库中,所以还需要单独引进来,你也可以把工具类和注解类单独放到一个java Library,这样Android项目和processor库都需要添加一下注解所在的库。

    ButterKnife 7.0.1之后的版本就是把注解和处理器单独分开为独立的库,使用的时候需要同时引入2者,也就是这样:

    dependencies {
      implementation 'com.jakewharton:butterknife:10.2.1'
      annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
    }

    这里有个坑,顺带提一下,在很多文章,你会看到引入处理器库的时候是使用android-apt插件的,这个插件在gradle2.2之后的版本被annotationProcessor替代了,官方已经宣布不再维护android-apt这个插件了,如果你的gradle版本比较低,那么引入的步骤就比较麻烦了,大致有3个步骤如下:
    1.android工程(project)的build.gradle下的dependencies下添加:
    classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’

    2.android工程(app)的build.gradle的dependencies中以apt的方式引入注解处理器,例如:
    apt project(’:processor’)

    3.android工程((app)的build.gradle文件顶部添加:
    apply plugin: ‘com.neenbedankt.android-apt’

    支持Fragment的使用
    先来看看使用效果:

    在MainActivity的内部添加了一个Fragment,代码如下:

    public class MainActivity extends AppCompatActivity {

        @InjectView(R.id.btn_title)
        TextView mTitleBtn;

        @InjectView(R.id.btn_desc)
        TextView mDescBtn;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            //注入
            ViewInjector.inject(this);

            //注入后就可以使用了
            mTitleBtn.setText("我是标题");
            mDescBtn.setText("我是描述");

            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.fl_container, new MainFragment())
                    .commitAllowingStateLoss();

        }

        //设置点击事件
        @InjectMethod({R.id.btn_title, R.id.btn_desc})
        public void showToast(View v) {
            switch (v.getId()) {
                case R.id.btn_title:
                    Toast.makeText(this, "标题被点击了", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.btn_desc:
                    Toast.makeText(this, "描述被点击了", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
        
        //内部的Fragment
        public static class MainFragment  extends Fragment {
            private View rootView;

            @InjectView(R.id.tv_info)
            TextView mInfoTv;

            @Override
            public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                                     @Nullable Bundle savedInstanceState) {

                if (rootView == null) {
                    rootView = inflater.inflate(R.layout.fragment_main, container, false);

                    //注入
                    ViewInjector.inject(this, rootView);

                }else{
                    ViewParent parent = rootView.getParent();
                    if (null != parent) {
                        ((ViewGroup) parent).removeView(rootView);
                    }
                }
                return rootView;
            }

            @Override
            public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
                super.onViewCreated(view, savedInstanceState);

                mInfoTv.setText("我是Fragment");
            }

            @InjectMethod(R.id.tv_info)
            public void onClick(View view) {
                Toast.makeText(getActivity(), "fragment被点击了", Toast.LENGTH_SHORT).show();
            }
        }

    }

    在Fragment注入的地方是用的是ViewInjector.inject(this, rootView);进行注入,很显然,这里需要对ViewInjector以及注解处理器做相应的修改

    修改ViewInjector
    /**
     * 注解注入工具类
     */
    public class ViewInjector {

        //定义注解生成类实现的接口
        public interface ViewBinder {
            void bind(T t, Object source);
        }

        //存储已实例化过的注解生成类
        static final Map BINDERS = new LinkedHashMap<>();

        /**
         * 注入Activity时用这个
         *
         * @param activity
         */
        public static void inject(Object activity) {
            inject(activity, activity);
        }

        /**
         * 给注解生成类注入值
         *
         * @param target Activity/Fragment
         * @param source target=Activity时,是Activity,如果是target=Fragment时,就传根Fragment的根View
         */
        public static void inject(Object target, Object source) {
            Class clazz = target.getClass();
            ViewBinder viewBinder = BINDERS.get(clazz);
            if (viewBinder == null) {
                try {
                    String packageName = clazz.getPackage().getName();
                    String className = clazz.getSimpleName();
                    String qualifiedName = packageName + "." + className + ViewBindProcessor.GEN_CLASS_SUFFIX;
                    viewBinder = (ViewBinder) Class.forName(qualifiedName).newInstance();
                    BINDERS.put(clazz, viewBinder);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (null != viewBinder) {
                viewBinder.bind(target, source); //执行绑定
            }

        }
    }


    修改ViewBindProcessor
    主要是generateInjectorCode方法的修改,如下所示:

    /**
         * 生成注入代码
         *
         * @param typeElement 元素所在类的Element
         * @param elements    需要注入的元素
         */
        private void generateInjectorCode(TypeElement typeElement, List elements) {

            //取出所在的类名
            String className = typeElement.getSimpleName().toString();
            String qualifiedName = typeElement.getQualifiedName().toString();
            //该类所在的包名
            String packageName = mElementUtils.getPackageOf(typeElement).asType().toString();
            //存储所有的字段名
            Map fieldMap = new HashMap<>();

            //开始编写java类
            StringBuilder builder = new StringBuilder();
            builder.append("// Generated code from ViewInjector. Do not modify!\n");
            builder.append("package " + packageName + ";\n"); //声明包
            builder.append("import mchenys.ViewInjector.ViewBinder;\n");//导包
            builder.append("import " + qualifiedName + ";\n");//导包
            builder.append("import android.view.*;\n");//导包
            builder.append("public class " + className + GEN_CLASS_SUFFIX + " implements ViewBinder<" + className + "> {\n");//声明类实现ViewBinder接口
            builder.append("\t@Override\n");
            builder.append("\tpublic void bind(" + className + " target, Object source){\n");//定义方法

            //判断是否是Fragment
            boolean isFragment = isFragment(typeElement.asType());
            if (isFragment) {
                builder.append("\t\tViewGroup rootView = (ViewGroup)source;\n");
            }

            //解析注解
            for (Element element : elements) {
                if (element.getKind() == ElementKind.FIELD) {
                    //如果不是View的子类则报错
                    if (!isView(element.asType())) {
                        mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a View", element);
                    }
                    //处理字段的注入
                    //获取变量类型
                    String type = element.asType().toString();
                    //获取变量名
                    String fieldName = element.getSimpleName().toString();
                    //id
                    int resourceId = element.getAnnotation(InjectView.class).value();

                    //将id和字段名关联存储
                    fieldMap.put(resourceId, fieldName);

                    //开始findViewById并赋值
                    if (isFragment) {
                        builder.append("\t\ttarget." + fieldName + "=(" + type + ")rootView.findViewById(" + resourceId + ");\n");
                    } else {
                        builder.append("\t\ttarget." + fieldName + "=(" + type + ")target.findViewById(" + resourceId + ");\n");
                    }
                } else if (element.getKind() == ElementKind.METHOD) {
                    //处理方法
                    int[] ids = element.getAnnotation(InjectMethod.class).value();
                    //得到方法名
                    String methodName = element.getSimpleName().toString();
                    for (int id : ids) {
                        //获取对应id的字段名
                        String fieldName = fieldMap.get(id);
                        if (null != fieldName) {
                            builder.append("\t\ttarget." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
                        } else {
                            if (isFragment) {
                                builder.append("\t\trootView");
                            }else{
                                builder.append("\t\ttarget");
                            }
                            builder.append(".findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
                        }
                        builder.append("\t\t\t@Override\n")
                                .append("\t\t\tpublic void onClick(View v) {\n")
                                .append("\t\t\t\ttarget." + methodName + "(v);\n")
                                .append("\t\t\t}\n")
                                .append("\t\t});\n");
                    }

                } else {
                    mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a FIELD or METHOD ", element);
                }


            }
            //添加结尾
            builder.append("\t}\n").append("}");

            //生成代码
            generateCode(className + GEN_CLASS_SUFFIX, builder.toString());

        }

    以及添加了一个判断当前类是否是Fragment的方法

    //递归判断是否是Fragment
    private boolean isFragment(TypeMirror type) {
        //获取所有父类类型
        List supers = mTypeUtils.directSupertypes(type);
        if (supers.size() == 0) {
            return false;
        }
        for (TypeMirror superType : supers) {
            if (superType.toString().equals("android.support.v4.app.Fragment")
                    || isFragment(superType)) {
                return true;
            }
        }
        return false;
    }

    搞定,编译android项目的时候,会看到项目的\build\generated\ap_generated_sources\debug\out目录下会多了2个自动生成的java文件:

    其中MainFragmentInjector的代码如下:

    // Generated code from ViewInjector. Do not modify!
    package blog.csdn.net.mchenys.essayjoke;
    import mchenys.ViewInjector.ViewBinder;
    import blog.csdn.net.mchenys.essayjoke.MainActivity.MainFragment;
    import android.view.*;
    public class MainFragmentInjector implements ViewBinder {
        @Override
        public void bind(MainFragment target, Object source){
            ViewGroup rootView = (ViewGroup)source;
            target.mInfoTv=(android.widget.TextView)rootView.findViewById(2131165328);
            target.mInfoTv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    target.onClick(v);
                }
            });
        }
    }

    内容就是在注解处理器的generateInjectorCode的生成结果。

    扩展
    对于InjectMethod声明的方法,如果想使用空参数的方法,亦或者执行方法的时候还有可能会有异常抛出,而我不想在APP上直接看到crash的弹弹窗,这些都是可以实现的,修改处理器的generateInjectorCode方法,针对方法处理的逻辑修改如下:

    else if (element.getKind() == ElementKind.METHOD) {
    //处理方法
     int[] ids = element.getAnnotation(InjectMethod.class).value();
     //得到方法名
     String methodName = element.getSimpleName().toString();

     //判断方法是否有参数,如果有的话参数类型必须是View的子类,且最多只能有一个参数
     ExecutableElement executableElement = (ExecutableElement) element;
     List parameters = executableElement.getParameters();
     boolean hasParams = !parameters.isEmpty();
     if (hasParams) {
         VariableElement variableElement = parameters.get(0);
         if (!isView(variableElement.asType())) {
             mMessager.printMessage(Diagnostic.Kind.ERROR, variableElement.asType().toString()
             +" is not a View parameter", variableElement);
         }
         if (parameters.size() != 1) {
             mMessager.printMessage(Diagnostic.Kind.ERROR, "can be at most one parameter", executableElement);
         }
     }

     for (int id : ids) {
         //获取对应id的字段名
         String fieldName = fieldMap.get(id);
         //设置点击事件
         if (null != fieldName) {
             builder.append("\t\ttarget." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
         } else {
             if (isFragment) {
                 builder.append("\t\trootView");
             } else {
                 builder.append("\t\ttarget");
             }
             builder.append(".findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
         }
         builder.append("\t\t\t@Override\n")
                 .append("\t\t\tpublic void onClick(View v) {\n")
                 .append("\t\t\t\ttry {\n"); //添加try cache
         if (hasParams) {
             builder.append("\t\t\t\t\ttarget." + methodName + "(v);\n"); //执行带参方法
         }else{
             builder.append("\t\t\t\t\ttarget." + methodName + "();\n");//执行空参方法
         }
         builder.append("\t\t\t\t} catch (Exception ex) {\n")
                 .append("\t\t\t\t\tex.printStackTrace();\n")
                 .append("\t\t\t\t}\n")
                 .append("\t\t\t}\n")
                 .append("\t\t});\n");
     }
     

  • 相关阅读:
    django rest framework 学习笔记-实战商城3
    物联网设备带你进入物联网时代,轻松实现数据互联互通
    开发平台模块化重构之实现
    STL-queue的使用及其模拟实现
    JavaScript弹窗关闭的编程方法
    SpringCloud Stream整合RabbitMQ3.5.0
    计算机网络概述
    TypeScript 快速入门
    游戏客户端--个人学习路线总结、指北
    学生个人网页设计作品:基于HTML+CSS+JavaScript实现摄影艺术网站 DIV布局简单的摄影主题网站
  • 原文地址:https://blog.csdn.net/linghu_java/article/details/132901023