成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

【騰訊Bugly干貨分享】深入理解 ButterKnife,讓你的程序?qū)W會(huì)寫代碼

seasonley / 3335人閱讀

摘要:這似乎是一個(gè)很有意思的話題,如果你的程序足夠聰明,它就可以自己寫代碼那么這么說(shuō)就是要給生成的代碼添加一個(gè)屬性咯不不不,是添加一組注入關(guān)系,后面生成代碼時(shí),注解管理器就需要根據(jù)這些解析來(lái)的關(guān)系來(lái)組織生成的代碼。

本文來(lái)自于騰訊bugly開發(fā)者社區(qū),非經(jīng)作者同意,請(qǐng)勿轉(zhuǎn)載,原文地址:http://dev.qq.com/topic/578753c0c9da73584b025875

0、引子

話說(shuō)我們做程序員的,都應(yīng)該多少是個(gè)懶人,我們總是想辦法驅(qū)使我們的電腦幫我們干活,所以我們學(xué)會(huì)了各式各樣的語(yǔ)言來(lái)告訴電腦該做什么——盡管,他們有時(shí)候也會(huì)誤會(huì)我們的意思。

突然有一天,我覺得有些代碼其實(shí),可以按照某種規(guī)則生成,但你又不能不寫——不是所有的重復(fù)代碼都可以通過(guò)重構(gòu)并采用高端技術(shù)比如泛型來(lái)消除的——比如我最痛恨的代碼:

TextView textView = (TextView) findViewById(R.id.text_view);
Button button = (Button) findViewById(R.id.button);

這樣的代碼,你總不能不寫吧,真是讓人沮喪。突然想到以前背單詞的故事:正著背背不過(guò) C,倒著背背不過(guò) V。嗯,也許寫 Android app,也是寫不過(guò) findViewById 的吧。。

我們今天要介紹的 ButterKnife 其實(shí)就是一個(gè)依托 Java 的注解機(jī)制來(lái)實(shí)現(xiàn)輔助代碼生成的框架,讀完本文,你將能夠了解到 Java 的注解處理器的強(qiáng)大之處,你也會(huì)對(duì) dagger2 和 androidannotations 這樣類似的框架有一定的認(rèn)識(shí)。

1、初識(shí) ButterKnife 1.1 ButterKnife 簡(jiǎn)介

說(shuō)真的,我一直對(duì)于 findViewById 這個(gè)的東西有意見,后來(lái)見到了 Afinal 這個(gè)框架,于是我們就可以直接通過(guò)注解的方式來(lái)注入,哇塞,終于可以跟 findViewById 說(shuō)『Byte Byte』了,真是好開心。

什么?寨見不是介么寫么?

不過(guò),畢竟是移動(dòng)端,對(duì)于用反射實(shí)現(xiàn)注入的 Afinal 之類的框架,我們總是難免有一種發(fā)自內(nèi)心的抵觸,于是。。。

別哭哈,不用反射也可以的~~

這個(gè)世界有家神奇的公司叫做 Square,里面有個(gè)大神叫 Jake Wharton,開源了一個(gè)神奇的框架叫做 ButterKnife,這個(gè)框架雖然也采用了注解進(jìn)行注入,不過(guò)人家可是編譯期生成代碼的方式,對(duì)運(yùn)行時(shí)沒有任何副作用,果真見效快,療效好,只是編譯期有一點(diǎn)點(diǎn)時(shí)間成本而已。

說(shuō)句題外話,現(xiàn)如今做 Android 如果不知道 Jake Wharton,我覺得面試可以直接 Pass 掉了。。。哈哈,開玩笑啦

1.2 ButterKnife 怎么用?

怎么介紹一個(gè)東西,那真是一個(gè)折學(xué)問(wèn)題。別老說(shuō)我沒文化,我的意思是比較曲折嘛。

我們還是要先簡(jiǎn)單介紹一些 ButterKnife 的基本用法,這些知識(shí)你在 ButterKnife 這里也可以看到。

簡(jiǎn)單來(lái)說(shuō),使用 ButterKnife 需要三步走:

配置編譯環(huán)境,由于 Butterknife 用到了注解處理器,所以,比起一般的框架,配置稍微多了些,不過(guò)也很簡(jiǎn)單啦:

buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
      classpath "com.android.tools.build:gradle:1.3.1"
      classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
    }
}
apply plugin: "com.neenbedankt.android-apt"
...
dependencies {
    compile "com.jakewharton:butterknife:8.1.0"
    apt "com.jakewharton:butterknife-compiler:8.1.0"
}

用注解標(biāo)注需要注解的對(duì)象,比如 View,比如一些事件方法(用作 onClick 之類的),例:

@Bind(R.id.title)
TextView title;

@OnClick(R.id.hello)
void sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
    ButterKnife.apply(headerViews, ALPHA_FADE);
}

在初始化布局之后,調(diào)用 bind 方法:

setContentView(R.layout.activity_main);
ButterKnife.bind(this);//一定要在 setContentView 之后哈,不然你就等著玩空指針吧

瞧,這時(shí)候你要是編譯一下,你的代碼就能歡快的跑起來(lái)啦,什么 findViewById,什么 setOnClickListener,我從來(lái)沒聽說(shuō)過(guò)~

哈,不過(guò)你還是要小心一點(diǎn)兒,你要是有本事寫成這樣,ButterKnife 就說(shuō)『信不信我報(bào)個(gè)錯(cuò)給你看啊!』

@Bind(R.id.title)
private TextView title;

@OnClick(R.id.hello)
private void sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
    ButterKnife.apply(headerViews, ALPHA_FADE);
}
Error:(48, 22) error: @Bind fields must not be private or static. (com.example.butterknife.SimpleActivity.title)
Error:(68, 18) error: @OnClick methods must not be private or static. (com.example.butterknife.SimpleActivity.sayHello)

這又是為神馬嘞?如果你知道 ButterKnife 的機(jī)制,那么這個(gè)問(wèn)題就很清晰了,前面我們已經(jīng)提到,ButterKnife 是通過(guò)注解處理器來(lái)生成輔助代碼進(jìn)而達(dá)到自己的注入目的的,那么我們就有必要瞅瞅它究竟生成了什么鬼。

話說(shuō),生成的代碼就在 build/generated/source/apt 下面,我們就以 ButterKnife 的官方 sample 為例,它生成的代碼如下:

讓我們看一下 SimpleActivity$$ViewBinder:

public class SimpleActivity$$ViewBinder implements ViewBinder {
  @Override
  public void bind(final Finder finder, final T target, Object source) {
    Unbinder unbinder = new Unbinder(target);
    View view;
    //注入 title,這里的 target 其實(shí)就是我們的 Activity
    view = finder.findRequiredView(source, 2130968576, "field "title"");
    target.title = finder.castView(view, 2130968576, "field "title"");
    
    //下面注入 hello 這個(gè) Button,并為其設(shè)置 click 事件
    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();
      }
    });
    ...
  }

  ...
}

我們看到這里面有個(gè)叫 bind 的方法,這個(gè)方法跟我們之前調(diào)用的 ButterKnife.bind的關(guān)系可想而知——其實(shí),后者只是個(gè)引子,調(diào)用它就是為了調(diào)用生成的代碼。什么,不信?好吧,我就喜歡你們這些充滿好奇的娃。我們?cè)谡{(diào)用 ButterKnife.bind 之后,會(huì)進(jìn)入下面的方法:

  static void bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class targetClass = target.getClass();
    try {
      ViewBinder viewBinder = findViewBinderForClass(targetClass);
      viewBinder.bind(finder, target, source);
    } catch (Exception e) {
      //省略異常處理
    }
  }

我們知道參數(shù) targetsource 在這里都是咱們的 Activity 的實(shí)例,那么找到的 viewBinder 又是什么鬼呢?

  private static ViewBinder findViewBinderForClass(Class cls)
      throws IllegalAccessException, InstantiationException {
    ViewBinder viewBinder = BINDERS.get(cls);
    //先找緩存
    if (viewBinder != null) {
      return viewBinder;
    }
    //檢查下是否支持這個(gè)類
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      return NOP_VIEW_BINDER;
    }
    try {
      //找到類名為 Activity 的類名加 "$$ViewBinder" 的類,實(shí)例化,并返回
      Class viewBindingClass = Class.forName(clsName + "$$ViewBinder");
      //noinspection unchecked
      viewBinder = (ViewBinder) viewBindingClass.newInstance();
    } catch (ClassNotFoundException e) {
      //注意這里支持了繼承關(guān)系
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    //緩存 viewBinder
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }

