Butterknife 源碼剖析(一)

源碼剖析——ButterKnife的工作流程

butterknife是一個android視圖快速注入庫,它通過給view字段添加java注解捏题,可以讓我們丟掉findViewById()來獲取view的方法,從而簡化了代碼罩旋。本文是基于7.0.1版本的剖析菱阵。

首先來看下注解方式:
1、標(biāo)準(zhǔn)Annotation
標(biāo)準(zhǔn)的Annotation咪橙,我們經(jīng)常用的@Override、@Deprecated虚倒、@SuppressWarnings美侦,這些是java自帶的幾個Annotation,分別表示重寫函數(shù)魂奥、不鼓勵使用菠剩、忽略某項(xiàng)Warning。

2耻煤、元Annotation
元Annotation是指用來定義Annotation的Annotation具壮,一般我們自定義Annotation時就會用到准颓。主要包括以下幾個:

  • @Documented是否會保存到Javadoc文檔中
  • @Retention保留時間,可選值SOURCE(源碼時)棺妓,CLASS(編譯時)攘已,RUNTIME(運(yùn)行時),默認(rèn)為CLASS怜跑,值為SOURCE大都為MarkAnnotation样勃,這類Annotation大都用來校驗(yàn),比如Override,Deprecated,SuppressWarnings
  • @Target可以用來修飾哪些程序元素妆艘,如TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER等彤灶,未標(biāo)注則表示可修飾所有
  • @Inherited是否可以被繼承,默認(rèn)為false

OK,我們來看看butterknife的@Bind注解,@Retention是編譯時旱幼,@Target是字段倘要。

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
 /** View ID to which the field will be bound. */
 int[] value();
}

編譯時 Annotation 指 @Retention 為 CLASS 的 Annotation,由apt(Annotation Processing Tool) 解析自動解析。ButterKnife便是用了Java Annotation Processing技術(shù),就是在Java代碼編譯成Java字節(jié)碼的時候就已經(jīng)處理了@Bind、@OnClick(ButterKnife還支持很多其他的注解)這些注解了心例。

你可以你定義注解,并且自己定義解析器來處理它們鞋囊。Annotation processing是在編譯階段執(zhí)行的止后,它的原理就是讀入Java源代碼,解析注解溜腐,然后生成新的Java代碼译株。新生成的Java代碼最后被編譯成Java字節(jié)碼,注解解析器(Annotation Processor)不能改變讀入的Java 類挺益,比如不能加入或刪除Java方法歉糜。

ButterKnife 工作流程
當(dāng)你編譯你的Android工程時,ButterKnife工程中ButterKnifeProcessor類的process()方法會執(zhí)行以下操作:

  • 開始它會掃描Java代碼中所有的ButterKnife注解@Bind望众、@OnClick匪补、@OnItemClicked等。
  • 當(dāng)它發(fā)現(xiàn)一個類中含有任何一個注解時烂翰,ButterKnifeProcessor會幫你生成一個Java類夯缺,名字類似$$ViewBinder,這個新生成的類實(shí)現(xiàn)了ViewBinder接口甘耿。
  • 這個ViewBinder類中包含了所有對應(yīng)的代碼踊兜,比如@Bind注解對應(yīng)findViewById(), @OnClick對應(yīng)了view.setOnClickListener()等等。
  • 最后當(dāng)Activity啟動ButterKnife.bind(this)執(zhí)行時棵里,ButterKnife會去加載對應(yīng)的ViewBinder類調(diào)用它們的bind()方法润文。

來看個使用butterknife的例子:

@Bind(R.id.button)Button mButton;

@Override
protected voidon Create(Bundlesaved InstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
}

@OnClick(R.id.button) void clickButton() {
    Toast.makeText(MainActivity.this,“HelloWorld!”,Toast.LENGTH_SHORT).show();
}

onCreate()方法中調(diào)用了ButterKnife.bind(this)我們點(diǎn)進(jìn)去看看:

public static void bind(Activity target) {
    bind(target, target, Finder.ACTIVITY);
}

調(diào)用了bind方法,參數(shù)分別為activity和Finder殿怜,繼續(xù)點(diǎn)進(jìn)去:

static void bind(Object target, Object source, Finder finder) {
  Class<?> targetClass = target.getClass();
  try {
    if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
    //查找ViewBinder類
    ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
    if (viewBinder != null) {
      viewBinder.bind(finder, target, source);
    }
  } catch (Exception e) {
    throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
  }
 }

第5行findViewBinderForClass應(yīng)該是去查找ViewBinder類典蝌,點(diǎn)進(jìn)去:

