YCApt關於apt方案實踐與總結
目錄介紹
- 00.註解系列博客彙總
- 01.什麼是apt
- 02.annotationProcessor和apt區別
- 03.項目目錄結構
- 04.該案例作用
- 05.使用說明
- 06.編譯期註解生成代碼[點擊事件案例]
- 07.運行期註解案例[setContentView案例]
- 08.使用註解替代枚舉
-
09.使用註解搭建路由[綜合案例]
- 9.1 搭建路由條件
- 9.2 通過註解去實現路由跳轉
- 9.3 自定義路由Processor編譯器
- 9.4 利用apt生成路由映射文件
- 9.5 路由框架的設計
- 9.6 路由參數的傳遞和接收
- 9.7 爲何需要依賴注入
- 9.8 Activity屬性注入
- 9.9 路由開源庫的使用
關於apt實踐與總結開源庫地址
https://github.com/yangchong2...
00.註解系列博客彙總
0.1 註解基礎系列博客
-
1.Annotation庫的簡單介紹 2.@Nullable和@NonNull 3.資源類型註釋 4.類型定義註釋 5.線程註釋 6.RGB顏色紙註釋 7.值範圍註釋 8.權限註釋 9.重寫函數註釋 10.返回值註釋 11.@Keep註釋 12.@SuppressWarnings註解 13.其他
- [02.Dagger2深入分析,待更新]()
-
- 什麼是註解,註解分類有哪些?自定義註解分類?運行註解案例展示分析,以一個最簡單的案例理解註解……使用註解替代枚舉,使用註解限定類型
-
- 什麼是apt?理解註解處理器的作用和用途……android-apt被替代?annotationProcessor和apt區別? 什麼是jack編譯方式?
-
- @Retention的作用?@Target(ElementType.TYPE)的解釋,@Inherited註解可以被繼承嗎?Annotation裏面的方法爲何不能是private?
-
- 後期更新
-
- 處理器類Processor介紹,重要方法,Element的作用,修飾方法的註解和ExecutableElement,瞭解修飾屬性、類成員的註解和VariableElement……
-
- 無法引入javax包下的類庫,成功運行一次,修改代碼後再運行就報錯
-
- 在做內存優化時,推薦使用註解代替枚舉,因爲枚舉佔用的內存更高,如何說明枚舉佔用內存高呢?這是爲什麼呢?
-
- 註解學習小案例,比較系統性學習註解並且應用實踐。簡單應用了運行期註解,通過註解實現了setContentView功能;簡單應用了編譯器註解,通過註解實現了防暴力點擊的功能,同時支持設置時間間隔;使用註解替代枚舉;使用註解一步步搭建簡單路由案例。結合相應的博客,在來一些小案例,從此應該對註解有更加深入的理解……
0.2 註解系列博客問題答疑
- 13.0.0.1 什麼是註解?系統內置的標準註解有哪些?SuppressWarnings用過沒?Android中提供了哪些與線程相關的註解?
- 13.0.0.2 什麼是apt?apt的難點和優勢?什麼是註解處理器?抽象處理器中四個方法有何作用?annotationProcessor和apt區別?
- 13.0.0.3 註解是怎麼分類的?自定義註解又是怎麼分類的?運行期註解原理是什麼?實際註解案例有哪些?
- 13.0.0.4 在自定義註解中,Annotation裏面的方法爲何不能是private?Annotation裏面的方法參數有哪些?
- 13.0.0.5 @Inherited是什麼意思?註解是不可以繼承的,這是爲什麼?註解的繼承這個概念該如何理解?
- 13.0.0.6 什麼是依賴注入?依賴注入案例舉例說明,有哪些方式,具備什麼優勢?依賴查找和依賴注入有什麼區別?
- 13.0.0.7 路由框架爲何需要依賴注入,不用的話行不行?路由用什麼方式注入,這些注入方式各具何特點,爲何選擇註解注入?
- 13.0.0.8 實際開發中使用到註解有哪些,使用註解替代枚舉?如何通過註解限定傳入的類型?爲何說枚舉損耗性能?
01.什麼是apt
-
什麼是apt
- APT,就是Annotation Processing Tool的簡稱,就是可以在代碼編譯期間對註解進行處理,並且生成Java文件,減少手動的代碼輸入。註解我們平時用到的比較多的可能會是運行時註解,比如大名鼎鼎的retrofit就是用運行時註解,通過動態代理來生成網絡請求。編譯時註解平時開發中可能會涉及的比較少,但並不是說不常用,比如我們經常用的輪子Dagger2, ButterKnife, EventBus3 都在用,所以要緊跟潮流來看看APT技術的來龍去脈。
-
編譯時註解。
- 也有人叫它代碼生成,其實他們還是有些區別的,在編譯時對註解做處理,通過註解,獲取必要信息,在項目中生成代碼,運行時調用,和直接運行手寫代碼沒有任何區別。而更準確的叫法:APT - Annotation Processing Tool
-
大概原理
- Java API 已經提供了掃描源碼並解析註解的框架,開發者可以通過繼承 AbstractProcessor 類來實現自己的註解解析邏輯。APT 的原理就是在註解了某些代碼元素(如字段、函數、類等)後,在編譯時編譯器會檢查 AbstractProcessor 的子類,並且自動調用其 process() 方法,然後將添加了指定註解的所有代碼元素作爲參數傳遞給該方法,開發者再根據註解元素在編譯期輸出對應的 Java 代碼
02.annotationProcessor和apt區別
-
annotationProcessor和apt區別
- Android 官方的 annotationProcessor 同時支持 javac 和 jack 編譯方式,而 android-apt 只支持 javac 方式。當然,目前 android-apt 在 Android Gradle 插件 2.2 版本上面仍然可以正常運行,如果你沒有想支持 jack 編譯方式的話,可以繼續使用 android-apt。
- 目前比如一些常用框架dagger2,butterKnife,ARouter等,都支持annotationProcessor
-
什麼是jack編譯方式?
- Jack (Java Android Compiler Kit)是新的Android 編譯工具,從Android 6.0 開始加入,替換原有的編譯工具,例如javac, ProGuard, jarjar和 dx。它主要負責將java代碼編譯成dex包,並支持代碼壓縮,混淆等。
-
Jack工具的主要優勢
- 完全開放源碼,源碼均在AOSP中,合作伙伴可貢獻源碼
- 加快編譯源碼,Jack 提供特殊的配置,減少編譯時間:pre-dexing, 增量編譯和Jack編譯服務器.
- 支持代碼壓縮,混淆,重打包和multidex,不在使用額外單獨的包,例如ProGuard。
03.項目目錄結構
-
項目目錄結構如圖:
- app:Demo
- AptAnnotation:java Library主要放一些項目中需要用到的自定義註解及相關代碼
- AptApi:Android Library. 是我們真正對外發布並交由第三方使用的庫,它引用了apt-jar包
- AptCompiler:java Library主要是應用apt技術處理註解,生成相關代碼或者相關源文件,是核心所在。
04.該案例作用
- 前期僅僅是爲了學習,同時先讓demo運行起來,雖然網上很多講解apt的博客寫的很詳細,但是還是有必要結合實際案例練習一下。
-
使用apt實現點擊事件【編譯期註解生成代碼】
- 在一定時間內,按鈕點擊事件只能執行一次。未到指定時間,不執行點擊事件。
-
使用apt實現setContentView功能【運行期註解案例】
- 使用簡單的註解,便可以設置佈局,等效於setContentView(R.layout.activity_main)
-
使用apt實現路由【綜合型案例】
- 比較全面的介紹從零起步,一步一步封裝簡易的路由開源庫。一共用10篇博客記錄了大部分的過程,想要更加深入瞭解,歡迎clone該項目。
05.使用說明
-
如下所示
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化OnceClick,並設置點擊事件間隔是2秒 OnceInit.once(this,2000); } @OnceClick(R.id.tv_1) public void Click1(){ Log.d("tag--------------------","tv_1"); }
06.編譯期註解生成代碼
-
如下所示,在app/build/generated/source/apt/debug/MainActivity$$_Once_Proxy目錄下
// 編譯生成的代碼,不要修改 // 更多內容:https://github.com/yangchong211 package com.ycbjie.ycapt; import android.view.View; import com.ycbjie.api.Finder; import com.ycbjie.api.AbstractInjector; public class MainActivity$$_Once_Proxy<T extends MainActivity> implements AbstractInjector<T> { public long intervalTime; @Override public void setIntervalTime(long time) { intervalTime = time; } @Override public void inject(final Finder finder, final T target, Object source) { View view; view = finder.findViewById(source, 2131165325); if(view != null){ view.setOnClickListener(new View.OnClickListener() { long time = 0L; @Override public void onClick(View v) { long temp = System.currentTimeMillis(); if (temp - time >= intervalTime) { time = temp; target.Click1(); } }}); } view = finder.findViewById(source, 2131165326); if(view != null){ view.setOnClickListener(new View.OnClickListener() { long time = 0L; @Override public void onClick(View v) { long temp = System.currentTimeMillis(); if (temp - time >= intervalTime) { time = temp; target.Click2(v); } }}); } } }
07.運行期註解案例
-
首先先定義自定義註解
//@Retention用來修飾這是一個什麼類型的註解。這裏表示該註解是一個運行時註解。 @Retention(RetentionPolicy.RUNTIME) //@Target用來表示這個註解可以使用在哪些地方。比如:類、方法、屬性、接口等等。 //這裏ElementType.TYPE 表示這個註解可以用來修飾:Class, interface or enum declaration。 //當你用ContentView修飾一個方法時,編譯器會提示錯誤。 @Target({ElementType.TYPE}) //這裏的interface並不是說ContentView是一個接口。 //就像申明類用關鍵字class。申明枚舉用enum。申明註解用的就是@interface。 public @interface ContentView { //返回值表示這個註解裏可以存放什麼類型值。 int value(); }
-
然後需要在activity中做註解解析
@SuppressLint("Registered") public class ContentActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //註解解析 //遍歷所有的子類 for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) { assert c != null; //找到修飾了註解ContentView的類 ContentView annotation = (ContentView) c.getAnnotation(ContentView.class); if (annotation != null) { try { //獲取ContentView的屬性值 int value = annotation.value(); //調用setContentView方法設置view this.setContentView(value); } catch (RuntimeException e) { e.printStackTrace(); } return; } } } }
-
關於如何使用,注意你寫的Activity需要實現ContentActivity,才能讓註解生效
@ContentView(R.layout.activity_four) public class FourActivity extends ContentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); findViewById(R.id.tv_1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(FourActivity.this,"運行期註解",Toast.LENGTH_SHORT).show(); } }); } }
09.使用註解搭建路由[綜合案例]
-
- 比較詳細地分析了阿里路由庫
-
- 爲何需要路由?實現路由方式有哪些,這些方式各有何優缺點?使用註解實現路由需要具備的條件以及簡單原理分析……
-
- 自定義Router註解,Router註解裏有path和group,這便是仿照ARouter對路由進行分組。然後看看註解生成的代碼,手寫路由跳轉代碼。
-
- Processor介紹,重要方法,Element的作用,修飾方法的註解和ExecutableElement
-
- 在Activity類上加上@Router註解之後,便可通過apt來生成對應的路由表,那麼究竟是如何生成的代碼呢?
- 在組件化開發中,有多個module,爲何要在build.gradle配置moduleName,又是如何通過代碼拿到module名稱?
- process處理方法如何生成代碼的,又是如何寫入具體的路徑,寫入文件的?
- 看完這篇文章,應該就能夠理解上面這些問題呢!
-
- 編譯期是在你的項目編譯的時候,這個時候還沒有開始打包,也就是你沒有生成apk呢!路由框架在這個時期根據註解去掃描所有文件,然後生成路由映射文件。這些文件都會統一打包到apk裏,app運行時期做的東西也不少,但總而言之都是對映射信息的處理,如執行執行路由跳轉等。那麼如何設計框架呢?
- 生成的註解代碼,又是如何把這些路由映射關係拿到手,或者說在什麼時候拿到手比較合適?爲何註解需要進行初始化操作?
- 如何得到得到路由表的類名,如何得到所有的routerAddress---activityClass映射關係?
-
[9.6 路由框架設計注意要點]()
- 需要注意哪些要點?
-
- 有哪些注入的方式可以解耦,你能想到多少?路由框架爲何需要依賴注入?路由爲何用註解進行依賴注入,而不是用反射方式注入,或者通過構造方法注入,或者通過接口方式注入?
-
- 在跳轉頁面時,如何傳遞intent參數,或者如何實現跳轉回調處理邏輯?
-
9.9 路由開源庫的使用
-
不帶參數直接跳轉
@Router(path = Path.six) public class SixActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_six); } } ARouter.getsInstance().build(Path.six) .navigation(MainActivity.this, new NavigationCallback() { @Override public void onFound(Postcard postcard) { Log.e("NavigationCallback","找到跳轉頁面"); } @Override public void onLost(Postcard postcard) { Log.e("NavigationCallback","未找到"); } @Override public void onArrival(Postcard postcard) { Log.e("NavigationCallback","成功跳轉"); } });
-
帶參數跳轉
@Router(path = Path.five) public class FiveActivity extends AppCompatActivity { @Extra String title; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_five); //添加這行代碼,實際上就是自動生成了下面獲取參數值的代碼 ARouter.getsInstance().inject(this); //如果不添加插入註解,則可以直接用下面的代碼。 //Intent intent = getIntent(); //String title = intent.getStringExtra("title"); Toast.makeText(this, "title=" + title, Toast.LENGTH_SHORT).show(); } } Bundle bundle = new Bundle(); bundle.putString("title","標題-------------"); ARouter.getsInstance() .build(Path.five) .withBundle(bundle) .navigation();
-
路由註解生成的代碼位置
-
10.其他說明
00.參考案例
- https://www.jianshu.com/p/335...
- https://www.jianshu.com/p/200...
- https://github.com/joyrun/Act...
- https://github.com/BaronZ88/R...
- https://github.com/alibaba/AR...
- https://github.com/Xiasm/Easy...
- https://github.com/chenenyu/R...
- https://www.jianshu.com/p/8a3...
- https://www.jianshu.com/p/e2d...
01.關於博客彙總鏈接
02.關於我的博客
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/...
- 簡書:http://www.jianshu.com/u/b7b2...
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
- 開源中國:https://my.oschina.net/zbj161...
- 泡在網上的日子:http://www.jcodecraeer.com/me...
- 郵箱:[email protected]
- 阿里雲博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xi...
- 掘金:https://juejin.im/user/593943...