在現今的軟件開發過程當中,軟件開發人員將更多的精力投入在了重複的類似勞動中。特別是在現在特別流行的 MVC 架構模式中,軟件各個層次的功能更加獨立,同時代碼的類似度也更加高。因此咱們須要尋找一種來減小軟件開發人員重複勞動的方法,讓程序員將更多的精力放在業務邏輯以及其餘更加具備創造力的工做上。java
服務端的技術發展了不少年,有不少值得能夠借鑑的地方。有的時候,在跟後臺溝通的時候,發現他們在設計好數據庫
和表結構
的時候,常常能夠一鍵生成常見的功能(增、刪、改、查)。後來知道,由於相識程度很是的高。因此他們常常以代碼來生成代碼。就這樣完成了一站式
的功能。android
更多時候,我在考慮,爲什麼他們不優先考慮封裝呢?git
Android
中有種模板編程的偷懶方式。但不是今天的主角。今天的主角是Annotation Processor
(註解處理器)。 在最近查閱源碼的時候。發現不少框架都會使用到Annotation Processor
好比說,ButterKnife
、Dagger
、ARouter
等等。因此理解Annotation Processor
(註解處理器)的原理,是一個Android
程序員必須具有的技能。程序員
Annotation Processor
直譯成中文就是(註解處理器),就是可以對註解進行處理。既然可以對註解進行處理,那麼先定義一個簡單的註解。github
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
複製代碼
定義完註解後,咱們會想着在何時使用它呢? 還記得上述的核心是爲了偷懶,那麼 在Android
存在有重複性很是高並且難度極地的代碼,就是對控件的獲取和處理。數據庫
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView1=(TextView) findViewById(R.id.tvHello);
textView1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
複製代碼
這樣的模板代碼不只很浪費時間,並且代碼的美觀程度也大大的下降了。那麼咱們如今嘗試着用本身定義的註解去處理。編程
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tvHello)
TextView tvHello;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@OnClick(R.id.tvHello)
public void changeText2() {
Toast.makeText(this, "2222", Toast.LENGTH_LONG).show();
}
}
複製代碼
上述的代碼,就會比較清晰。不會有獲取控件的過程的代碼。聲明即便用了。那麼僅僅這樣打上一個註解,可以自動的獲取控件嗎?答案是不可能的。咱們還缺乏一步編寫註解處理器(其實就是編寫遇到這個註解改怎麼處理)。api
在編寫註解處理器以前,先嚐試着去編寫一個類。這個類的職責就是獲取控件。bash
public final class MainActivity$ViewBinding {
public MainActivity$ViewBinding(MainActivity target, View source) {
if(target == null) return;
if(source == null) return;
target.tvHello = (android.widget.TextView)source.findViewById(2131165275);
}
public MainActivity$ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
}
複製代碼
上面的這個類,就是須要註解處理器
幫咱們自動生成的模板代碼。咱們必須知道本身須要什麼代碼,才能控制註解處理器
生成咱們想要的代碼。那麼觀察一下最終生成的代碼,其中有幾個須要注意的:數據結構
BindView
BindView
的註解的時候,咱們須要爲這個字段賦值。而賦值的操做,咱們是經過屬性直接賦值(默認的訪問權限是包訪問權限)target.tvHello = (android.widget.TextView)source.findViewById(2131165275);
因此這個由註解處理器
幫我生成的類,須要跟目標類在同級包下。 3. 這個類的名字是按照必定格式生成的,在本文中它是由目標類+$ViewBinding。
到這裏,須要注意的點都講完了,接下開始來編寫註解處理器
的核心代碼。
在知道了咱們最終須要生成什麼代碼以後,就須要對整個目錄進行規劃一下,由於這個工具不只僅是當前這個項目會被使用。頗有不少項目將對其引用。你可能會將其發佈到JCenter
上。
根據上面的圖,咱們分別創建3
個module
。其中2
個是java libary
和android libary
最終的目錄結構是這樣的
而後咱們將剛纔編寫的BindView
註解放入inject-annotation
模塊中
在劃分話目錄結構後,咱們能夠編寫註解處理器的核心代碼了,也就意味着,咱們須要把它的代碼放置在inject-compiler
中。
須要在inject-compiler
下的build.gradle
導入幾個庫
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
api 'com.squareup:javapoet:1.10.0'
implementation project(':inject-annotation')
複製代碼
介紹一下這幾個庫,
auto-service
是幫助生成META-INF/services/javax.annotation.processing
文件中的內容javapoet
更加面向對象的輸出代碼inject-annotation
須要處理的註解。在添加完這些東西以後,咱們就須要建立一個註解處理器了
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
HashMap<TypeElement, List<Element>> datas = new HashMap<>();
for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
TypeElement originalType = (TypeElement) element.getEnclosingElement();
if (datas.get(originalType) == null) {
List<Element> elements = new ArrayList<>();
datas.put(originalType, elements);
}
List<Element> dd = datas.get(originalType);
dd.add(element);
}
for (Map.Entry<TypeElement, List<Element>> entry : datas.entrySet()) {
TypeElement key = entry.getKey();
List<Element> value = entry.getValue();
createFile(key, value);
}
return false;
}
複製代碼
核心代碼大概是上面的,就是遍歷類中的註解元素,包裝成一個Map
數據的數據結構。
而後將構建後的數據結構,進行處理,按照之間MainActivity$ViewBinding
的所預想的規則進行輸出。
private MethodSpec createCustomerConstructor2View(TypeElement originalType, List<Element> elements) {
ParameterSpec targetParamSpec = ParameterSpec.builder(TypeName.get(originalType.asType()), "target").build();
MethodSpec.Builder constructor1 = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(targetParamSpec)
.addParameter(mViewParameterSpec)
.addStatement("if(target == null) return")
.addStatement("if(source == null) return");
for (Element element : elements) {
String variateName = element.getSimpleName().toString();
String variateType = element.asType().toString();
int resId = element.getAnnotation(BindView.class).value();
constructor1.addStatement("target.$L = ($N)source.findViewById($L)", variateName, variateType, resId);
}
return constructor1.build();
}
複製代碼
這樣咱們就完成了註解處理器的編寫,當你編寫後從新Rebuild Project
後,若是不出意外的話,你能夠找到下面這個文件。
Annotation Processor
(註解處理器)若是你在編寫註解處理器
可能不是你預想,那麼斷點調試就變得很是的重要了。那麼接下來介紹如何調試註解處理器
.
Edit Configurations
新建一個Remote Configurations
Terminal
中運行下面命令./gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac
須要斷點的地方,下斷點。
Debug
剛纔添加的Configurations
5.它就能夠正常的斷點了
當咱們完成了註解處理器
的編寫,也正常的生成了MainActivity$ViewBinding
文件後,咱們就須要調用所生成的類,讓它來幫咱們完成控件的注入。由於其中會涉及到對Activity
的引用,因此這個包是android libary
。也就意味着,接下來的代碼須要在inject-core
模塊中編寫。
咱們在進入一個Activity
或者Fragment
的時候,可以調用inject
方法,而inject
的實現以下:
public static void inject(Activity activity) {
String name = activity.getClass().getSimpleName();
String packName = activity.getPackageName();
String fullName = name + "$ViewBinding";
try {
Class targetClass = activity.getClassLoader().loadClass(packName + "." + fullName);
Constructor constructor = targetClass.getConstructor(activity.getClass());
constructor.newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
就是經過反射,而後實例化剛纔的對象。這樣的話,咱們就完成了控件的注入了。
JCenter
上由於咱們項目中存在有jar
和aar
格式的包,因此須要分別上傳。在編寫上傳任務以前,須要先在最外層的build.gradle
添加plugin
,請注意插件的版本。否則會出現一些奇奇怪怪的問題。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
複製代碼
jar
格式的gradle
,在最外層新建jar_publish.gradle
文件,而後在:inject-compiler
的build.gradle
中 apply。aar
格式的gradle
,在最外層新建aar_publish.gradle
文件,而後在:inject-core
的build.gradle
中 apply。而後依次上傳便可。