如何实现编译时注入
2017-03-02 23:18
204 查看
本文是参考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 ,编译生成代码库,依赖关系如下图
![](http://img.blog.csdn.net/20170302225445277?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWF3aW5zdGFrZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
3. 项目配置
根build.gradle配置:
主module配置:
注入module inject 配置:
编译module配置:
配置中需要注意各个module之间的关系,主module是不需要依赖编译module的,使用的是apt project('inject-complier')
4.代码编写
首先在主module中使用依赖注入
inject module中存在两个类,InjectView 和ViewBinder
//与Activity绑定
注解module中存在一个注解类:
编译module中存在三个类
BindViewProcessor 实现了AbstractProcessor,用来对注解预处理并实现代码生成
pojo类,封装了需要注入属性的类的相关信息
如果需要打印日志的话,是无法通过System.out.print或者Android中的Log来实现,可以定义一个文件写入类,专门用来写日志:
以上代码就全部完成。
在运行后查看主module的build目录下,生成的内部类信息
![](http://img.blog.csdn.net/20170302232803918?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWF3aW5zdGFrZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
看下自动生成的内部类内容:
可以看到,内部类实现了ViewBinder接口,注意这里用到了泛型,同时是通过target.textView来访问的textView,所以在MainActivity中不能把TextView属性定义为private
程序运行后可以发现我们自动给textView赋值了。
同时还可以增加对其他属性和方法的扩展,实现原理大同小异,主要是应用到了javax.lang.model包下的类。
github 地址:源码
实现原理是通过代码自动生成一个内部类,在内部类中包含了对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 }
主module配置:
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') }
注入module inject 配置:
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"
编译module配置:
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"
配置中需要注意各个module之间的关系,主module是不需要依赖编译module的,使用的是apt project('inject-complier')
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); } }
inject module中存在两个类,InjectView 和ViewBinder
//与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<T> { void bind(T targer); }
注解module中存在一个注解类:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }
编译module中存在三个类
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<String> getSupportedAnnotationTypes() { Set<String> 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<TypeElement,List<FieldViewBinding>> 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<FieldViewBinding> 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<TypeElement,List<FieldViewBinding>> item : typeElementListMap.entrySet()){ List<FieldViewBinding> 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<list.size();i++){ FieldViewBinding fieldViewBinding = list.get(i); String packageNameString = fieldViewBinding.getType().toString(); ClassName viewClass = ClassName.bestGuess(packageNameString); methodBuilder.addStatement("target.$L=($T)target.findViewById($L)",fieldViewBinding.getName(),viewClass,fieldViewBinding.getResId()); } result.addMethod(methodBuilder.build()); try { JavaFile.builder(packageName, result.build()).addFileComment("auto create by Powerzhou").build().writeTo(filer); }catch (Exception e){ } } return false; } private String getClassName(TypeElement enClosingElement, String packageName) { int packageLength = packageName.length()+1; return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".","$"); } private String getPackageName(TypeElement enClosingElement) { return elementsUtils.getPackageOf(enClosingElement).getQualifiedName().toString(); } }
pojo类,封装了需要注入属性的类的相关信息
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; } }
如果需要打印日志的话,是无法通过System.out.print或者Android中的Log来实现,可以定义一个文件写入类,专门用来写日志:
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<T extends MainActivity> implements ViewBinder<MainActivity> { @Override public void bind(final MainActivity target) { target.textView=(TextView)target.findViewById(2131427415); } }
可以看到,内部类实现了ViewBinder接口,注意这里用到了泛型,同时是通过target.textView来访问的textView,所以在MainActivity中不能把TextView属性定义为private
程序运行后可以发现我们自动给textView赋值了。
同时还可以增加对其他属性和方法的扩展,实现原理大同小异,主要是应用到了javax.lang.model包下的类。
github 地址:源码
相关文章推荐
- eclipse 插件开发中如何实现刷新和重编译
- 马宁教你如何通过软件编译实现嵌入式开发
- Android应用开发中如何实现条件编译
- C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?
- 如何理解java中的依赖注入 通过构造函数和反射机制来实现的
- 问题的提出:如何在Windows上通过终端程序实现Unix平台的前台编译?
- 教你如何实现在原系统上编译升级新内核的编译
- C++的多态如何在编译和运行期实现
- JAVA如何实现条件编译(从思路开始)
- 在不同编译环境中如何实现密码的隐藏
- C++ 接口与实现分离技术---如何将文件间的编译关系降至最低
- 如何实现虚函数的规则(VC中 C++ virtual 编译规则)
- eclipse 插件开发中如何实现刷新和重编译
- 软件包的管理及如何实现WEB源代码编译安装流程(内附图解释)
- spring 如何实现注入多个数据源,并且可以同时使用这多个数据源!
- 如何用C#实现依赖注入?
- 如何实现条件编译(VS2010为例)
- Java如何实现条件编译
- PHP实现如何把数组编译成URL格式
- 关于在VC++6.0中同时有多个main()文件时如何实现执行编译和运行