打造簡單的依賴注入功能
讓程式碼幫我們自動寫程式碼。
目標:實現類似於Butterknife的id注入。
首先先了解Java 的註解吧。
註解的分類
SOURCE:只是用作標記,會被編譯器丟棄。如@Override、@SuppressWarnings
CLASS:編譯時動態處理。
RUNTIME:執行時處理,可能會用到反射
上面註解的保留時間。用於@Retention
還有一個@Target用以標記註解的作用物件
他的值是一個ElementType陣列
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
有這些類別。我是從java的原始碼裡面直接拷貝出來的說明和定義。
因為我們是要注入id,所以Target就選擇FIELD了。
宣告一個註解用來注入程式碼View的id:
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by lps on 2017/8/28.
*
* @version 1
* @see
* @since 2017/8/28 16:52
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
int value();
}
這裡我們的Retention就是CLASS的
然後我們需要一個類來處理我們剛剛定義的註解。
我們編寫一個類繼承自AbstractProcessor。(在android中是找不到這個類的,需要在AS中新建一個JavaLib的Moudle才行,原因暫時未知)
並且需要在這個lib下面建立META-INF/services/javax.annotation.processing.Processor檔案
裡面填入我們的Processor的路徑(這一步也不是清楚,可能是jvm需要這樣的定義去解析吧,不定義是不能生成程式碼的)
我自己命名為ViewInjectorProcessor。其實就是通過編譯器編譯時解析到這裡,為我們建立ViewInjector。
遍歷有註解的類,分別建立class+$Injector.
比如這裡的demo就是MainActivity$Injector和Main2Activity$Injector.
其實寫這個Processor主要就是解析註解生成類的程式碼。用程式碼為我們的註解自動生成類和對應的處理程式碼。
process的實現是我們的核心。
package com.example;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
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.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* Created by lps on 2017/8/28.
*
* @version 1
* @see
* @since 2017/8/28 17:21
*/
@SupportedAnnotationTypes("com.example.InjectView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ViewInjectorProcessor extends AbstractProcessor {
private static final String GEN_CLASS_SUFFIX = "$Injector";
private static final String INJECTOR_NAME = "ViewInjector";
private static final String TAG = "ViewInjectorProcessor";
private Types mTypeUtils;
private Elements mElementUtils;
private Filer mFiler;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mTypeUtils = processingEnv.getTypeUtils();
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 使用了@InjectView註解的元素
Set<? extends Element> mElementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectView.class);
if (mElementsAnnotatedWith.size() == 0) return true;
Map<Element, List<Element>> elemtMap = new HashMap<>();
StringBuffer buffer = new StringBuffer();
// 構建註解類
buffer.append("package com.example;\n")
.append("public class " + INJECTOR_NAME + "{\n");
for (Element mElement : mElementsAnnotatedWith) {
if (!isView(mElement.asType())) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a View ", mElement);
}
Element clazz = mElement.getEnclosingElement();
addElment(elemtMap, clazz, mElement);
}
System.out.println(TAG+elemtMap);
// 遍歷elemtmap,即遍歷所有的註解類
for (Map.Entry<Element, List<Element>> entry : elemtMap.entrySet()) {
Element clazz = entry.getKey();
String classname = clazz.getSimpleName().toString();
String packageName = mElementUtils.getPackageOf(clazz).asType().toString();
generateInjectorCode(packageName, classname, entry.getValue());
String fullName = clazz.asType().toString();
// 拼接ViewInjector的inject方法程式碼
buffer.append("\tpublic static void inject(" + fullName + " arg){\n")
.append("\t\t" + fullName + GEN_CLASS_SUFFIX + ".inject(arg);\n")
.append("\t}\n");
}
buffer.append("}");
// 生成ViewInjector類
generateCode(INJECTOR_NAME, buffer.toString());
return true;
}
/**
* 生成程式碼
* @param classname
* @param code
*/
private void generateCode(String classname, String code) {
try {
JavaFileObject file = mFiler.createSourceFile(classname);
Writer mWriter = file.openWriter();
mWriter.write(code);
mWriter.close();
} catch (IOException mE) {
mE.printStackTrace();
}
}
/**
* 生成注入器的程式碼如MainActivity$Injector
* @param mPackageName
* @param mClassname
* @param views
*/
private void generateInjectorCode(String mPackageName, String mClassname, List<Element> views) {
StringBuilder mBuilder = new StringBuilder();
mBuilder.append("package " + mPackageName + ";\n\n")
.append("public class " + mClassname + GEN_CLASS_SUFFIX + "{\n")
.append("public static void inject(" + mClassname + " arg){\n");
// 對每個View 遍歷。生成findViewByid程式碼
for (Element mElement : views) {
String type = mElement.asType().toString();
String name = mElement.getSimpleName().toString();
int id = mElement.getAnnotation(InjectView.class).value();
mBuilder.append("\t\targ." + name + "=(" + type + ")arg.findViewById(" + id + ");\n");
}
mBuilder.append("\t}\n").append("}");
generateCode(mClassname + GEN_CLASS_SUFFIX, mBuilder.toString());
}
/**
* 向mElemtMap中存入,elemt和List的鍵值對
* @param mElemtMap
* @param mClazz
* @param mElement
*/
private void addElment(Map<Element, List<Element>> mElemtMap, Element mClazz, Element mElement) {
List<Element> list = mElemtMap.get(mClazz);
if (list == null) {
list = new ArrayList<>();
mElemtMap.put(mClazz, list);
}
list.add(mElement);
}
//判斷是否是View的子類
private boolean isView(TypeMirror mTypeMirror) {
List<? extends TypeMirror> supers = mTypeUtils.directSupertypes(mTypeMirror);
if (supers.size() == 0) {
return false;
}
for (TypeMirror supertype :
supers) {
if (supertype.toString().equals("android.view.View") || isView(supertype)) {
return true;
}
}
return false;
}
}
程式碼如上。
@SupportedAnnotationTypes的值為當前類支援的註解的完整類路徑,支援萬用字元。
@SupportedSourceVersion 標識該處理器支援的原始碼版本
最後就是在app中使用了,首先需要app依賴我們的lib,然後引入apt外掛。
在Project的gradle指令碼中新增程式碼
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
在app的gradle指令碼中新增plugin
apply plugin: 'com.neenbedankt.android-apt'
就可以在Activity中使用了。使用了註解,然後make project一下
就可以在app的build下看到apt生成的程式碼了
相關文章
- JavaScrpit 的簡單的依賴注入Java依賴注入
- 簡單歡樂的依賴注入函式依賴注入函式
- 簡單談談Hilt——依賴注入框架依賴注入框架
- 用trait實現簡單的依賴注入AI依賴注入
- Spring【依賴注入】就是這麼簡單Spring依賴注入
- 又一個「簡單」的 PHP5.3 依賴注入容器PHP依賴注入
- 如何用最簡單的方式解釋依賴注入?依賴注入是如何實現解耦的?(通俗易懂)依賴注入解耦
- 依賴注入?依賴注入是如何實現解耦的?依賴注入解耦
- Spring學習:簡單實現一個依賴注入和迴圈依賴的解決Spring依賴注入
- ASP.NET Core中的依賴注入(2):依賴注入(DI)ASP.NET依賴注入
- angular依賴注入Angular依賴注入
- XUnit 依賴注入依賴注入
- Struts 依賴注入依賴注入
- 依賴倒置(DIP)與依賴注入(DI)依賴注入
- 極簡架構模式-依賴注入模式架構模式依賴注入
- spring 的依賴注入Spring依賴注入
- JavaScript裡的依賴注入JavaScript依賴注入
- 簡單解釋什麼是 依賴注入 和 控制反轉依賴注入
- Spring的核心機制依賴注入簡介Spring依賴注入
- SAP Spartacus OccEndpointsService單元測試的依賴注入依賴注入
- [譯] 依賴注入?? 哈??依賴注入
- Angular 依賴注入原理Angular依賴注入
- .Net Core — 依賴注入依賴注入
- 理解 Angular 依賴注入Angular依賴注入
- Spring依賴注入Spring依賴注入
- Spring依賴注入---Spring依賴注入
- 依賴注入系列教程依賴注入
- 我看依賴注入依賴注入
- webapi - 使用依賴注入WebAPI依賴注入
- 依賴注入是否值得?依賴注入
- AngularJs動態載入模組和依賴注入簡單介紹AngularJS依賴注入
- 類的反射和依賴注入反射依賴注入
- [譯]javascript中的依賴注入JavaScript依賴注入
- Spring 依賴注入的理解Spring依賴注入
- 簡單瞭解下Spring中的各種Aware介面實現依賴注入Spring依賴注入
- Golang 依賴注入設計哲學|12.6K 🌟 的依賴注入庫 wireGolang依賴注入
- Spring的依賴注入的方式Spring依賴注入
- Asp .Net Core 依賴注入依賴注入