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
就是我们的android项目
定义注解,InjectView
就放在这个 module 中
注解解析器,生成代码
定义 Butterkinfe 的 API,比如我们常用的 Butterknife.bind()
在 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();
}
有了注解,接着就需要一个注解解析器,去解析注解然后生成代码。
同样的,建立一个 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;
}
}
这个方法中,主要做了几件事
- 找到所有使用 InjectView 注解的类
- 解析 InjectView 注解的信息
- 根据信息生成代码
创建一个 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)。这样也挺麻烦了,还是直接用注解吧。
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 文件夹中看到生成的代码了