Android高級開發進階之路2——手寫butterknife(註解,註解處理器,類加載器)

Android高級開發進階之路2——手寫butterknife(註解,註解處理器,類加載器)

 

首先我們來簡單講講ButterKnife的工作過程:

引入庫:

compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

使用:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_butterknife)
    TextView tvButterknife;
   
//...

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

當我們執行build操作的之後,Android工程經過編譯器的編譯,java文件會被生成class文件,ButterKnife會在以下下目錄生成相應的文件,並且幫我們findviewbyid。

那麼這個文件是怎麼跟Activity綁定關係的呢?

是通過Activity中的ButterKnife.bind(this);來進行綁定的。

public final class ButterKnife {
/**
   * BindView annotated fields and methods in the specified {@code target} using the {@code source}
   * {@link Dialog} as the view root.
   *
   * @param target Target class for view binding.
   * @param source Dialog on which IDs will be looked up.
   */
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
    View sourceView = source.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

  private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }
}

------------------------------------------------------------------------------------------------------------------

好了,下面我們來看一下如何手寫一個ButterKnife?

首先我們要明白兩個概念:註解和註解處理器

註解:相當於一個牌子

註解處理器:相當於一個識別牌子的機器

 

  1. 新建註解庫
  2. 註解庫中新建註解BindView
  3. 新建IBinder接口類,主要的作用就是接口化編程,令編譯器生成的class文件轉化成改接口的實現類
  4. 新建註解編譯器庫,庫中的build.gradle文件必須引入兩個庫(
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    
  5. 新建註解編譯器,複寫其中重要的4個方法init();getSupportedAnnotationTypes();getSupportedSourceVersion();process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)在process方法中使用filer對象生成一個類文件。要注意的地方是:這個文件必須extends AbstractProcessor並且,這個類一定要使用@AutoService(Processor.class)進行註解。
  6. 在註解庫中新建一個MyButterKnife類,用於綁定Activity對象。通過Class.forName("{註解編譯器生成的類文件完整的ClassName}")進行類加載,得到勢力,把activity對象傳入其中來綁定關係。
  7. 在Activity中使用註解,並且用MyButterKnife在setContent之後綁定關係

 

 

 

新建-註解庫

 

庫裏聲明註解BindView

@Target(ElementType.FIELD)//聲明註解作用域
@Retention(RetentionPolicy.SOURCE)//聲明註解聲明週期
public @interface BindView {
    int value();
}

 

定義一個IBind<T>接口,生成的class文件需要實現的這個接口,定義一個方法來接收Activity對象

public interface IBind<T> {
    void bind(T target);
}

 

 

新建-註解編譯器庫

重點:新建一個註解編譯器類AnnotationCompiler,具體說明看註釋。


/**
 * 註解處理器 生成Activity對應的類來綁定view
 */
@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(BindView.class.getCanonicalName());
        return types;
    }

    /**
     * 聲明當情註解處理器支持的java版本
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    /**
     * 在這個方法裏生成我們需要的文件
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {


        //拿到應用裏面所有用到BindView的節點,
        //關於節點,有類節點,成員變量節點,方法節點,一個樹形結構來的
        Set<? extends Element> elementsAnnotation = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        //將所有的節點根據不同的文件分開
        Map<String, List<Element>> map = new HashMap<>();


        for (Element element :
                elementsAnnotation) {
            //獲取成員變量的節點
            VariableElement variableElement = (VariableElement) element;

            //獲取類節點
            Element elementClass = variableElement.getEnclosingElement();
            String className = elementClass.getSimpleName().toString();
            if (map.get(className) == null) {
                map.put(className, new ArrayList<Element>());
            }
            map.get(className).add(variableElement);
        }

        if (map.size() > 0) {
            //k開始寫文件
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List<Element> variableElements = map.get(activityName);

                //通過獲取成員變量的節點獲取到上一個節點,也就是類節點
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                //通過類節點,獲取到類的包名
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();

                Writer writer = null;
                //創建文件
                try {
                    JavaFileObject sourceFile
                            = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");

                     writer = sourceFile.openWriter();

                    writer.write("package "+packageName+";\n");
                    writer.write("import com.bluetree.annotation_lib.IBind;\n");
                    writer.write("public class "+activityName+"_ViewBinding implements IBind<"+packageName + "." + activityName+"> {\n");
                    writer.write("  @Override\n");
                    writer.write("  public void bind("+packageName + "." + activityName+" target) {\n");

                    for (Element varableEle :
                            variableElements) {

                        //獲取變量名
                        String varableName = varableEle.getSimpleName().toString();
                        //獲取id
                        int id = varableEle.getAnnotation(BindView.class).value();
                        //獲取變量類型
                        TypeMirror typeMorror = varableEle.asType();


                        writer.write("   target."+varableName+" = ("+typeMorror+")target.findViewById("+id+");\n");
                    }
                    writer.write("  }\n");
                    writer.write("}\n");

                } catch (IOException e) {
                    e.printStackTrace();
                }
                finally {
                    if(writer!=null){
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        }





        return false;
    }
}

 

使用類加載技術引用

在註解庫中加入工具類,用於綁定Activity

/**
 * 通過接口,綁定activity和註解編譯器生成的class文件建立關係
 * 涉及到
 */
public class MyButterKnife {
    public static void bind(Object activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            // Class.forName("ClassName")方式會執行類加載的加載、鏈接、初始化三個步驟
            Class<?> aClass = Class.forName(name);
            IBind iBinder = (IBind) aClass.newInstance();
            iBinder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在Activity中使用我們前面定義好的註解

public class SampleAnnotationActivity extends AppCompatActivity {

    @BindView(R.id.textView)
    TextView textView;

    @BindView(R.id.button2)
    Button button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample_annotation);
        MyButterKnife.bind(SampleAnnotationActivity.this);

        button2.setText("333");

    }
}

 

 

整理了一下比較少用的操作:

annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
public SourceVersion getSupportedSourceVersion() {
    return processingEnv.getSourceVersion();
}
public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new HashSet<>();
    types.add(BindView.class.getCanonicalName());
    return types;
}
public SourceVersion getSupportedSourceVersion() {
    return processingEnv.getSourceVersion();
}
//關於節點,有類節點,成員變量節點,方法節點,一個樹形結構來的
Set<? extends Element> elementsAnnotation = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//通過類節點,獲取到類的包名
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
//創建文件
    JavaFileObject sourceFile
            = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");

     writer = sourceFile.openWriter();
//獲取id
int id = varableEle.getAnnotation(BindView.class).value();
//獲取變量類型
TypeMirror typeMorror = varableEle.asType();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章