Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试

Posted 哈特谢普苏特

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试相关的知识,希望对你有一定的参考价值。

⚠️创建的模块是java模块而非android Library,如果创建的是后者,则无法使用AbstractProcessor

 

按照书上讲述的,想要自己去仿写一下butterknife

最终的项目结构如上图所示。其中annotations是放不同注解的java libary,process是注解处理器,也是个java libary,butterKnife是个android libary是为了实现最终的onbind()方法

 

 最终生成的文件在这个位置(一开始找错了,以为是resource下 检查了很多次都没有 最终全局搜了一下发现可能因为as 和gradle版本的问题,最终生成的文件在这个路径下)

ButterKinfe的实质是代替我们自己去写findViewById以及OnClick,解放了我们的双手,使用BindView等注解实现对应的方法,但是实质上它的内部仍然是需要通过findViewById()的方式去寻找资源id,并将其与控件进行绑定,如下图所示

 因此,我们仿写ButterKnife的实质也就是自己去定义类似的注解,并生成对应的文件和功能。

1.在项目中新建一个Java Library来专门存放注解,这个Library名为annotations,类似于下图的做法

该java libary中的build.gradle配置如下

plugins 
    id 'java-library'


dependencies 
    implementation fileTree(dir: 'libs', include: ['*.jar'])


java 
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8

 2.在项目中再新建一个Java Library来存放注解处理器,这个Library名为process(注意路径,最一开始写的时候我一不小心把部分路径丢失了,导致一直无法生成正确的结果)由于process是注解处理器,因此需要手动或自动生成Processor文件,如下图所示

 我使用的是Google开源的AutoService,build.gradle配置如下

plugins 
    id 'java-library'



dependencies 
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
    implementation project(path: ':annotations')


java 
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8

并在处理器中添加@AutoService(Processor.class)

 

 举一个简单的例子BindString

在没有ButterKnife之前,使用string,类似于

public class MainActivity extends AppCompatActivity 
    @BindView(R.id.text_view)
    TextView textView;
    //@BindString(R.string.app_name)
    String meg = null;

    @OnClick(R.id.text_view)
    public void onClick(View view)
        switch (view.getId())
            case R.id.text_view:
                meg = getResources().getString(R.string.app_name);
                Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();
                break;
        
    

使用之后

public class MainActivity extends AppCompatActivity 
    @BindView(R.id.text_view)
    TextView textView;
    @BindString(R.string.app_name)
    String meg;

    @OnClick(R.id.text_view)
    public void onClick(View view)
        switch (view.getId())
            case R.id.text_view:
                Toast.makeText(MainActivity.this,meg,Toast.LENGTH_LONG).show();
                break;
        
    


    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        TextView textView = findViewById(R.id.text_view);
    
生成的MainActivity$ViewBinder文件
package com.example.farmedemo;
import android.view.View;
public class MainActivity$ViewBinder
public MainActivity$ViewBinder(final com.example.farmedemo.MainActivity target)
target.textView =(android.widget.TextView)target.findViewById(2131230975);
(target.findViewById(2131230975)).setOnClickListener(new View.OnClickListener() 
public void onClick(View p0) 
target.onClick(p0);

);
target.meg =(java.lang.String)target.getBaseContext().getResources().getString(2131623963);



AnnotationCompiler
package com.example.process;

