项目作者: okadaNana

项目描述 :
butterknife 1.0
高级语言: Java
项目地址: git://github.com/okadaNana/ViewInject.git
创建时间: 2017-03-26T07:08:27Z
项目社区:https://github.com/okadaNana/ViewInject

开源协议:

下载


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

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

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

最终效果

在 Activity 中使用注解绑定控件

  1. public class MainActivity extends AppCompatActivity {
  2. @InjectView(R.id.tv)
  3. public TextView mTextView;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. Views.inject(this);
  9. mTextView.setText("TextView");
  10. }
  11. }

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

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

项目结构

  • 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 内容如下

  1. apply plugin: 'java'
  2. sourceCompatibility = JavaVersion.VERSION_1_7
  3. targetCompatibility = JavaVersion.VERSION_1_7

然后定义一个注解

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. @Retention(RetentionPolicy.CLASS)
  6. @Target(ElementType.FIELD)
  7. public @interface InjectView {
  8. int value();
  9. }

compiler

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

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

  1. apply plugin: 'java'
  2. sourceCompatibility = JavaVersion.VERSION_1_7
  3. targetCompatibility = JavaVersion.VERSION_1_7
  4. dependencies {
  5. compile 'com.google.auto.service:auto-service:1.0-rc2'
  6. compile project(':annotation')
  7. }

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

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

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

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

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

api

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

  1. import android.app.Activity;
  2. import java.lang.reflect.Method;
  3. public class Views {
  4. private Views() {
  5. // No instances.
  6. }
  7. public static void inject(Activity activity) {
  8. try {
  9. Class<?> injector = Class.forName(activity.getClass().getName() + "$$ViewInjector");
  10. Method inject = injector.getMethod("inject", activity.getClass());
  11. inject.invoke(null, activity);
  12. } catch (RuntimeException e) {
  13. throw e;
  14. } catch (Exception e) {
  15. throw new RuntimeException("Unable to inject views for activity " + activity, e);
  16. }
  17. }
  18. }

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

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

app

build.gradle 定义如下

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'com.neenbedankt.android-apt'
  3. android {
  4. compileSdkVersion 24
  5. buildToolsVersion "25.0.2"
  6. defaultConfig {
  7. applicationId "com.okada.viewinject"
  8. minSdkVersion 14
  9. targetSdkVersion 24
  10. versionCode 1
  11. versionName "1.0"
  12. }
  13. buildTypes {
  14. release {
  15. minifyEnabled false
  16. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  17. }
  18. }
  19. }
  20. dependencies {
  21. compile fileTree(include: ['*.jar'], dir: 'libs')
  22. compile 'com.android.support:appcompat-v7:24.2.1'
  23. compile project(':annotation')
  24. apt project(':compiler')
  25. compile project(':api')
  26. }

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

  1. buildscript {
  2. repositories {
  3. jcenter()
  4. }
  5. dependencies {
  6. classpath 'com.android.tools.build:gradle:2.2.0'
  7. classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // 加上这句话
  8. }
  9. }
  10. allprojects {
  11. repositories {
  12. jcenter()
  13. }
  14. }
  15. task clean(type: Delete) {
  16. delete rootProject.buildDir
  17. }

现在可以使用注解了

  1. public class MainActivity extends AppCompatActivity {
  2. @InjectView(R.id.tv)
  3. public TextView mTextView;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. setContentView(R.layout.activity_main);
  8. Views.inject(this);
  9. mTextView.setText("TextView");
  10. }
  11. }

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

项目源码

https://github.com/okadaNana/ViewInject

参考来源