private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
      throws IllegalAccessException, InstantiationException {
    //從內(nèi)存中查找
    ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder;
    }
    String clsName = cls.getName();
    //檢查是否為framework class
    if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    try {
      //實(shí)例化“MainActivity$$ViewBinder”這樣的類
      Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
      //noinspection unchecked
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
      if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      //異常,則去父類查找
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    //放入內(nèi)存并返回
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }

第3行是首先從內(nèi)存中查找头谜,看BINDERS的定義:

static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<Class<?>, ViewBinder<Object>>();

內(nèi)存中沒有骏掀,第9行判斷如果framework class,就放棄查找并返回ViewBinder的空實(shí)現(xiàn)實(shí)例柱告。

  public static final String ANDROID_PREFIX = "android.";
  public static final String JAVA_PREFIX = "java.";
  static final ViewBinder<Object> NOP_VIEW_BINDER = new ViewBinder<Object>() {
    @Override public void bind(Finder finder, Object target, Object source) { }
    @Override public void unbind(Object target) { }
  };

第14行是去實(shí)例化“MainActivity$$ViewBinder”這樣的類截驮,如果viewBinder不為空,放入緩存并返回际度;如果ClassNotFoundException異常則去父類查找葵袭。

  public static final String SUFFIX = "$$ViewBinder";

我們回到上面的bind()方法,查找到的ViewBinder不為空乖菱,則執(zhí)行viewBinder.bind(finder,target,source)方法坡锡。

當(dāng)我們編譯運(yùn)行后,在build文件夾下找到MainActivity$$ViewBinder.java類窒所,具體代碼為:

public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
    target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(android.view.View p0) {
          target.clickButton();
        }
      });
  }

  @Override public void unbind(T target) {
    target.mButton = null;
  }
}

最終鹉勒,bind()方法會執(zhí)行到MainActivity$$ViewBinder.java類中的bind()方法。

那么吵取,MainActivity$$ViewBinder.java類是如何生成的呢禽额?這個在下篇 編譯期解析注解、生成java代碼流程 中揭曉皮官。

在上面的過程中可以看到脯倒,為什么你用@Bind、@OnClick等注解標(biāo)注的屬性或方法必須是public或protected的臣疑,因?yàn)锽utterKnife是通過ExampleActivity.this.button來注入View的盔憨。

為什么要這樣呢?有些注入框架比如roboguice你是可以把View設(shè)置成private的讯沈,答案就是性能郁岩。如果你把View設(shè)置成private,那么框架必須通過反射來注入View缺狠,一個很大的缺點(diǎn)就是在Activity運(yùn)行時大量使用反射會影響App的運(yùn)行性能问慎,造成卡頓以及生成很多臨時Java對象更容易觸發(fā)GC,不管現(xiàn)在手機(jī)的CPU處理器變得多快挤茄,如果有些操作會影響性能如叼,那么是肯定要避免的,這就是ButterKnife與其他注入框架的不同穷劈。

轉(zhuǎn)載請標(biāo)明出處:http://www.reibang.com/p/95d4f0eb6027

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笼恰,一起剝皮案震驚了整個濱河市踊沸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌社证,老刑警劉巖逼龟,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異追葡,居然都是意外死亡腺律,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門宜肉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匀钧,“玉大人,你說我怎么就攤上這事谬返≈梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵遣铝,是天一觀的道長吊圾。 經(jīng)常有香客問我,道長翰蠢,這世上最難降的妖魔是什么项乒? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮梁沧,結(jié)果婚禮上檀何,老公的妹妹穿的比我還像新娘。我一直安慰自己廷支,他們只是感情好频鉴,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恋拍,像睡著了一般垛孔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上施敢,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天周荐,我揣著相機(jī)與錄音,去河邊找鬼僵娃。 笑死概作,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的默怨。 我是一名探鬼主播讯榕,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愚屁?” 一聲冷哼從身側(cè)響起济竹,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霎槐,沒想到半個月后规辱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栽燕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了改淑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碍岔。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朵夏,靈堂內(nèi)的尸體忽然破棺而出蔼啦,到底是詐尸還是另有隱情,我是刑警寧澤仰猖,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布捏肢,位于F島的核電站,受9級特大地震影響饥侵,放射性物質(zhì)發(fā)生泄漏鸵赫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一躏升、第九天 我趴在偏房一處隱蔽的房頂上張望辩棒。 院中可真熱鬧,春花似錦膨疏、人聲如沸一睁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽者吁。三九已至,卻和暖如春饲帅,著一層夾襖步出監(jiān)牢的瞬間复凳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工灶泵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留染坯,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓丘逸,卻偏偏與公主長得像单鹿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子深纲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容