本文是参考butterknife简单实现在页面中对TextView 属性注入
实现原理是通过代码自动生成一个内部类,在内部类中包含了对TextView的值注入的代码
主要用到的类有
javax.annotation.processing.AbstractProcessor ,通过继承这个类实现对注解预处理来生成具体的类
javax.lang.model.* 里面包含了很多生成类文件需要的类和工具
实现步骤:
1. 在Android Studio中新建一个工程ButterKnife,里面包含了一个MainActivity,页面中存在一个TextView,id是R.id.text;
2. 新建3个module,其中一个Android module,注入库,一个java module ,注解库,一个java module ,编译生成代码库,依赖关系如下图
3. 项目配置
根build.gradle配置:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.powerzhou.butterknife"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.2.0'
testCompile 'junit:junit:4.12'
compile project(':inject')
apt project(':inject-complier')
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile project(':inject-annotion')
}
注解module配置:
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
apply plugin: 'java'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':inject-annotion')
compile 'com.google.auto:auto-common:0.8'
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
4.代码编写
首先在主module中使用依赖注入
public class MainActivity extends Activity {
@BindView(R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectView.bindView(this);
Log.d("Powerzhou","textView is "+textView);
}
}
//与Activity绑定
public class InjectView {
public static void bindView(Activity activity){
String className = activity.getClass().getName();
try{
Class> clazz = Class.forName(className+"$$ViewBinder");
ViewBinder binder = (ViewBinder)clazz.newInstance();
binder.bind(activity);
}catch (Exception e){
}
}
}
//定义接口,生成的内部类实际上就是实现了ViewBinder的匿名内部类 一般生成class后形式为MainActivity$$ViewBinder.class
public interface ViewBinder {
void bind(T targer);
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
BindViewProcessor 实现了AbstractProcessor,用来对注解预处理并实现代码生成
package com.complier;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import com.annotion.BindView;
/**
* APT Annotation Processing Tools
*/
@AutoService(Processor.class)
//don't recommend this way , use override the function
//@SupportedAnnotationTypes("annotation.processor.GenerateInterface")
//@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
/**
* deal the element
*/
private Elements elementsUtils;
private Types typeUtils;
/**
* create java file
*/
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementsUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
filer = processingEnvironment.getFiler();
}
/**
* handle BinderView.classz
* @return
*/
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
Map> typeElementListMap = new HashMap<>();
FileUtils.print("------------> ");
/**
* element , it is the java class type
*/
for(Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
TypeElement enClosingElement = (TypeElement)element.getEnclosingElement();
List list = typeElementListMap.get(enClosingElement);
if(list == null){
list = new ArrayList<>();
typeElementListMap.put(enClosingElement,list);
}
String packageName = getPackageName(enClosingElement);
int id = element.getAnnotation(BindView.class).value();
String fieldName = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName,typeMirror,id);
list.add(fieldViewBinding);
}
for(Map.Entry> item : typeElementListMap.entrySet()){
List list = item.getValue();
if(list == null || list.size() == 0){
continue;
}
TypeElement enClosingElement = item.getKey();
String packageName = getPackageName(enClosingElement);
String complite = getClassName(enClosingElement,packageName);
ClassName className = ClassName.bestGuess(complite);
ClassName viewBinder = ClassName.get("com.example","ViewBinder");
TypeSpec.Builder result = TypeSpec.classBuilder(complite+"$$ViewBinder")
.addModifiers(Modifier.PUBLIC)
.addTypeVariable(TypeVariableName.get("T",className))
.addSuperinterface(ParameterizedTypeName.get(viewBinder,className));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addAnnotation(Override.class)
.addParameter(className,"target",Modifier.FINAL);
for(int i=0;i
package com.complier;
import javax.lang.model.type.TypeMirror;
/**
* Created by Administrator on 2017/3/1 0001.
*/
public class FieldViewBinding {
private String name;//textview
private TypeMirror type;//TextView 类型
private int resId;//-->R.id.textview
public FieldViewBinding(String name, TypeMirror type, int resId) {
this.name = name;
this.type = type;
this.resId = resId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TypeMirror getType() {
return type;
}
public void setType(TypeMirror type) {
this.type = type;
}
public int getResId() {
return resId;
}
public void setResId(int resId) {
this.resId = resId;
}
}
package com.complier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Created by Administrator on 2017/3/1 0001.
*/
public class FileUtils {
public static void print(String text)
{
File file=new File("C:\\Users\\Administrator\\Desktop\\log1.txt");
if(!file.exists())
{
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
FileWriter fileWriter=new FileWriter(file.getAbsoluteFile(),true);
fileWriter.write(text+"\n");
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在运行后查看主module的build目录下,生成的内部类信息
看下自动生成的内部类内容:
// /**auto create by Powerzhou**/
package com.powerzhou.butterknife;
import android.widget.TextView;
import com.example.ViewBinder;
import java.lang.Override;
public class MainActivity$$ViewBinder implements ViewBinder {
@Override
public void bind(final MainActivity target) {
target.textView=(TextView)target.findViewById(2131427415);
}
}
程序运行后可以发现我们自动给textView赋值了。
同时还可以增加对其他属性和方法的扩展,实现原理大同小异,主要是应用到了javax.lang.model包下的类。
github 地址:源码