import com.example.annotations.BindString;
import com.example.annotations.BindView;
import com.example.annotations.OnClick;
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor 
  //生成文件的对象
  Filer filer;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnvironment) 
    super.init(processingEnvironment);
    //初始化生成文件的对象
    filer = processingEnvironment.getFiler();
  

  /**
   * 声明注解处理器要处理的注解
   * @return
   */
  @Override
  public Set<String> getSupportedAnnotationTypes() 
    Set<String> types = new HashSet<>();
    types.add(OnClick.class.getCanonicalName());
    types.add(BindView.class.getCanonicalName());
    types.add(BindString.class.getCanonicalName());
    return types;
  

  /**
   * 声明注解处理器支持的java源版本
   * @return
   */
  @Override
  public SourceVersion getSupportedSourceVersion() 
    return processingEnv.getSourceVersion();
  

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 
    //获取到程序中的注解 并且和它所在的类对象一一对应起来
    Map<TypeElement, ElementForType> andParseTargets = findAndParseTargets(roundEnvironment);
    //写文件
    if(andParseTargets.size()>0)
      Iterator<TypeElement> iterator = andParseTargets.keySet().iterator();
      Writer writer = null;
      while (iterator.hasNext())
        TypeElement typeElement = iterator.next();
        ElementForType elementForType = andParseTargets.get(typeElement);
        String activityName = typeElement.getSimpleName().toString();
        //获取到新类名
        String newClazzName = activityName+"$ViewBinder";
        //获取到包名
        String packageName = getPackageName(typeElement);
        //创建java文件
        try 
          JavaFileObject sourceFile = filer.createSourceFile(
          packageName+ "." + newClazzName);
          writer = sourceFile.openWriter();
          StringBuffer stringBuffer = getStringBuffer(packageName, newClazzName, typeElement, elementForType);
          writer.write(stringBuffer.toString());
         catch (IOException e) 
          e.printStackTrace();
        finally 
          if(writer != null)
            try 
              writer.close();
             catch (IOException e) 
              e.printStackTrace();
            
          
        
      
    
    return false;
  

  /**
   * 获取到所有的注解 以及将注解和Activity一一对应起来
   * @param roundEnvironment
   */
  private Map<TypeElement, ElementForType> findAndParseTargets(RoundEnvironment roundEnvironment) 
    Map<TypeElement, ElementForType> map = new HashMap<>();

    //获取到模块中所有用到了BindView的节点
    Set<? extends Element> viewElelments = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    //获取到模块中所有用到了OnClick的节点
    Set<? extends Element> methodElements = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
    //获取到模块中所有用到了BindString的节点
    Set<?extends Element> stringElements = roundEnvironment.getElementsAnnotatedWith(BindString.class);
    //遍历所有的成员变量节点  一一对应的封装到ElementForType对象中
    for (Element viewElelment : viewElelments) 
      //转换
      VariableElement variableElement = (VariableElement) viewElelment;
      //获取到它的上一个节点  成员变量节点的上一个节点  就是类节点
      TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
      //获取到类节点在map中所对应的value
      ElementForType elementForType = map.get(typeElement);
      List<VariableElement> viewElements;
      //如果这个类节点在map中已经存在
      if(elementForType !=null)
        //获取到类节点所对应的值中的控件节点的集合
        viewElements = elementForType.getViewElements();
        //如果集合为空就创建一个新的集合  然后放置到类节点所对应的值中
        if(viewElements == null)
          viewElements = new ArrayList<>();
          elementForType.setViewElements(viewElements);
        
      else
        //如果elementForType为空  就创建一个新的
        elementForType = new ElementForType();
        //同时创建一个新的控件的节点的集合
        viewElements = new ArrayList<>();
        elementForType.setViewElements(viewElements);
        if(!map.containsKey(typeElement))
          map.put(typeElement,elementForType);
        
      
      //最后  将遍历到的这个控件的节点的对象放置到控件的节点封装类中
      viewElements.add(variableElement);
    
    //遍历所有的点击事件的方法的节点  并且封装在对象中
    for (Element methodElement : methodElements) 
      ExecutableElement executableElement = (ExecutableElement) methodElement;
      TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
      ElementForType elementForType = map.get(typeElement);
      List<ExecutableElement> executableElements;
      logUtil(elementForType+"");
      if(elementForType !=null)
        executableElements = elementForType.getMethodElements();
        if(executableElements == null)
          executableElements = new ArrayList<>();
          elementForType.setMethodElements(executableElements);
        
      else
        elementForType = new ElementForType();
        executableElements = new ArrayList<>();
        elementForType.setMethodElements(executableElements);
        if(!map.containsKey(typeElement))
          map.put(typeElement,elementForType);
        
      
      executableElements.add(executableElement);
    
    //遍历成员变量节点并封装
    for (Element viewElelment : stringElements) 
      //转换
      VariableElement variableElement = (VariableElement) viewElelment;
      //获取到它的上一个节点  成员变量节点的上一个节点  就是类节点
      TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
      //获取到类节点在map中所对应的value
      ElementForType elementForType = map.get(typeElement);
      List<VariableElement> viewElements;
      //如果这个类节点在map中已经存在
      if(elementForType !=null)
        //获取到类节点所对应的值中的控件节点的集合
        viewElements = elementForType.getStringElements();
        //如果集合为空就创建一个新的集合  然后放置到类节点所对应的值中
        if(viewElements == null)
          viewElements = new ArrayList<>();
          elementForType.setStringElements(viewElements);
        
      else
        //如果elementForType为空  就创建一个新的
        elementForType = new ElementForType();
        //同时创建一个新的控件的节点的集合
        viewElements = new ArrayList<>();
        elementForType.setStringElements(viewElements);
        if(!map.containsKey(typeElement))
          map.put(typeElement,elementForType);
        
      
      //最后  将遍历到的这个控件的节点的对象放置到控件的节点封装类中
      viewElements.add(variableElement);
    

    return map;
  

  /**
   * 获取包名的方法
   * @param typeElement
   */
  public String getPackageName(Element typeElement)
    //获取包名
    PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(typeElement);
    Name qualifiedName = packageOf.getQualifiedName();
    return qualifiedName.toString();
  

  public void logUtil(String message)
    Messager messager = processingEnv.getMessager();
    messager.printMessage(Diagnostic.Kind.NOTE,message);
  

  /**
   * 获取到类的拼装语句的方法
   * @param packageName
   * @param newClazzName
   * @param typeElement
   * @param elementForType
   * @return
   */
  public StringBuffer getStringBuffer(String packageName,String newClazzName,
                                      TypeElement typeElement,ElementForType elementForType ) 
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("package " + packageName + ";\\n");
    stringBuffer.append("import android.view.View;\\n");
    stringBuffer.append("public class " + newClazzName + "\\n");
    stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target)\\n");
    if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) 
      List<VariableElement> viewElements = elementForType.getViewElements();
      for (VariableElement viewElement : viewElements) 
        //获取到类型
        TypeMirror typeMirror = viewElement.asType();
        //获取到控件的名字
        Name simpleName = viewElement.getSimpleName();
        //获取到资源ID
        int resId = viewElement.getAnnotation(BindView.class).value();
        stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\\n");
      
    


    if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) 
      List<ExecutableElement> methodElements = elementForType.getMethodElements();
      for (ExecutableElement methodElement : methodElements) 
        int[] resIds = methodElement.getAnnotation(OnClick.class).value();
        String methodName = methodElement.getSimpleName().toString();
        for (int resId : resIds) 
          stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() \\n");
          stringBuffer.append("public void onClick(View p0) \\n");
          stringBuffer.append("target." + methodName + "(p0);\\n");
          stringBuffer.append("\\n);\\n");
        
      
    

    if (elementForType != null && elementForType.getStringElements() != null && elementForType.getStringElements().size() > 0) 
      List<VariableElement> variableElements = elementForType.getStringElements();
      for (VariableElement variableElement : variableElements) 
        //获取到类型
        TypeMirror typeMirror = variableElement.asType();
        //获取到控件的名字
        Name simpleName = variableElement.getSimpleName();
        //获取到资源ID

        int resId = variableElement.getAnnotation(BindString.class).value();
        stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.getBaseContext().getResources().getString(" + resId + ");\\n");
        stringBuffer.append("\\n\\n");

      
