目錄介紹
- 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.Annotation註解詳細介紹
1.Annotation庫的簡單介紹 2.@Nullable和@NonNull 3.資源型別註釋 4.型別定義註釋 5.執行緒註釋 6.RGB顏色紙註釋 7.值範圍註釋 8.許可權註釋 9.重寫函式註釋 10.返回值註釋 11.@Keep註釋 12.@SuppressWarnings註解 13.其他 複製程式碼
- 02.Dagger2深入分析,待更新
- 03.註解詳細介紹
- 什麼是註解,註解分類有哪些?自定義註解分類?執行註解案例展示分析,以一個最簡單的案例理解註解……使用註解替代列舉,使用註解限定型別
- 04.APT技術詳解
- 什麼是apt?理解註解處理器的作用和用途……android-apt被替代?annotationProcessor和apt區別? 什麼是jack編譯方式?
- 06.自定義annotation註解
- @Retention的作用?@Target(ElementType.TYPE)的解釋,@Inherited註解可以被繼承嗎?Annotation裡面的方法為何不能是private?
- 07.註解之相容kotlin
- 後期更新
- 08.註解之處理器類Processor
- 處理器類Processor介紹,重要方法,Element的作用,修飾方法的註解和ExecutableElement,瞭解修飾屬性、類成員的註解和VariableElement……
- 10.註解遇到問題和解決方案
- 無法引入javax包下的類庫,成功執行一次,修改程式碼後再執行就報錯
- 11.註解代替列舉
- 在做記憶體優化時,推薦使用註解代替列舉,因為列舉佔用的記憶體更高,如何說明列舉佔用記憶體高呢?這是為什麼呢?
- 12.註解練習案例開原始碼
- 註解學習小案例,比較系統性學習註解並且應用實踐。簡單應用了執行期註解,通過註解實現了setContentView功能;簡單應用了編譯器註解,通過註解實現了防暴力點選的功能,同時支援設定時間間隔;使用註解替代列舉;使用註解一步步搭建簡單路由案例。結合相應的部落格,在來一些小案例,從此應該對註解有更加深入的理解……
好訊息
- 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
- 連結地址:github.com/yangchong21…
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議或者問題,萬事起於忽微,量變引起質變!
13.0.0.1 什麼是註解?系統內建的標準註解有哪些?SuppressWarnings用過沒?Android中提供了哪些與執行緒相關的註解?
- 什麼是註解?
- Annotation(註解)就是Java提供了一種元程式中的元素關聯任何資訊或者任何後設資料(metadata)的途徑和方法。基本規則:Annotation(註解)不能影響程式程式碼的執行,無論增加,刪除Annotation(註解),程式碼都始終如一的執行。
- 系統內建的標準註解有哪些?
- @Override表示子類對父類方法的重寫所帶的註解;@Deprecated表示已經過時,不建議使用,但是依然可以使用;@SuppressWarnings("all")用來抑制編譯時的警告資訊,編譯器在你寫程式碼的時候,難免會出現很多的警告,有強迫症的程式猿會感到極其不爽,那麼腫麼辦呢?@SuppressWarnings註解就可以告訴編譯器,別警告啦,程式碼不會有問題的。
- SuppressWarnings用過沒?
- @SuppressWarnings註解一定要傳遞一個引數給它,來表示過濾掉哪些型別的警告,筆者新增了”all”表示過濾掉所有型別的警告,很好理解吧!那麼還可以傳遞什麼引數來過濾警告呢?看看下面的表格你就會知道啦:
- Android中提供了哪些與執行緒相關的註解?
- @UiThread,通常可以等同於主執行緒,標註方法需要在UIThread執行,比如View類就使用這個註解
- @MainThread 主執行緒,經常啟動後建立的第一個執行緒
- @WorkerThread 工作者執行緒,一般為一些後臺的執行緒,比如AsyncTask裡面的doInBackground就是這樣的
- @BinderThread 註解方法必須要在BinderThread執行緒中執行,一般使用較少
13.0.0.2 什麼是apt?apt的難點和優勢?什麼是註解處理器?抽象處理器中四個方法有何作用?annotationProcessor和apt區別?
- 什麼是apt?
- APT(Annotation Processing Tool的簡稱),可以在程式碼編譯期解析註解,並且生成新的Java檔案,減少手動的程式碼輸入。現在有很多主流庫都用上了 APT,比如 Dagger2, ButterKnife等
- apt的難點和優勢?
- 難點:就apt本身來說沒有任何難點可言,難點一在於設計模式和解耦思想的靈活應用,二在與程式碼生成的繁瑣,你可以手動字串拼接,當然有更高階的玩法用squareup的javapoet,用建造者的模式構建出任何你想要的原始碼
- 優點:它的強大之處無需多言,看代表框架的原始碼,你可以學到很多新姿勢。總的一句話:它可以做任何你不想做的繁雜的工作,它可以幫你寫任何你不想重複程式碼。
- 什麼是註解處理器?
- 註解處理器是一個在javac中的,用來編譯時掃描和處理的註解的工具。你可以為特定的註解,註冊你自己的註解處理器。
- 註解處理器可以生成Java程式碼,這些生成的Java程式碼會組成 .java 檔案,但不能修改已經存在的Java類(即不能向已有的類中新增方法)。而這些生成的Java檔案,會同時與其他普通的手寫Java原始碼一起被javac編譯。
- 抽象處理器中四個方法有何作用?
- 每一個註解處理器都要繼承於AbstractProcessor,如下所示:
public class MyProcessor 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(); } } 複製程式碼
- 這幾個方法如下
- init(ProcessingEnvironment processingEnvironment): 每一個註解處理器類都必須有一個空的建構函式。然而,這裡有一個特殊的init()方法,它會被註解處理工具呼叫,並輸入ProcessingEnviroment引數。ProcessingEnviroment提供很多有用的工具類Elements,Types和Filer。後面我們將看到詳細的內容。
- process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 這相當於每個處理器的主函式main()。你在這裡寫你的掃描、評估和處理註解的程式碼,以及生成Java檔案。輸入引數RoundEnviroment,可以讓你查詢出包含特定註解的被註解元素。後面我們將看到詳細的內容。
- getSupportedAnnotationTypes(): 這裡你必須指定,這個註解處理器是註冊給哪個註解的。注意,它的返回值是一個字串的集合,包含本處理器想要處理的註解型別的合法全稱。換句話說,你在這裡定義你的註解處理器註冊到哪些註解上。
- getSupportedSourceVersion(): 用來指定你使用的Java版本。通常這裡返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支援Java 7的話,你也可以返回SourceVersion.RELEASE_7。我推薦你使用前者。
- 每一個註解處理器都要繼承於AbstractProcessor,如下所示:
- annotationProcessor和apt區別?
- Android 官方的 annotationProcessor 同時支援 javac 和 jack 編譯方式,而 android-apt 只支援 javac 方式。當然,目前 android-apt 在 Android Gradle 外掛 2.2 版本上面仍然可以正常執行,如果你沒有想支援 jack 編譯方式的話,可以繼續使用 android-apt。
- 什麼是jack編譯方式?
- Jack (Java Android Compiler Kit)是新的Android 編譯工具,從Android 6.0 開始加入,替換原有的編譯工具,例如javac, ProGuard, jarjar和 dx。它主要負責將java程式碼編譯成dex包,並支援程式碼壓縮,混淆等。
- Jack工具的主要優勢
- 完全開放原始碼,原始碼均在AOSP中,合作伙伴可貢獻原始碼
- 加快編譯原始碼,Jack 提供特殊的配置,減少編譯時間:pre-dexing, 增量編譯和Jack編譯伺服器.
- 支援程式碼壓縮,混淆,重打包和multidex,不在使用額外單獨的包,例如ProGuard。
13.0.0.3 註解是怎麼分類的?自定義註解又是怎麼分類的?執行期註解原理是什麼?
- 註解是怎麼分類的?
- 標準 Annotation
- 包括 Override, Deprecated, SuppressWarnings,是java自帶的幾個註解,他們由編譯器來識別,不會進行編譯, 不影響程式碼執行,至於他們的含義不是這篇部落格的重點,這裡不再講述。
- 元 Annotation
- @Retention, @Target, @Inherited, @Documented,它們是用來定義 Annotation 的 Annotation。也就是當我們要自定義註解時,需要使用它們。
- 自定義 Annotation
- 根據需要,自定義的Annotation。而自定義的方式,下面我們會講到。
- 標準 Annotation
- 自定義註解又是怎麼分類的?
- 同樣,自定義的註解也分為三類,通過元Annotation - @Retention 定義:
- @Retention(RetentionPolicy.SOURCE)
- 原始碼時註解,一般用來作為編譯器標記。如Override, Deprecated, SuppressWarnings。
- @Retention(RetentionPolicy.RUNTIME)
- 執行時註解,在執行時通過反射去識別的註解。
- 定義執行時註解,只需要在宣告註解時指定@Retention(RetentionPolicy.RUNTIME)即可。
- 執行時註解一般和反射機制配合使用,相比編譯時註解效能比較低,但靈活性好,實現起來比較簡答。
- @Retention(RetentionPolicy.CLASS)
- 編譯時註解,在編譯時被識別並處理的註解,這是本章重點。
- 編譯時註解能夠自動處理Java原始檔並生成更多的原始碼、配置檔案、指令碼或其他可能想要生成的東西。
- 執行期註解原理是什麼?
- 實際註解案例有哪些?
- 執行時註解:retrofit
- 編譯時註解:Dagger2, ButterKnife, EventBus3
13.0.0.4 在自定義註解中,Annotation裡面的方法為何不能是private?Annotation裡面的方法引數有哪些?
- Annotation裡面的方法為何不能是private?
- 只能用public或預設(default)這兩個訪問權修飾.例如,String value();不能是private;因為它是提供給外部使用的。
- Annotation裡面的方法引數有哪些
- 引數只能使用基本型別byte,short,char,int,long,float,double,boolean八種基本資料型別和 String,Enum,Class,annotations等資料型別,以及這一些型別的陣列.例如,String value();這裡的引數型別就為String;
13.0.0.5 @Inherited是什麼意思?註解是不可以繼承的,這是為什麼?註解的繼承這個概念該如何理解?
- @Inherited是什麼意思?
- 該註解的字面意識是繼承,但你要知道註解是不可以繼承的。@Inherited是在繼承結構中使用的註解。
- 如果你的註解是這樣定義的:
- 當你的註解定義到類A上,此時,有個B類繼承A,且沒使用該註解。但是掃描的時候,會把A類設定的註解,掃描到B類上。
@Inherited @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Test { //... } 複製程式碼
- 註解是不可以繼承的,這是為什麼?
- 註解不能繼承
- 註解的繼承這個概念該如何理解?
- 這裡講的繼承並不是通過@Inherited修飾的註解。這個“繼承”是一個註解的使用技巧,使用上的感覺類似於依賴倒置,來自於ButterKnife原始碼。
- 這是ButterKnife的OnClick 註解。特殊的地方在於**@OnClick修飾了註解@ListenerClass**,並且設定了一些只屬於@OnClick的屬性。
- 那這樣的作用是什麼呢?凡是修飾了@OnClick的地方,也就自動修飾了@ListenerClass。類似於@OnClick是@ListenerClass的子類。而ButterKnife有很多的監聽註解@OnItemClick、@OnLongClick等等。這樣在做程式碼生成時,不需要再單獨考慮每一個監聽註解,只需要處理@ListenerClass就OK。
@Target(METHOD) @Retention(CLASS) @ListenerClass( targetType = "android.view.View", setter = "setOnClickListener", type = "butterknife.internal.DebouncingOnClickListener", method = @ListenerMethod( name = "doClick", parameters = "android.view.View" ) ) public @interface OnClick { /** View IDs to which the method will be bound. */ int[] value() default { View.NO_ID }; } 複製程式碼
13.0.0.6 什麼是依賴注入?依賴注入案例舉例說明,有哪些方式,具備什麼優勢?依賴查詢和依賴注入有什麼區別?
- 什麼是依賴注入
- 在物件導向程式設計中,經常處理處理的問題就是解耦,程式的耦合性越低表明這個程式的可讀性以及可維護性越高。控制反轉(Inversion of Control或IoC)就是常用的物件導向程式設計的設計原則,使用這個原則我們可以降低耦合性。其中依賴注入是控制反轉最常用的實現。
- 依賴注入案例舉例說明,有哪些方式,具備什麼優勢?
- 依賴是程式中常見的現象,比如類Car中用到了GasEnergy類的例項energy,通常的做法就是在Car類中顯式地建立GasEnergy類的例項,並賦值給energy。如下面的程式碼
interface Energy { } class GasEnergy implements Energy { } class Car { Energy energy = new GasEnergy(); } 複製程式碼
- 存在問題
- 類Car承擔了多餘的責任,負責energy物件的建立,這必然存在了嚴重的耦合性。舉一個現實中的例子,一輛汽車使用哪種能源不是由汽車來決定,而是由汽車製造商(CarMaker)來決定,這是汽車製造商的責任。
- 可擴充套件性,假設我們想修改能源為電動力,那麼我們必然要修改Car這個類,明顯不符合開放閉合原則。
- 不利於單元測試。
- 有哪些方式解耦
- 依賴注入是這樣的一種行為,在類Car中不主動建立GasEnergy的物件,而是通過外部傳入GasEnergy物件形式來設定依賴。 常用的依賴注入有如下三種方式
- 構造器注入
- 將需要的依賴作為構造方法的引數傳遞完成依賴注入。
class Car { Energy mEnergy; public Car(Energy energy) { mEnergy = energy; } } 複製程式碼
- Setter方法注入
- 增加setter方法,引數為需要注入的依賴亦可完成依賴注入。
class Car { Energy mEnergy; public void setEnergy(Energy energy) { mEnergy = energy; } } 複製程式碼
- 介面注入
- 介面注入,聞其名不言而喻,就是為依賴注入建立一套介面,依賴作為引數傳入,通過呼叫統一的介面完成對具體實現的依賴注入。
- 介面注入和setter方法注入類似,不同的是介面注入使用了統一的方法來完成注入,而setter方法注入的方法名稱相對比較隨意。
interface EnergyConsumerInterface { public void setEnergy(Energy energy); } class Car implements EnergyConsumerInterface { Energy mEnergy; public void setEnergy(Energy energy) { mEnergy = energy; } } 複製程式碼
- 依賴是程式中常見的現象,比如類Car中用到了GasEnergy類的例項energy,通常的做法就是在Car類中顯式地建立GasEnergy類的例項,並賦值給energy。如下面的程式碼
- 依賴查詢和依賴注入
- 依賴查詢和依賴注入一樣屬於控制反轉原則的具體實現,不同於依賴注入的被動接受,依賴查詢這是主動請求,在需要的時候通過呼叫框架提供的方法來獲取物件,獲取時需要提供相關的配置檔案路徑、key等資訊來確定獲取物件的狀態。
13.0.0.7 路由框架為何需要依賴注入,不用的話行不行?路由用什麼方式注入,這些注入方式各具何特點,為何選擇註解注入?
- 路由框架為何需要依賴注入,不用的話行不行?
- 路由的目的就是要實現跳轉,但是你有沒有想過,兩個Activity之間的跳轉肯定免不了要傳入一些引數,如果我們在跳轉後還要通過intent去獲取引數,這樣豈不是很麻煩,如果可以自動把引數賦給屬性多好啊!
- 元件化中兩個module之間可能有一些功能並不需要跳轉頁面,如支付模組要獲取使用者模組的使用者id,並不需要跳轉頁面,那麼我們就要持有使用者模組含有獲取使用者id功能的類的引用,如果我們在支付模組建立一個使用者模組的功能引用,顯然就違背瞭解耦的規則。這兩個問題顯然用依賴注入的方式會更好些,如果你用過ARouter,你會發現ARouter中的服務(IProvider)就是通過依賴注入實現的。
- 路由用什麼方式注入,這些注入方式各具何特點,為何選擇註解注入?
- 可以使用註解的方式,至於為啥,接下來我分析一下……
- 為什麼要用註解實現依賴注入,因為我們用了apt啊,那豈不是天生實現依賴注入的利器。如果你去寫配置檔案或構造方法等等,未免太複雜。
- 無法通過構造方法注入【構造器注入】
- 在多元件並行開發過程中,因為兩個module沒有引用關係,所以就不能通過構造方法傳入要依賴的類,這個時候怎麼辦呢?連對方的引用都得不到,如何進行依賴呢?
- 不要通過反射方式注入
- 可能你會想到反射,我們先pass這個方案,能不用反射就能做好的前提下,我們最好不要用反射。
- 不建議通過介面方式注入【也可以,但若是開源成lib,則不建議】
- 可能有人會想到,在基類下沉一個介面功能標準,比如在基類庫base模組定義獲取使用者id的介面,然後在使用者模組實現介面的方法。那麼當支付模組需要用到這個功能,就宣告這個介面,然後一行註解通過框架為你建立例項,這樣使用者模組只需要提供功能,並不需要關心誰在用這個功能,這樣豈不是大大減小了耦合。
- 但是有一點,元件化實踐中,有很多個模組,那豈不是所有的都需要依賴base基類庫,才能使用到介面注入,這樣不易轉接。
- 可以使用註解的方式,至於為啥,接下來我分析一下……
13.0.0.8 實際開發中使用到註解有哪些,使用註解替代列舉?如何通過註解限定傳入的型別?為何說列舉損耗效能?
- 實際開發中使用到註解有哪些,使用註解替代列舉?
- 程式碼如下所示
- 具體的案例,可以看我視訊播放器開源庫:github.com/yangchong21…
/** * 播放模式 * -1 播放錯誤 * 0 播放未開始 * 1 播放準備中 * 2 播放準備就緒 * 3 正在播放 * 4 暫停播放 * 5 正在緩衝(播放器正在播放時,緩衝區資料不足,進行緩衝,緩衝區資料足夠後恢復播放) * 6 正在緩衝(播放器正在播放時,緩衝區資料不足,進行緩衝,此時暫停播放器,繼續緩衝,緩衝區資料足夠後恢復暫停 * 7 播放完成 */ public @interface CurrentState{ int STATE_ERROR = -1; int STATE_IDLE = 0; int STATE_PREPARING = 1; int STATE_PREPARED = 2; int STATE_PLAYING = 3; int STATE_PAUSED = 4; int STATE_BUFFERING_PLAYING = 5; int STATE_BUFFERING_PAUSED = 6; int STATE_COMPLETED = 7; } 複製程式碼
- 如何通過註解限定傳入的型別?
- 程式碼如下所示
- 具體的案例,可以看我視訊播放器開源庫:github.com/yangchong21…
- 列舉最大的作用是提供了型別安全。為了彌補Android平臺不建議使用列舉的缺陷,官方推出了兩個註解,IntDef和StringDef,用來提供編譯期的型別檢查。
- 倘若,傳入的值不是IjkPlayerType中的型別,則會導致編譯提醒和警告。
/** * 通過註解限定型別 * TYPE_IJK IjkPlayer,基於IjkPlayer封裝播放器 * TYPE_NATIVE MediaPlayer,基於原生自帶的播放器控制元件 */ @Retention(RetentionPolicy.SOURCE) public @interface IjkPlayerType { int TYPE_IJK = 111; int TYPE_NATIVE = 222; } @IntDef({IjkPlayerType.TYPE_IJK,IjkPlayerType.TYPE_NATIVE}) public @interface PlayerType{} //使用 /** * 設定播放器型別,必須設定 * 注意:感謝某人建議,這裡限定了傳入值型別 * 輸入值:ConstantKeys.IjkPlayerType.TYPE_IJK 或者 ConstantKeys.IjkPlayerType.TYPE_NATIVE * @param playerType IjkPlayer or MediaPlayer. */ public void setPlayerType(@ConstantKeys.PlayerType int playerType) { mPlayerType = playerType; } 複製程式碼
關於其他內容介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:github.com/yangchong21…
- 知乎:www.zhihu.com/people/yczb…
- 簡書:www.jianshu.com/u/b7b2c6ed9…
- csdn:my.csdn.net/m0_37700275
- 喜馬拉雅聽書:www.ximalaya.com/zhubo/71989…
- 開源中國:my.oschina.net/zbj1618/blo…
- 泡在網上的日子:www.jcodecraeer.com/member/cont…
- 郵箱:yangchong211@163.com
- 阿里雲部落格:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:segmentfault.com/u/xiangjian…
- 掘金:juejin.im/user/593943…