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

  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

解决方案:


编译时注解 - ButterKnife源码分析和手写_第1张图片
解决方案.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 implements ViewBinder {
  @Override
  public Unbinder bind(Finder finder, T target, Object source) {
    return new InnerUnbinder<>(target, finder, source);
  }

  protected static class InnerUnbinder 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源码分析和手写_第2张图片
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源码分析和手写_第3张图片
ButterKnife的第一个版本架构图.png

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

ButterKnife的第二个版本架构图:


编译时注解 - ButterKnife源码分析和手写_第4张图片
ButterKnife的第二个版本架构图.png

新建各个Lib依赖

编译时注解 - ButterKnife源码分析和手写_第5张图片
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 getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet<>();
        for (Class annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set> getSupportedAnnotations() {
        Set> annotations = new LinkedHashSet<>();
        annotations.add(MyBindView.class);
        // ... 其他注解,如onClick

        return annotations;
    }

    //3. 生成代码
    @Override
    public boolean process(Set 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控制台输出:

编译时注解 - ButterKnife源码分析和手写_第6张图片
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 getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet<>();
        for (Class annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
    }

    private Set> getSupportedAnnotations() {
        Set> annotations = new LinkedHashSet<>();
        annotations.add(MyBindView.class);
        // ... 其他注解,如onClick

        return annotations;
    }

    //3. 生成代码
    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        System.out.println("----------->代码生成部分");
        Set 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
        Map> map = new LinkedHashMap<>();
        for (Element e : elements) {
            Element enclosingElement = e.getEnclosingElement();

            List viewElements = map.get(enclosingElement);
            if (viewElements == null) {
                viewElements = new ArrayList<>();
                map.put(enclosingElement, viewElements);
            }
            viewElements.add(e);
        }

        // 2.生成 java类

        for (Map.Entry> entry : map.entrySet()) {
            Element enclosingElement = entry.getKey();
            List 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 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 clazz = (Class) Class.forName(activity.getClass().getName() + "_MyViewBinder");

            Constructor constructor = clazz.getDeclaredConstructor(activity.getClass());
            MyUnbinder unbinder = constructor.newInstance(activity);
            return unbinder;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return MyUnbinder.EMPTY;
    }
}

运行效果:


编译时注解 - ButterKnife源码分析和手写_第7张图片
运行效果.png

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

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

源码的一些思考:

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

END.

你可能感兴趣的:(编译时注解 - ButterKnife源码分析和手写)