簡(jiǎn)單看下注釋就很容易理解了,如果我們的 Activity 名為 SimpleActivity,那么找到的 ViewBinder 應(yīng)該就是 SimpleActivity$$ViewBinder。

還是回到我們前面的問(wèn)題,如果需要注入的成員是 private,ButterKnife 會(huì)報(bào)錯(cuò),顯然,如果 titleprivate,生成的代碼中又寫到 target.title,這不就是在搞笑么?小樣兒,你以為你是生成的代碼, Java 虛擬機(jī)就會(huì)讓你看見不該看的東西么?

當(dāng)然,對(duì)需要注入的成員的要求不止這些啦,我們稍后就會(huì)知道,其實(shí)對(duì)于靜態(tài)成員和某些特定包下的類的成員也是不支持注入的。

1.3 小結(jié)

這個(gè)框架給我們的感覺就是,用起來(lái)炒雞簡(jiǎn)單有木有。說(shuō)話想當(dāng)年,@ 給了我們上網(wǎng)沖浪的感覺,現(xiàn)在,我們?nèi)匀恢恍枰诖a里面 @ 幾下,就可以在后面各種浪了。

等等,這么簡(jiǎn)單的表象后面,究竟隱藏著怎樣的秘密?它那光鮮的外表下面又有那些不可告人的故事?請(qǐng)看下回分解。

2、ButterKnife,給我上一盤蛋炒飯

Jake 大神,我賭一個(gè)月好萊塢會(huì)員,你一定是一個(gè)吃貨。。

我們把生成代碼這個(gè)過(guò)程比作一次蛋炒飯,在炒的時(shí)候你要先準(zhǔn)備炊具,接著準(zhǔn)備用料,然后開炒,出鍋。

2.1 準(zhǔn)備炊具

蛋炒飯是在鍋里面炒出來(lái)的,那么我們的 ButterKnife 的"鍋"又是什么鬼?

閑話少敘,且說(shuō)從我們配置好的注解,到最終生成的代碼,這是個(gè)怎樣的過(guò)程呢?

上圖很清晰嘛,雖然什么都沒說(shuō)。額。。別動(dòng)手。。

你看圖里面 ButterKnife 很厲害的樣子,其實(shí)丫是仗勢(shì)欺人。仗誰(shuí)的勢(shì)呢?我們千呼萬(wàn)喚始出來(lái)滴注解處理器,這時(shí)候就要登上歷史舞臺(tái)啦!

話說(shuō) Java 編譯器編譯代碼之前要先來(lái)個(gè)預(yù)處理,這時(shí)候編譯器會(huì)對(duì) classpath 下面有下圖所示配置的注解處理器進(jìn)行調(diào)用,那么這時(shí)候我們就可以干壞事兒了(怎么每到這個(gè)時(shí)候都會(huì)很興奮呢。。)

所以,如果你要自己寫注解處理器的話,首先要繼承 AbstractProcessor ,然后寫下類似的配置。不過(guò)稍等一下,讓我們看下 Butterknife 是怎么做的:

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
    ...
}

AutoService 是干什么的呢?看看剛才的圖,有沒有注意到那個(gè)文件夾是紅色?是的,它是自動(dòng)生成的,而負(fù)責(zé)生成這個(gè)配置的家伙就是 AutoService,這是 google 的一個(gè)開源組件,非常簡(jiǎn)單,我就不多說(shuō)了。

簡(jiǎn)而言之:注解處理器為我們打開了一扇門,讓我們可以在 Java 編譯器編譯代碼之前,執(zhí)行一段我們的代碼。當(dāng)然這代碼也不一定就是要生成別的代碼了,你可以去檢查那些被注解標(biāo)注的代碼的命名是否規(guī)范(周志明大神的 《深入理解 Java 虛擬機(jī)》一書當(dāng)中有這個(gè)例子)。啊,你說(shuō)你要去輸出一個(gè) “Hello World”,~~(╯﹏╰)b 也可以。。吧。。

2.2 嘿蛋炒飯,最簡(jiǎn)單又最困難

既然知道了程序的入口,那么我們就要來(lái)看看 ButterKnife 究竟干了什么見不得人的事兒。在這里,所有的輸入就是我們?cè)谧约旱拇a中配置的注解,所有的輸出,就是生成的用于注入對(duì)象的輔助代碼。

