利用编译时注解实现一个ButterKnife

ButterKnife 这个框架真的很好用,简化了大量的代码,在效率上和手写的代码相比几乎没有损失。虽然会用 ButterKnife

, 同时 ButterKnife 的原理也要明白。

于是,找到 ButterKnife 最早的 1.0 版本,学习一下它的原理,并模仿一下。

最终效果

在 Activity 中使用注解绑定控件

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv)
    public TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Views.inject(this);
        mTextView.setText("TextView");
    }
}

框架根据注解生成的代码如下

public class MainActivity$$ViewInjector {
  public static void inject(MainActivity activity) {
    activity.mTextView = (android.widget.TextView) activity.findViewById(2131427413);
  }
}

项目结构

  • app
  • annotation
  • compiler
  • api

app

就是我们的android项目

annotation

定义注解,InjectView 就放在这个 module 中

compiler

注解解析器,生成代码

api

定义 Butterkinfe 的 API,比如我们常用的 Butterknife.bind()

annotation

在 Android Studio 点击 File - New Module - Java Library 新建一个 Java Module

build.gradle 内容如下

apply plugin: 'java'

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7

然后定义一个注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface InjectView {
    int value();
}

compiler

有了注解,接着就需要一个注解解析器,去解析注解然后生成代码。

同样的,建立一个 Java Library,build.gradle 的定义如下

apply plugin: 'java'

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7

dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile project(':annotation')
}

在这里引入了 Google 的 auto-service 依赖。它可以用来帮助生成 META-INF 文件

接着就是创建注解解析器,实现 process 方法

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        return false;
    }
}

这个方法中,主要做了几件事

  1. 找到所有使用 InjectView 注解的类
  2. 解析 InjectView 注解的信息
  3. 根据信息生成代码

api

创建一个 Android Library,建立一个 Views

import android.app.Activity;

import java.lang.reflect.Method;

public class Views {

    private Views() {
        // No instances.
    }

    public static void inject(Activity activity) {
        try {
            Class<?> injector = Class.forName(activity.getClass().getName() + "$$ViewInjector");
            Method inject = injector.getMethod("inject", activity.getClass());
            inject.invoke(null, activity);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Unable to inject views for activity " + activity, e);
        }
    }
}

因为,我们生成代码的类名是 Activity的类名+$$ViewInjector 。比如一个 Activity 叫 HelloActivity,那么生成的代码的类名就是 HelloActivity$$ViewInjector。因此这里利用反射,根据类名的规则找到我们生成的类,调用 inject() 方法。

当然,也可以不使用反射,直接调用生成好的类 HelloActivity$$ViewInjector.inject(Activity activity),这样也是可以的。但是有个弊端,每次注解完毕只会,都需要 rebuild 一次 project,等代码生成好之后,才能调用 HelloActivity$$ViewInjector.inject(Activity activity)。这样也挺麻烦了,还是直接用注解吧。

app

build.gradle 定义如下

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.okada.viewinject"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile project(':annotation')
    apt project(':compiler')
    compile project(':api')
}

同时在项目的 build.gradle 定义如下

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // 加上这句话
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

现在可以使用注解了

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv)
    public TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Views.inject(this);
        mTextView.setText("TextView");
    }
}

然后点击 Build - Rebuild Project,就可以在 app - build - generated - source - apt 文件夹中看到生成的代码了

项目源码

https://github.com/okadaNana/ViewInject

参考来源

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容