今年你們都在搞組件化,組件化開發不可避免的須要用到路由(Router)來完成組件之間數據的交互,這就促進了各類路由發展如:阿里的ARouter以及ActivityRouter等優秀的Router框架。爲了方便你們的開發這些Router庫以及像ButterKnife這類的庫都用到了註解技術。本篇目的是進行一波掃盲。html
Java提供了三個基本的Annotation註解,使用時須要在Annotation前面增長@符號,並把Annotation當成一個修飾符來使用。注:Java提供的基本Annotation註解都在java.lang包下面java
public class Animal {
public void run() {
//TODO
}
}
複製代碼
public class Monkey extends Animal {
@Override
public void run() {
//使用了OVerride註解以後,必須重寫父類方法
}
}
複製代碼
public class Animal {
@Deprecated
public void run() {
//TODO
}
}
複製代碼
3. @SuppressWarnings:抑制編譯器警告(用的比較少)。Java代碼編譯時IDE每每會給開發者不少警告信息,例如變量沒有使用等,這種警告多了以後很大程度上影響咱們debug效率。此註解就是來抑制這些警告。舉個栗子:
@SuppressWarning("unused")
public void foo() {
String s;
}
複製代碼
若是不使用@SuppressWarning來抑制編譯器警告,上面的代碼會被警告變量s從未使用。出了"unused",該註解支持的抑制類型還有下圖的內容(注該圖摘自IBM Knowledge Center)。android
JDK出了在java.lang包中提供了1.1介紹的幾種基本Annotation外還在java.lang.annotation包下面提供了四個Meta Annotation(元Annotation)。這四種元Annotation都是來修飾自定義註解的。(hold住節奏,看完這個小結我們就能夠自定義Annotation了)git
舉個例子,自定義一個BindView註解(看不懂不要緊,現有一個感性的認識,下一節開始作自定義Annotation講解)。github
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
複製代碼
當成員變量爲value時,能夠省略。也就是說上述代碼能夠換成 @Retention(RetentionPolicy.CLASS)
segmentfault
//此註解修飾的是屬性
@Target(ElementType.FIELD)
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
複製代碼
/**
* Created by will on 2018/2/4.
*/
@Documented
//此註解修飾的是屬性
@Target(ElementType.FIELD)
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
}
複製代碼
/**
* Created by will on 2018/2/4.
*/
@Documented
//此註解修飾的是屬性
@Target(ElementType.FIELD)
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id();
}
複製代碼
@Documented
//此註解修飾的是屬性
@Target(ElementType.FIELD)
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
複製代碼
根據有沒有成員變量,咱們能夠將Annotation劃分紅兩種:api
注意:只定義了自定義註解沒有任何效果,還須要對Annotation的信息進行提取與加工!!!緩存
上面咱們自定義了BindView註解,你是否是想直接拿到Activity中使用呢?例如:bash
而後你發現Crash了。。。這就要引入下一節的內容了,使用apt對被註解的代碼進行二次加工。完成自定義Annotation後,咱們還須要知道,針對這些註解,咱們要作哪些相關的處理,這就涉及到了Annotation的解析操做。 解析Annotation,一般分爲:對運行時Annotation的解析、對編譯時Annotation的解析; 解析Annotation,其實就是如何從代碼中找到Annotation,一般咱們的作法是:oracle
反射的解析方式,一般運用在運行時Annotation的解析。 反射是指:利用Class、Field、Method、Construct等reflect對象,獲取Annotation
一般使用Runtime修飾的註解須要使用反射來配合解析
@Retention(value = RetentionPolicy.RUNTIME)
/**
* Created by will on 2018/2/4.
*/
@Documented
//此註解修飾的是屬性
@Target(ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface test {
int id() default 0;
}
複製代碼
public class Animal {
@BindView(id = 1000)
String a;
@Deprecated
public void run() {
//TODO
}
}
複製代碼
private void testMethod() {
Class clazz = Animal.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
int id = bindView.id();
Log.e("------", String.valueOf(id));
}
}
}
複製代碼
APT:是一個註解處理工具 Annotation Processing Tool 做用:利用apt,咱們能夠找到源代中的註解,並根據註解作相應的處理
利用APT,在編譯時生成額外的代碼,不會影響性能,只是影響項目構建的速度
這裏咱們說一下Android中使用apt的步驟 Android中開發自定義的apt學會兩個庫及一個類基本就足夠了
@AutoService(Processor.class)
下面會用到。JavaPoet的學習能夠直接借鑑官方api,AutoService學習成本較低(只須要用裏面一句代碼而已,學習成本能夠忽略),下面咱們重點學習一下AbstractProcessor的使用。
/**
* Created by will on 2018/2/5.
*/
public class CustomProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
複製代碼
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Elements elementUtils = processingEnv.getElementUtils();
}
複製代碼
這個工具類是用來處理源代碼的,在自定義註解處理器的領域裏面,Java源代碼每個類型都屬於一個Element,具體使用方法能夠直接參考Java官方文檔
package com.example; // PackageElement
public class Test { // TypeElement
private int a; // VariableElement
private Test other; // VariableElement
public Test () {} // ExecuteableElement
public void setA ( // ExecuteableElement
int newA // TypeElement
) {}
}
複製代碼
例如,我有一個TypeElement,但願拿到這個class所在的包名就可使用Elemnts這個工具
private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
}
複製代碼
再來一個栗子,有一個表明Test的TypeElement,但願獲取全部的子元素能夠這麼寫(注意,這個頗有用)
TypeElement testClass = ... ;
for (Element e : testClass.getEnclosedElements()){ // iterate over children
Element parent = e.getEnclosingElement(); // parent == testClass
}
複製代碼
好了枯燥的基礎知識看完了以後咱們一塊兒寫一個簡單的ButterKnife
@Documented
//此註解修飾的是屬性
@Target(ElementType.FIELD)
//此註解的做用域是Class,也就是編譯時
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
複製代碼
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.7.0'
複製代碼
/**
* Created by will on 2018/2/4.
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
/**
* 工具類,能夠從init方法的ProcessingEnvironment中獲取
*/
private Elements elementUtils;
/**
* 緩存全部子Element
* key:父Element類名
* value:子Element
*/
private HashMap<String, List<Element>> cacheElements = null;
/**
* 緩存全部父Element
* key:父Element類名
* value:父Element
*/
private HashMap<String, Element> cacheAllParentElements = null;
@Override
public Set<String> getSupportedAnnotationTypes() {
// 規定須要處理的註解類型
return Collections.singleton(BindView.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations
, RoundEnvironment roundEnv) {
//掃描全部註解了BindView的Field,由於咱們全部註解BindView的地方都是一個Activity的成員
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
//將全部子elements進行過濾
addElementToCache(element);
}
if (cacheElements == null || cacheElements.size() == 0) {
return true;
}
for (String parentElementName : cacheElements.keySet()) {
//判斷一下獲取到的parent element是不是類
try {
//使用JavaPoet構造一個方法
MethodSpec.Builder bindViewMethodSpec = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(ClassName.get(cacheAllParentElements.get(parentElementName).asType())
, "targetActivity");
List<Element> childElements = cacheElements.get(parentElementName);
if (childElements != null && childElements.size() != 0) {
for (Element childElement : childElements) {
BindView bindView = childElement.getAnnotation(BindView.class);
//使用JavaPoet對方法內容進行添加
bindViewMethodSpec.addStatement(
String.format("targetActivity.%s = (%s) targetActivity.findViewById(%s)"
, childElement.getSimpleName()
, ClassName.get(childElement.asType()).toString()
, bindView.id()));
}
}
//構造一個類,以Bind_開頭
TypeSpec typeElement = TypeSpec.classBuilder("Bind_"
+ cacheAllParentElements.get(parentElementName).getSimpleName())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindViewMethodSpec.build())
.build();
//進行文件寫入
JavaFile javaFile = JavaFile.builder(
getPackageName((TypeElement) cacheAllParentElements.get(parentElementName))
, typeElement).build();
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
return true;
}
}
return true;
}
private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 緩存父Element對應的全部子Element
* 緩存父Element
*
* @param childElement
*/
private void addElementToCache(Element childElement) {
if (cacheElements == null) {
cacheElements = new HashMap<>();
}
if (cacheAllParentElements == null) {
cacheAllParentElements = new HashMap<>();
}
//父Element類名
String parentElementName = null;
parentElementName = ClassName.get(childElement.getEnclosingElement().asType()).toString();
if (cacheElements.containsKey(parentElementName)) {
List<Element> childElements = cacheElements.get(parentElementName);
childElements.add(childElement);
} else {
ArrayList<Element> childElements = new ArrayList<>();
childElements.add(childElement);
cacheElements.put(parentElementName, childElements);
cacheAllParentElements.put(parentElementName, childElement.getEnclosingElement());
}
}
}
複製代碼
注意:Android Gradle插件2.2版本發佈後,Android 官方提供了annotationProcessor來代替android-apt,annotationProcessor同時支持 javac 和 jack 編譯方式,而android-apt只支持 javac 方式。同時android-apt做者宣佈不在維護,這裏我直接用了annotationProcessor
implementation project(':annotations')
annotationProcessor project(':annotations_compiler')
複製代碼
public class MainActivity extends AppCompatActivity {
@BindView(id = R.id.tv_test)
TextView tv_test;
@BindView(id = R.id.tv_test1)
TextView tv_test1;
@BindView(id = R.id.iv_image)
ImageView iv_image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bind_MainActivity.bindView(this);
tv_test.setText("test_1");
tv_test1.setText("test_2");
iv_image.setImageDrawable(getDrawable(R.mipmap.ic_launcher));
}
}
複製代碼
contact way | value |
---|---|
weixinjie1993@gmail.com | |
W2006292 | |
github | https://github.com/weixinjie |
blog | https://juejin.im/user/57673c83207703006bb92bf6 |