關(guān)于注解處理器的更多細(xì)節(jié)請(qǐng)大家參考相應(yīng)的資料哈,我這里直接給出 ButterKnife 的核心代碼,在 ButterKnifeProcessor.process 當(dāng)中:

  @Override public boolean process(Set elements, RoundEnvironment env) {
    //下面這一句解析注解
    Map targetClassMap = findAndParseTargets(env);
    
    //解析完成以后,需要生成的代碼結(jié)構(gòu)已經(jīng)都有了,它們都存在于每一個(gè) BindingClass 當(dāng)中
    for (Map.Entry entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      try {
        //這一步完成真正的代碼生成
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }

    return true;
  }

我們知道,ButterKnife 對(duì)于需要注入對(duì)象的成員有要求的,在解析注解配置時(shí),首先要對(duì)被標(biāo)注的成員進(jìn)行檢查,如果檢查失敗,直接拋異常。

在分析解析過(guò)程時(shí),我們以 @Bind 為例,注解處理器找到用 @Bind 標(biāo)注的成員,檢驗(yàn)這些成員是否符合注入的條件(比如不能是 private,不能是 static 之類),之后將注解當(dāng)中的值取出來(lái),創(chuàng)建或者更新對(duì)應(yīng)的 BindingClass。

  private Map findAndParseTargets(RoundEnvironment env) {
    Map targetClassMap = new LinkedHashMap<>();
    Set erasedTargetNames = new LinkedHashSet<>();
    
    // Process each @Bind element.
    for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseBind(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, Bind.class, e);
      }
    }
    ...
    return targetClassMap;
  }

現(xiàn)在以前面提到的 title 為例,解析的時(shí)候拿到的 element 其實(shí)對(duì)應(yīng)的就是 title 這個(gè)變量。

  private void parseBind(Element element, Map targetClassMap,
      Set erasedTargetNames) {

    ... 省略掉檢驗(yàn) element 是否符合條件的代碼 ...
    
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.ARRAY) {
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (LIST_TYPE.equals(doubleErasure(elementType))) {
      parseBindMany(element, targetClassMap, erasedTargetNames);
    } else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
    // 顯然這里被注入的對(duì)象類型不能是 Iterable,List 除外~
      error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
          ((TypeElement) element.getEnclosingElement()).getQualifiedName(),
          element.getSimpleName());
    } else {
      parseBindOne(element, targetClassMap, erasedTargetNames);
    }
  }

在注入 title 時(shí),對(duì)應(yīng)的要接著執(zhí)行 parseBindOne 方法:

  private void parseBindOne(Element element, Map targetClassMap,
      Set erasedTargetNames) {
    ... 省略掉一些校驗(yàn)代碼 ...
    
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
        ... 處理錯(cuò)誤,顯然被注入的必須是 View 的子類 ...
    }

    // Assemble information on the field.
    int[] ids = element.getAnnotation(Bind.class).value();
    if (ids.length != 1) {
       ... 以前已經(jīng)確認(rèn)是單值綁定,所以出現(xiàn)了參數(shù)為多個(gè)的情況就報(bào)錯(cuò)...
    }

    ... 省略構(gòu)建 BindingClass 對(duì)象的代碼 ...
    BindingClass bindingClass = targetClassMap.get(enclosingElement);

    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    // 根據(jù)注解信息來(lái)生成注入關(guān)系,并添加到 bindingClass 當(dāng)中
    FieldViewBinding binding = new FieldViewBinding(name, type, required);
    bindingClass.addField(id, binding);

    ...
  }

其實(shí)每一個(gè)注解的解析流程都是類似的,解析的最終目標(biāo)就是在這個(gè) bindingClassaddField,這意味著什么呢?

通過(guò)前面的分析,其實(shí)我們已經(jīng)知道解析注解的最終目標(biāo)是生成那些用于注入的代碼,這就好比我們讓注解管理器寫代碼。這似乎是一個(gè)很有意思的話題,如果你的程序足夠聰明,它就可以自己寫代碼~~

那么這么說(shuō) addField 就是要給生成的代碼添加一個(gè)屬性咯?不不不,是添加一組注入關(guān)系,后面生成代碼時(shí),注解管理器就需要根據(jù)這些解析來(lái)的關(guān)系來(lái)組織生成的代碼。所以,要不要再看一下生成的代碼,看看還有沒有新的發(fā)現(xiàn)?

