美文网首页android
编译时注解 - ButterKnife源码分析和手写

编译时注解 - ButterKnife源码分析和手写

作者: Spring618 | 来源:发表于2018-11-08 20:20 被阅读359次
    1. ButterKnife介绍
      主要是解决掉 findViewById 和 setOnclick ,还包括资源的注入 , IOC ,运行时注解(上次)和编译时注解(ButterKnife注解)

    2. ButterKnife原理分析
      主要采用编译时注解,说白了就是用 apt 生成代码

    ButterKnife使用

    github地址:
    https://github.com/JakeWharton/butterknife

    配置步骤:

    说明:最初我按照最新的配置步骤来配置项目的时候,发现报错。

    error: resource android:attr/fontVariationSettings resource android:attr/ttcIndex not found.

    dependencies {
      implementation 'com.jakewharton:butterknife:9.0.0-rc1'
      annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc1'
    }
    
    //To use Butter Knife in a library, add the plugin to your buildscript:
    buildscript {
      repositories {
        mavenCentral()
       }
      dependencies {
        classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-rc1'
      }
    }
    
    // and then apply it in your module:
    
    apply plugin: 'com.android.library'
    apply plugin: 'com.jakewharton.butterknife'
    

    网上查找解决办法:https://blog.csdn.net/wumama123/article/details/79493190,但是还没有解决。然后之后改用ButterKnife8.1.0的版本。

    对应配置地址:https://www.jianshu.com/p/0392199a682b

    然后发现还是会报错:

    Error:android-apt plugin is incompatible with the Android Gradle plugin. Please use 'annotationProc

    解决方案:


    解决方案.png

    一切配置后之后使用:

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.user)
        TextView username;
    
        Unbinder mUnbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mUnbinder =   ButterKnife.bind(this);
            username.setText("Spring");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mUnbinder.unbind();
        }
    }
    

    ButterKnife原理分析:
    通过搜索会发现有这样一个自动生成的类MainActivity$$ViewBinder:

    public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
      @Override
      public Unbinder bind(Finder finder, T target, Object source) {
        return new InnerUnbinder<>(target, finder, source);
      }
    
      protected static class InnerUnbinder<T extends MainActivity> implements Unbinder {
        protected T target;
    
        protected InnerUnbinder(T target, Finder finder, Object source) {
          this.target = target;
    
          target.username = finder.findRequiredViewAsType(source, 2131230891, "field 'username'", TextView.class);
        }
    
        @Override
        public void unbind() {
          T target = this.target;
          if (target == null) throw new IllegalStateException("Bindings already cleared.");
    
          target.username = null;
    
          this.target = null;
        }
      }
    }
    
    ButterKnife自动生成.png

    通过分析这个代码,我们可以简单抽象的理解成,我们自己写了一个类MainActivity_ViewBinder:

    public class MainActivity_ViewBinder {
        MainActivity target;
    
        public MainActivity_ViewBinder(MainActivity target) {
            this.target = target;
            target.username = target.findViewById(R.id.user);
        }
    }
    

    使用的时候其实是在自动绑定控件:

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.user)
        TextView username;
    
        Unbinder mUnbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // mUnbinder = ButterKnife.bind(this);
            new MainActivity_ViewBinder(this);
            username.setText("Spring MainActivity_ViewBinder");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mUnbinder.unbind();
        }
    }
    

    MainActivity$$ViewBinder 这个类会随着每次编译运行的时候,自动生成。那么如何手写一个ButterKnife呢?
    用到的原理是apt,是java知识,在《Thinking in java》这本书里面有讲解。

    手写一个ButterKnife

    生成代码的环境是java代码,因此是一个库。

    ButterKnife的第一个版本架构图:


    ButterKnife的第一个版本架构图.png

    但是我们需要拆分,因为生成代码的compiler这部分代码,不应该打包在apk包里面,因此我们对上面的架构图改版。

    ButterKnife的第二个版本架构图:


    ButterKnife的第二个版本架构图.png

    新建各个Lib依赖

    lib依赖关系.png

    MyButterKnife-annotation:用来写注解,这里我们只写一个MyBindView注解用作演示,后面可以添加onClick事件等。

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

    MyButterKnife-compiler :为生成代码的依赖包,这里需要在这个包下面的build.gradle拷入两个依赖以及设置编码,完整build.gradle如下:

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation project(':MyButterKnife-annotations')
    
        // 拷入
        compile 'com.google.auto.service:auto-service:1.0-rc3'
        compile 'com.squareup:javapoet:1.8.0'
    }
    
    //Error:(23, 35) 错误: 编码GBK的不可映射字符
    tasks.withType(JavaCompile){
        options.encoding ="UTF-8"
    }
    
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    

    写一个代码生成的Processor类MyButterKnifeProcessor继承MyButterKnifeProcessor,完整代码如下:

    @AutoService(Processor.class)
    public class MyButterKnifeProcessor extends AbstractProcessor {
    
        // 1. 指定处理的版本,这里返回最新版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
    
        //2. 给到需要处理的注解
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
                types.add(annotation.getCanonicalName());
            }
            return types;
        }
    
        private Set<Class<? extends Annotation>> getSupportedAnnotations() {
            Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
            annotations.add(MyBindView.class);
            // ... 其他注解,如onClick
    
            return annotations;
        }
    
        //3. 生成代码
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            System.out.println("----------->代码生成部分");
    
            return false;
        }
    }
    

    为了演示MyButterKnifeProcessor的process方法是否能走到,我们在app里面引入compiler和annotations这两个依赖,然后在MainActivity里面绑定一个View,代码如下:

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.user)
        TextView username;
        @BindView(R.id.user2)
        TextView username2;
        @MyBindView(R.id.user3)// 使用自己写的MyBindView注解
        TextView username3;
        Unbinder mUnbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mUnbinder = ButterKnife.bind(this);
            //new MainActivity_ViewBinder(this);
            username.setText("Spring MainActivity_ViewBinder");
            username2.setText("username2");
    
    
            //username3.setText("username3");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mUnbinder.unbind();
       
    

    然后运行程序,在gradle console控制台输出:

    gradle console控制台输出.png

    可以看到我们的代码执行了。

    小结:以上的代码需要注意几点

    • @AutoService(Processor.class) 这个注解别忘记加
    • 解决编码GBK的不可映射字符的问题

      tasks.withType(JavaCompile){
      options.encoding ="UTF-8"
      }

    • MainActivity需要改动之后,apt才会执行
    • 错误信息提示

    Annotation processors must be explicitly declared now
    解决参考:https://blog.csdn.net/daihuimaozideren/article/details/78902079 ,推荐使用第二种

    处理processor

    这里直接贴出完整代码,后面会说几个注意事项:

    @AutoService(Processor.class)
    public class MyButterKnifeProcessor extends AbstractProcessor {
    
        private Filer mFiler;//用来生成java文件
        private Elements mElementsUtils;//用来获取生成java文件的路径
    
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mFiler = processingEnvironment.getFiler();
            mElementsUtils = processingEnvironment.getElementUtils();
        }
    
        // 1. 指定处理的版本,这里返回最新版本
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
    
        //2. 给到需要处理的注解
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
                types.add(annotation.getCanonicalName());
            }
            return types;
        }
    
        private Set<Class<? extends Annotation>> getSupportedAnnotations() {
            Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
            annotations.add(MyBindView.class);
            // ... 其他注解,如onClick
    
            return annotations;
        }
    
        //3. 生成代码
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            System.out.println("----------->代码生成部分");
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyBindView.class);
            for (Element e : elements) {
                Element enclosingElement = e.getEnclosingElement();
                System.out.println("--Element--" + e.getSimpleName().toString() + " in " + enclosingElement.getSimpleName().toString());
            }
    
    
            // 1. 解析 map: activity-->list<element>
            Map<Element, List<Element>> map = new LinkedHashMap<>();
            for (Element e : elements) {
                Element enclosingElement = e.getEnclosingElement();
    
                List<Element> viewElements = map.get(enclosingElement);
                if (viewElements == null) {
                    viewElements = new ArrayList<>();
                    map.put(enclosingElement, viewElements);
                }
                viewElements.add(e);
            }
    
            // 2.生成 java类
    
            for (Map.Entry<Element, List<Element>> entry : map.entrySet()) {
                Element enclosingElement = entry.getKey();
                List<Element> elementsValue = entry.getValue();
    
                System.out.println(enclosingElement + "----------->" + elementsValue.size());//com.ivyzh.butterknifedemo.MainActivity----------->2
    
    
                String activitySimpleName = enclosingElement.getSimpleName().toString();
                ClassName unbinderClassSimpleName = ClassName.get("com.ivyzh.mybutterknife", "MyUnbinder");
                System.out.println("unbinderClassSimpleName----------->" + unbinderClassSimpleName);//com.ivyzh.mybutterknife.MyUnbinder
    
    
                // 2.1组装类:  xxx_MyViewBinding implements Unbinder
                TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activitySimpleName + "_MyViewBinder")
                        .addModifiers(Modifier.FINAL, Modifier.PUBLIC).addSuperinterface(unbinderClassSimpleName)
                        .addField(ClassName.bestGuess(enclosingElement.toString()), "target");// 添加成员变量 MainActivity target;
                ;
    
    
                // 2.2组装unbind 方法
                MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.FINAL, Modifier.PUBLIC);
    
                // 2.3组装构造函数: public xxx_ViewBinding(xxx target)
    //             ClassName activityName = ClassName.bestGuess(activitySimpleName);// 这里不能用activitySimpleName
                ClassName activityName = ClassName.bestGuess(enclosingElement.toString());// 用enclosingElement.toString()
                MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(activityName, "target");
    
    
                // 2.3.1添加 target.textView1 = Utils.findViewById(target,R.id.tv1);
                //  target.username = finder.findRequiredViewAsType(source, 2131230905, "field 'username'", TextView.class);
    
                // 2.3.2 unber里面的方法也在这里面实现
                unbindMethodBuilder.addStatement("$L target= this.target", enclosingElement.getSimpleName().toString());
    
                for (Element view : elementsValue) {
                    String viewName = view.getSimpleName().toString();
                    ClassName utilsName = ClassName.get("com.ivyzh.mybutterknife", "FindViewUtils");
                    int resId = view.getAnnotation(MyBindView.class).value();
                    constructorBuilder.addStatement("this.target = target");
                    constructorBuilder.addStatement("target.$L = $L.findViewById(target,$L)", viewName, utilsName, resId);
                    unbindMethodBuilder.addStatement("target.$L = null", viewName);
    
                }
    
    
                // 2.4 将方法统一添加到classBuilder中
                classBuilder.addMethod(constructorBuilder.build());// 添加构造函数
                classBuilder.addMethod(unbindMethodBuilder.build());// 添加unbinder方法
    
    
                // 2.5生成类
                try {
                    String packageName = "";//如果是空的话,会生成在根目录
                    packageName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
                    JavaFile.builder(packageName, classBuilder.build())
                            .build()
                            .writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("生成类失败:" + e.toString());
                }
            }
    
    
            return false;
        }
    }
    

    MyUnbinder:

    public interface MyUnbinder {
        void unbind();
    
        MyUnbinder EMPTY = new MyUnbinder() {
            @Override
            public void unbind() {
            }
        };
    }
    

    FindViewUtils:这个工具类写在MyButterKnife依赖包中

    public class FindViewUtils {
    
        public static <T> T findViewById(Activity activity, int viewId) {
            return (T) activity.findViewById(viewId);
        }
    }
    

    MainActivity:

    public class MainActivity extends AppCompatActivity {
    
        // 使用MainActivity_ViewBinder
        TextView username;
        // 使用ButterKnife
        @BindView(R.id.user2)
        TextView username2;
        // 使用自己写的MyBindView注解
        @MyBindView(R.id.user3)
        TextView username3;
        @MyBindView(R.id.user4)
        TextView tvUserName4;
    
        Unbinder mUnbinder;
        MyUnbinder mMyUnbinder;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mUnbinder = ButterKnife.bind(this);
            new MainActivity_ViewBinder(this);
            mMyUnbinder = MyButterKnife.bind(this);
            username.setText("use MainActivity_ViewBinder");
            username2.setText("use butterknife.");
            username3.setText("use MyBindView");
            tvUserName4.setText("use by MyBindView");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mUnbinder.unbind();
            mMyUnbinder.unbind();
        }
    }
    
    

    MyButterKnife:

    public class MyButterKnife {
    
        public static MyUnbinder bind(Activity activity) {
    
            try {
                // MainActivity_MyViewBinder
                //ClassNotFoundException: com.ivyzh.butterknifedemo.MainActivity_MyViewBinder
                Class<? extends MyUnbinder> clazz = (Class<? extends MyUnbinder>) Class.forName(activity.getClass().getName() + "_MyViewBinder");
    
                Constructor<? extends MyUnbinder> constructor = clazz.getDeclaredConstructor(activity.getClass());
                MyUnbinder unbinder = constructor.newInstance(activity);
                return unbinder;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return MyUnbinder.EMPTY;
        }
    }
    
    

    运行效果:


    运行效果.png

    上面四个TextView分别是通过自定义简化的MainActivity_ViewBinder、JakeWharton的butterknife、自己写的MyBindView、自己写的MyBindView。

    另:瑕疵 Onclick 注解没有 , id 好像不正常,private 属性没提示错误(属性加private修饰会报错, 错误: tvUserName4可以在MainActivity中访问private),等等 ,可以参考 ButterKnife 的源码。

    源码的一些思考:

    面试
    开发中看看使用场景(明天)
    拿出部分代码整合到自己的框架中 ,有的时候我们很多功能是没有用到的

    END.

    相关文章

      网友评论

        本文标题:编译时注解 - ButterKnife源码分析和手写

        本文链接:https://www.haomeiwen.com/subject/lupwxqtx.html