13.Android之註解問題

楊充發表於2019-02-15

目錄介紹

  • 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”表示過濾掉所有型別的警告,很好理解吧!那麼還可以傳遞什麼引數來過濾警告呢?看看下面的表格你就會知道啦:
    • image
  • 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。我推薦你使用前者。
  • 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 - @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;因為它是提供給外部使用的。
    • image
  • 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;
        }
      }
      複製程式碼
  • 依賴查詢和依賴注入
    • 依賴查詢和依賴注入一樣屬於控制反轉原則的具體實現,不同於依賴注入的被動接受,依賴查詢這是主動請求,在需要的時候通過呼叫框架提供的方法來獲取物件,獲取時需要提供相關的配置檔案路徑、key等資訊來確定獲取物件的狀態。

13.0.0.7 路由框架為何需要依賴注入,不用的話行不行?路由用什麼方式注入,這些注入方式各具何特點,為何選擇註解注入?

  • 路由框架為何需要依賴注入,不用的話行不行?
    • 路由的目的就是要實現跳轉,但是你有沒有想過,兩個Activity之間的跳轉肯定免不了要傳入一些引數,如果我們在跳轉後還要通過intent去獲取引數,這樣豈不是很麻煩,如果可以自動把引數賦給屬性多好啊!
    • 元件化中兩個module之間可能有一些功能並不需要跳轉頁面,如支付模組要獲取使用者模組的使用者id,並不需要跳轉頁面,那麼我們就要持有使用者模組含有獲取使用者id功能的類的引用,如果我們在支付模組建立一個使用者模組的功能引用,顯然就違背瞭解耦的規則。這兩個問題顯然用依賴注入的方式會更好些,如果你用過ARouter,你會發現ARouter中的服務(IProvider)就是通過依賴注入實現的。
  • 路由用什麼方式注入,這些注入方式各具何特點,為何選擇註解注入?
    • 可以使用註解的方式,至於為啥,接下來我分析一下……
      • 為什麼要用註解實現依賴注入,因為我們用了apt啊,那豈不是天生實現依賴注入的利器。如果你去寫配置檔案或構造方法等等,未免太複雜。
    • 無法通過構造方法注入【構造器注入】
      • 在多元件並行開發過程中,因為兩個module沒有引用關係,所以就不能通過構造方法傳入要依賴的類,這個時候怎麼辦呢?連對方的引用都得不到,如何進行依賴呢?
    • 不要通過反射方式注入
      • 可能你會想到反射,我們先pass這個方案,能不用反射就能做好的前提下,我們最好不要用反射。
    • 不建議通過介面方式注入【也可以,但若是開源成lib,則不建議】
      • 可能有人會想到,在基類下沉一個介面功能標準,比如在基類庫base模組定義獲取使用者id的介面,然後在使用者模組實現介面的方法。那麼當支付模組需要用到這個功能,就宣告這個介面,然後一行註解通過框架為你建立例項,這樣使用者模組只需要提供功能,並不需要關心誰在用這個功能,這樣豈不是大大減小了耦合。
      • 但是有一點,元件化實踐中,有很多個模組,那豈不是所有的都需要依賴base基類庫,才能使用到介面注入,這樣不易轉接。

13.0.0.8 實際開發中使用到註解有哪些,使用註解替代列舉?如何通過註解限定傳入的型別?為何說列舉損耗效能?

  • 實際開發中使用到註解有哪些,使用註解替代列舉?
    /**
     * 播放模式
     * -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.關於我的部落格

03.12.註解練習案例開原始碼

相關文章