2.3、出鍋咯

話說(shuō),注解配置已經(jīng)解析完畢,我們已經(jīng)知道我們要生成的代碼長(zhǎng)啥樣了,那么下一個(gè)問(wèn)題就是如何真正的生成代碼。這里用到了一個(gè)工具 JavaPoet,同樣出自 Square 的大神之手。JavaPoet 提供了非常強(qiáng)大的代碼生成功能,比如我們下面將給出生成輸出 HelloWorld 的 JavaDemo 的代碼:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

這樣就可以生成下面的代碼了:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

其實(shí)我們自己寫個(gè)程序生成一些代碼并不難,不過(guò)導(dǎo)包這個(gè)事情卻非常的令人焦灼,別擔(dān)心,JavaPoet 可以把這些統(tǒng)統(tǒng)搞定。

有了個(gè)簡(jiǎn)單的認(rèn)識(shí)之后,我們要看下 ButterKnife 都用 JavaPoet 干了什么。還記得下面的代碼么:

bindingClass.brewJava().writeTo(filer);

這句代碼將 brew 出來(lái)的什么鬼東西寫到了 filer 當(dāng)中,Filer 嘛發(fā)揮想象力就知道是類似于文件的東西,換句話說(shuō),這句代碼就是完成代碼生成到指定文件的過(guò)程。

Brew Java !!

~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[ ]P coffee! [ ]P

 JavaFile brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(className)
    //添加修飾符為 public,生成的類是 public 的
        .addModifiers(PUBLIC)
        .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));

    /*其實(shí) Bind 過(guò)程也是有繼承關(guān)系的,我有一個(gè) Activity A 有注入,另一個(gè) B 繼承它,那么生成注入 B 的成員的代碼時(shí),就要把 A 的注入一起捎上*/
    if (parentViewBinder != null) {
      result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentViewBinder),
          TypeVariableName.get("T")));
    } else {
      result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
    }

    if (hasUnbinder()) {
      result.addType(createUnbinderClass());
    }

    //這一句很關(guān)鍵,我們的絕大多數(shù)注入用到的代碼都在這里了
    result.addMethod(createBindMethod());

    //輸出一個(gè) JavaFile 對(duì)象(其實(shí)這里離生成最終的代碼已經(jīng)很近了),完工
    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

現(xiàn)在我們需要繼續(xù)看下 createBindMethod 方法,這個(gè)方法是生成代碼的關(guān)鍵~

 private MethodSpec createBindMethod() {
    /*創(chuàng)建了一個(gè)叫做 bind 的方法,添加了 @Override 注解,方法可見性為 public
     以及一些參數(shù)類型 */
    MethodSpec.Builder result = MethodSpec.methodBuilder("bind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC)
        .addParameter(FINDER, "finder", FINAL)
        .addParameter(TypeVariableName.get("T"), "target", FINAL)
        .addParameter(Object.class, "source");

    if (hasResourceBindings()) {
      // Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
      result.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
          .addMember("value", "$S", "ResourceType")
          .build());
    }
    
    // Emit a call to the superclass binder, if any.
    if (parentViewBinder != null) {
      result.addStatement("super.bind(finder, target, source)");
    }
    
    /* 關(guān)于 unbinder,我們一直都沒有提到過(guò),如果我們有下面的注入配置:
        @Unbinder
        ButterKnife.Unbinder unbinder;
    * 那么這時(shí)候就會(huì)在生成的代碼中添加下面的代碼,這實(shí)際上就是構(gòu)造 unbinder
    */
    // If the caller requested an unbinder, we need to create an instance of it.
    if (hasUnbinder()) {
      result.addStatement("$T unbinder = new $T($N)", unbinderBinding.getUnbinderClassName(),
          unbinderBinding.getUnbinderClassName(), "target");
    }


    /*
    * 這里就是注入 view了,addViewBindings 這個(gè)方法其實(shí)就生成功能上類似
        TextView textView = (TextView) findViewById(...) 的代碼
    */
    if (!viewIdMap.isEmpty() || !collectionBindings.isEmpty()) {
      // Local variable in which all views will be temporarily stored.
      result.addStatement("$T view", VIEW);

      // Loop over each view bindings and emit it.
      for (ViewBindings bindings : viewIdMap.values()) {
        addViewBindings(result, bindings);
      }

      // Loop over each collection binding and emit it.
      for (Map.Entry entry : collectionBindings.entrySet()) {
        emitCollectionBinding(result, entry.getKey(), entry.getValue());
      }
    }

    /*
    * 注入 unbinder
    */ 
    // Bind unbinder if was requested.
    if (hasUnbinder()) {
      result.addStatement("target.$L = unbinder", unbinderBinding.getUnbinderFieldName());
    }

    /* ButterKnife 其實(shí)不止支持注入 View, 還支持注入 字符串,主題,圖片。。
    * 所有資源里面你能想象到的東西
    */
    if (hasResourceBindings()) {
        //篇幅有限,我還是省略掉他們吧
        ...
    }

    return result.build();
  }