//    

      return stringBuffer;
    
    return stringBuffer;
  

ElementForType
package com.example.process;

import java.util.List;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

public class ElementForType 
  //所有绑定View的成员变量的节点的集合
  List<VariableElement> viewElements;
  //所有点击时间方法的节点的集合
  List<ExecutableElement> methodElements;
  List<VariableElement> stringElements;

  public List<VariableElement> getStringElements() 
    return stringElements;
  

  public void setStringElements(List<VariableElement> stringElements) 
    this.stringElements = stringElements;
  

  public List<VariableElement> getViewElements() 
    return viewElements;
  

  public void setViewElements(List<VariableElement> viewElements) 
    this.viewElements = viewElements;
  

  public List<ExecutableElement> getMethodElements() 
    return methodElements;
  

  public void setMethodElements(List<ExecutableElement> methodElements) 
    this.methodElements = methodElements;
  

可以看出,butterKnife为我们做了很多事情,上面的代码距离真正的butterknife相差很大,butterknife一般不用于组件化开发,因为会导致控件Id的重复。

以上是关于Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试的主要内容,如果未能解决你的问题,请参考以下文章

Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试

《Android进阶之光》--Material Design

《Android进阶之光》--ButterKnife

入门实战资料《Android进阶解密》+《Android进阶之光》+《重构改善既有的代码第2版》电子资料学习

Android依赖注入之BufferKnife 8.0注解使用

IOC 控制反转Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )