ButterKnife實現(控件初始化)

新博客地址

在學習了XUtils的注入方式之後,看了下ButterKnife的實現方式,結果發現完全不一樣,然後借鑑網上的博客,結果發現用的都是Eclipse以及舊版本的ButterKnife進行實現的。

這裏我用AndroidStudio根據ButterKnife的版本進行了實現。

文字枯燥,還是先看下butterknife的module圖:
這裏寫圖片描述

先看下ButterKnife中生成的java代碼(看看騷包的註釋 Do not modify!)

// Generated code from Butter Knife. Do not modify!
package com.example.butterknife;

import android.view.View;
import android.widget.AdapterView;
import butterknife.ButterKnife;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Finder;
import butterknife.internal.Utils;
import butterknife.internal.ViewBinder;
import java.lang.IllegalStateException;
import java.lang.Object;
import java.lang.Override;
import java.lang.SuppressWarnings;

public class SimpleActivity$$ViewBinder<T extends SimpleActivity> implements ViewBinder<T> {
  @Override
  public void bind(final Finder finder, final T target, Object source) {
    Unbinder unbinder = createUnbinder(target);
    View view;
    view = finder.findRequiredView(source, 2130968576, "field 'title'");
    target.title = finder.castView(view, 2130968576, "field 'title'");
    view = finder.findRequiredView(source, 2130968577, "field 'subtitle'");
    target.subtitle = finder.castView(view, 2130968577, "field 'subtitle'");
    view = finder.findRequiredView(source, 2130968578, "field 'hello', method 'sayHello', and method 'sayGetOffMe'");
    target.hello = finder.castView(view, 2130968578, "field 'hello'");
    unbinder.view2130968578 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.sayGetOffMe();
      }
    });
    view = finder.findRequiredView(source, 2130968579, "field 'listOfThings' and method 'onItemClick'");
    target.listOfThings = finder.castView(view, 2130968579, "field 'listOfThings'");
    unbinder.view2130968579 = view;
    ((AdapterView<?>) view).setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> p0, View p1, int p2, long p3) {
        target.onItemClick(p2);
      }
    });
    view = finder.findRequiredView(source, 2130968580, "field 'footer'");
    target.footer = finder.castView(view, 2130968580, "field 'footer'");
    target.headerViews = Utils.listOf(
        finder.<View>findRequiredView(source, 2130968576, "field 'headerViews'"), 
        finder.<View>findRequiredView(source, 2130968577, "field 'headerViews'"), 
        finder.<View>findRequiredView(source, 2130968578, "field 'headerViews'"));
    target.unbinder = unbinder;
  }

  @SuppressWarnings("unchecked")
  protected <U extends Unbinder<T>> U createUnbinder(T target) {
    return (U) new Unbinder(target);
  }

  @SuppressWarnings("unchecked")
  protected <U extends Unbinder<T>> U accessUnbinder(T target) {
    return (U) target.unbinder;
  }

  public static class Unbinder<T extends SimpleActivity> implements ButterKnife.ViewUnbinder<T> {
    private T target;

    View view2130968578;

    View view2130968579;

    protected Unbinder(T target) {
      this.target = target;
    }

    @Override
    public final void unbind() {
      if (target == null) throw new IllegalStateException("Bindings already cleared.");
      unbind(target);
      target = null;
    }

    protected void unbind(T target) {
      target.title = null;
      target.subtitle = null;
      view2130968578.setOnClickListener(null);
      view2130968578.setOnLongClickListener(null);
      target.hello = null;
      ((AdapterView<?>) view2130968579).setOnItemClickListener(null);
      target.listOfThings = null;
      target.footer = null;
      target.headerViews = null;
      target.unbinder = null;
    }
  }
}

ButterKnifeView注入demo(簡單易學)

這個demo的代碼生成,以及接口思想,以及調用生成代碼的思想,都是借鑑ButterKnife的

我先簡單實現了ButterKnife的View注入,去除了ButterKnife繁瑣的編譯校驗,本篇也沒有添加事件注入(下一篇會有)

XUtils 與 ButterKnife實現方式的區別

上篇XUtils實現註解初始化用的是反射 + 動態代理

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。

APT

而ButterKnife使用的是APT (Annotation Processing Tool )