不知道為什么,這段代碼讓我想起了我寫代碼的樣子。。那分明就是 ButterKnife 在替我們寫代碼嘛。

當(dāng)然,這只是生成的代碼中最重要的最核心的部分,為了方便理解,我把 demo 里面生成的這個(gè)方法列出來(lái)方便查看:

  @Override
  public void bind(final Finder finder, final T target, Object source) {
    //構(gòu)造 unbinder
    Unbinder unbinder = new Unbinder(target);
    //下面開始 注入 view
    View view;
    view = finder.findRequiredView(source, 2130968576, "field "title"");
    target.title = finder.castView(view, 2130968576, "field "title"");
    //... 省略掉其他成員的注入 ...
    //注入 unbinder
    target.unbinder = unbinder;
  }
3、Hack 一下,定義我們自己的注解 BindLayout

我一直覺得,既然 View 都能注入了,咱能不能把 layout 也注入了呢?顯然這沒什么難度嘛,可為啥 Jake 大神沒有做這個(gè)功能呢?我覺得主要是因?yàn)椤?。。你想哈,你注入個(gè) layout,大概要這么寫

@BindLayout(R.layout.main)
public class AnyActivity extends Activity{...}

可我們平時(shí)怎么寫呢?

public class AnyActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstances){
        super.onCreate(savedInstances);
        setContentView(R.layout.main);
    }
}

你別說(shuō)你不繼承 onCreate 方法啊,所以好像始終要寫一句,性價(jià)比不高?誰(shuí)知道呢。。。

不過(guò)呢,咱們接下來(lái)就運(yùn)用我們的神功,給 ButterKnife 添磚加瓦(這怎么感覺像校長(zhǎng)說(shuō)的呢。。嗯,他說(shuō)的是社河會(huì)蟹主@義),讓 ButterKnife 可以 @BindLayout。先看效果:

//注入 layout
@BindLayout(R.layout.simple_activity)
public class SimpleActivity extends Activity {
    ...
}

生成的代碼:

public class SimpleActivity$$ViewBinder implements ViewBinder {
  @Override
  public void bind(final Finder finder, final T target, Object source) {
    //生成了這句代碼來(lái)注入 layout
    target.setContentView(2130837504);
    //下面省略掉的代碼我們已經(jīng)見過(guò)啦,就是注入 unbinder,注入 view
    ...
  }
  
  ...
}

那么我們要怎么做呢?一個(gè)字,順藤摸瓜~

第一步,當(dāng)然是要定義注解 BindLayout

@Retention(CLASS) @Target(TYPE)
public @interface BindLayout {
    @LayoutRes int value();
}

第二步,我們要去注解處理器里面添加對(duì)這個(gè)注解的支持:

 @Override public Set getSupportedAnnotationTypes() {
    Set types = new LinkedHashSet<>();
    ...
    types.add(BindLayout.class.getCanonicalName());
    ...
    return types;
  }

第三步,注解處理器的解析環(huán)節(jié)要添加支持:

private Map findAndParseTargets(RoundEnvironment env) {
    Map targetClassMap = new LinkedHashMap<>();
    Set erasedTargetNames = new LinkedHashSet<>();

    // Process each @Bind element.
    for (Element element : env.getElementsAnnotatedWith(BindLayout.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
          parseBindLayout(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
          logParsingError(element, BindLayout.class, e);
      }
    }
    ...
}

下面是 parseBindLayout 方法:

private void parseBindLayout(Element element, Map targetClassMap, Set erasedTargetNames) {
    /*與其他注解解析不同,BindLayout 標(biāo)注的類型就是 TYPE,所以這里直接強(qiáng)轉(zhuǎn)為 
     TypeElement,其實(shí)就是對(duì)應(yīng)于 Activity 的類型*/
    TypeElement typeElement = (TypeElement) element;
    Set modifiers = element.getModifiers();
    
    // 只有 private 不可以訪問(wèn)到,static 類型不影響,這也是與其他注解不同的地方
    if (modifiers.contains(PRIVATE)) {
        error(element, "@%s %s must not be private. (%s.%s)",
                BindLayout.class.getSimpleName(), "types", typeElement.getQualifiedName(),
                element.getSimpleName());
        return;
    }
    
    // 同樣的,對(duì)于 android 開頭的包內(nèi)的類不予支持
    String qualifiedName = typeElement.getQualifiedName().toString();
    if (qualifiedName.startsWith("android.")) {
        error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
                BindLayout.class.getSimpleName(), qualifiedName);
        return;
    }
    
    // 同樣的,對(duì)于 java 開頭的包內(nèi)的類不予支持
    if (qualifiedName.startsWith("java.")) {
        error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
                BindLayout.class.getSimpleName(), qualifiedName);
        return;
    }

    /* 我們暫時(shí)只支持 Activity,如果你想支持 Fragment,需要區(qū)別對(duì)待哈,
    因?yàn)槎叱跏蓟?View 的代碼不一樣 */
    if(!isSubtypeOfType(typeElement.asType(), ACTIVITY_TYPE)){
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
                BindLayout.class.getSimpleName(), typeElement.getQualifiedName(), element.getSimpleName());
        return;
    }
    
    // 拿到注解傳入的值,比如 R.layout.main
    int layoutId = typeElement.getAnnotation(BindLayout.class).value();
    if(layoutId == 0){
        error(element, "@%s for a Activity must specify one layout ID. Found: %s. (%s.%s)",
                BindLayout.class.getSimpleName(), layoutId, typeElement.getQualifiedName(),
                element.getSimpleName());
        return;
    }
    
    BindingClass bindingClass = targetClassMap.get(typeElement);
    if (bindingClass == null) {
        bindingClass = getOrCreateTargetClass(targetClassMap, typeElement);
    }
    
    // 把這個(gè)布局的值塞給 bindingClass,這里我只是簡(jiǎn)單的存了下這個(gè)值
    bindingClass.setContentLayoutId(layoutId);
    log(element, "element:" + element + "; targetMap:" + targetClassMap + "; erasedNames: " + erasedTargetNames);
}

第四步,添加相應(yīng)的生成代碼的支持,這個(gè)在 BindingClass.createBindMethod 當(dāng)中:

  private MethodSpec createBindMethod() {
    MethodSpec.Builder result = MethodSpec.methodBuilder("bind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC)
        .addParameter(FINDER, "finder", FINAL)
        .addParameter(TypeVariableName.get("T"), "target", FINAL)
        .addParameter(Object.class, "source");

    
    if (hasResourceBindings()) {
        ... 省略之 ...
    }
    
    //如果 layoutId 不為 0 ,那說(shuō)明有綁定,添加一句 setContentView 完事兒~~
    //要注意的是,這句要比 view 注入在前面。。。你懂的,不然自己去玩空指針
    if(layoutId != 0){
      result.addStatement("target.setContentView($L)", layoutId);
    }

    ...
}

這樣,我們就可以告別 setContentView 了,寫個(gè)注解,非常清爽,隨意打開個(gè) Activity 一眼就看到了布局在哪里,哈哈哈哈哈

其實(shí)是說(shuō)你胖。。

4、androidannotations 和 dagger2 4.1 androidannotations

androidannotations 同樣是一個(gè)注入工具,如果你稍微接觸一下它,你就會(huì)發(fā)現(xiàn)它的原理與 ButterKnife 如出一轍。下面我們給出其中非常核心的代碼:

    private void processThrowing(Set annotations, RoundEnvironment roundEnv) throws Exception {
        if (nothingToDo(annotations, roundEnv)) {
            return;
        }

        AnnotationElementsHolder extractedModel = extractAnnotations(annotations, roundEnv);
        AnnotationElementsHolder validatingHolder = extractedModel.validatingHolder();
        androidAnnotationsEnv.setValidatedElements(validatingHolder);

        try {
            AndroidManifest androidManifest = extractAndroidManifest();
            LOGGER.info("AndroidManifest.xml found: {}", androidManifest);

            IRClass rClass = findRClasses(androidManifest);

            androidAnnotationsEnv.setAndroidEnvironment(rClass, androidManifest);

        } catch (Exception e) {
            return;
        }

        AnnotationElements validatedModel = validateAnnotations(extractedModel, validatingHolder);

        ModelProcessor.ProcessResult processResult = processAnnotations(validatedModel);

        generateSources(processResult);
    }

我們就簡(jiǎn)單看下,其實(shí)也是注解解析和代碼生成幾個(gè)步驟,當(dāng)然,由于 androidannotations 支持的功能要復(fù)雜的多,不僅僅包含 UI 注入,還包含線程切換,網(wǎng)絡(luò)請(qǐng)求等等,因此它的注解解析邏輯也要復(fù)雜得多,閱讀它的源碼時(shí),建議多多關(guān)注一下它的代碼結(jié)構(gòu)設(shè)計(jì),非常不錯(cuò)。

從使用的角度來(lái)說(shuō),ButterKnife 只是針對(duì) UI 進(jìn)行注入,功能比較單一,而 androidannotations 真是有些龐大和強(qiáng)大,究竟使用哪一個(gè)框架,那要看具體需求了。

4.2 Dagger 2

Dagger 2 算是超級(jí)富二代了,媽是 Square,爹是 Google—— Dagger 2 源自于 Square 的開源項(xiàng)目,目前已經(jīng)由 Google 接管(怎么感覺 Google 喜當(dāng)?shù)墓?jié)奏 →_→)。

Dagger 本是一把利刃,它也是用來(lái)注入成員的一個(gè)框架,不過(guò)相對(duì)于前面的兩個(gè)框架,它

顯得更基礎(chǔ),因?yàn)樗会槍?duì)具體業(yè)務(wù)

顯得更通用,因?yàn)樗灰蕾囘\(yùn)行平臺(tái)

顯得更復(fù)雜,因?yàn)樗P(guān)注于對(duì)象間的依賴關(guān)系

用它的開發(fā)者說(shuō)的一句話就是(大意):有一天,我們發(fā)現(xiàn)我們的構(gòu)造方法居然需要 3000 行,這時(shí)候我們意識(shí)到是時(shí)候?qū)懸粋€(gè)框架幫我們完成構(gòu)造方法了。

換句話說(shuō),如果你的構(gòu)造方法沒有那么長(zhǎng),其實(shí)也沒必要引入 Dagger 2,因?yàn)槟菢訒?huì)讓你的代碼顯得。。。不是那么的好懂。

當(dāng)然,我們放到這里提一下 Dagger 2,是因?yàn)樗?完全去反射,實(shí)現(xiàn)的思想與前面提到的兩個(gè)框架也是一毛一樣啊。所以你可以不假思索的說(shuō),Dagger 2 肯定至少有兩個(gè)模塊,一個(gè)是 compiler,里面有個(gè)注解處理器;還有一個(gè)是運(yùn)行時(shí)需要依賴的模塊,主要提供 Dagger 2 的注解支持等等。

5、小結(jié)

本文通過(guò)對(duì) ButterKnife 的源碼的分析,我們了解到了 ButterKnife 這樣的注入框架的實(shí)現(xiàn)原理,同時(shí)我們也對(duì) Java 的注解處理機(jī)制有了一定的認(rèn)識(shí);接著我們還對(duì) ButterKnife 進(jìn)行了擴(kuò)充的簡(jiǎn)單嘗試——總而言之,使用起來(lái)非常簡(jiǎn)單的 ButterKnife 框架的實(shí)現(xiàn)實(shí)際上涉及了較多的知識(shí)點(diǎn),這些知識(shí)點(diǎn)相對(duì)生僻,卻又非常的強(qiáng)大,我們可以利用這些特性來(lái)實(shí)現(xiàn)各種各樣個(gè)性化的需求,讓我們的工作效率進(jìn)一步提高。

來(lái)吧,解放我們的雙手!

更多精彩內(nèi)容歡迎關(guān)注bugly的微信公眾賬號(hào):

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/66215.html

相關(guān)文章

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<