apt是一個命令行工具,與之配套的還有一套用來描述程序語義結構的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在編譯時刻的靜態結構。通過Mirror API可以獲取到被註解的Java類型元素的信息,從而提供相應的處理邏輯。具體的處理工作交給apt工具來完成。編寫註解處理器的核心是AnnotationProcessorFactory和AnnotationProcessor兩個接口。後者表示的是註解處理器,而前者則是爲某些註解類型創建註解處理器的工廠。

  1. APT配置
    需要再compiler Module中的main.resources.META-INF.services下新建文本java.annotation.processing.Processor,在文本內添加Processor全名(我的Processor是com.example.MyProcessor)
    或者直接在Processor實現類上@AutoService(Processor.class)(我還沒實現成功)
    需要在build.gradle裏面配置APT插件(classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’)

  2. Processor類
    (1)JAVA1.6以後的Processor實現類都需要extends AbstractProcessor
    (2)Processor類需要指定其處理的註解
    方式一(類上註解):@SupportedAnnotationTypes(“com.example.AptBetter”)
    方式二(重寫方法):getSupportedAnnotationTypes
    (4)process方法,編譯時進入的主方法,用於分析註解信息,以及生成代碼
    (4)Processor都要指定版本
    一般獲取最新的版本:

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
  3. Process內部元素
    ProcessingEnvironment:編譯環境

    // 用於生成代碼
    Filer filer = processingEnv.getFiler();
    // 用於編譯時給圖
    Messager messager = processingEnv.getMessager();
    // Element工具,可以獲取包名
    Elements elementUtils = processingEnv.getElementUtils();

    RoundEnvironment:回合環境(國內暫時沒有固定的說法,暫且定義爲元素環境)
    因爲可以從中獲取到5大Element

    Mirror的五大Element
    PackageElement 包元素
    TypeElement     類元素
    VariableElement  變量元素
    ExecutableElement  方法元素
    TypeParameterElement  參數元素
    通過這些元素,可以獲取到元素的各種信息:註解、類型、名稱等等

    其中TypeMirror.accept這個我還很模糊,有明白的請賜教

  4. 代碼生成器
    這裏有一個大殺器:JavaPoet (Java詩人) 也是Jake Wharton所參與的著作之一,膜拜Jake Wharthon,膜拜Square。大家可以直接到Github上搜索JavaPoet。

運行期流程

這裏寫圖片描述

主要是通過Butterknife調用生成的Activity$$ViewBinder類中的bind方法,實現了控件初始化、時間注入

Question :

(1)在代碼編寫的時候類都還沒有生成,那麼如何獲取其對象
answer:通過反,Activity也是通過這種方式生成的

Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
ViewBinder viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();

(2)如何能調用bind方法,如何能強轉,因爲生成的代碼實現了ViewBinder接口,接口的力量啊!針對接口編程!

分析compiler

思路分析:
(1)一個類、一個內部類生成一個同包下的名稱相似的IOC容器類
(2)一類中會有很多控件需要初始化,那麼我們把一個類、一個內部類所提供的多個控件信息、類名、包名、id封裝成一個Bean:BindingClass(ButterKnife除了可以給控件初始化之外,還可以給各種資源初始化,甚至可以解綁UnBinding,ButterKnife中把每種類型都封裝成一種Bean,這裏我們緊緊做了View的初始化),我們這裏BindingClass裏面僅僅放的是FieldViewBinding
(3)一個需要初始化的類對應一個BindingClass(含有很多FieldViewBinding),對應生成一個ViewBinder,那麼生成代碼的邏輯我們可以放在Compiler中的BindingClass,作爲其內部類(可以直接獲取BindingClass中的類名、包名、控件集合)

這裏寫圖片描述

編譯器讀信息

// 所有AptBetter的成員變量
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(AptBetter.class);
        // 元素歸類
        for (Element element : elementsAnnotatedWith) {
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            BindingClass bindingClass = null;
            if (allBindEvent.containsKey(typeElement)) {
                bindingClass = allBindEvent.get(typeElement);
            } else {
                String targetType = typeElement.getQualifiedName().toString();
                String classPackage = getPackageName(typeElement);
                // packageName$className$$ViewBinder
                String className = getClassName(typeElement, classPackage) + SUFFIX;

                bindingClass = new BindingClass(classPackage, className, targetType);
                allBindEvent.put(typeElement, bindingClass);
            }
            int value = element.getAnnotation(AptBetter.class).value();
            String elementName = element.getSimpleName().toString();
            TypeName typeName = TypeName.get(element.asType());
            bindingClass.addField(value, new FieldViewBinding(elementName, typeName, value));
        }

代碼生成(JavaPoet)

這裏是用的是JavaPoet,下面是教程地址:

發佈了39 篇原創文章 · 獲贊 9 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章