Android應用開發編譯框架流程與IDE及Gradle概要
1 背景
建議閱讀本文之前先閱讀《Android Studio入門到精通》和《Groovy腳本基礎全攻略》及《Gradle腳本基礎全攻略》三篇博客作為背景知識,這樣才能更好、更系統的串起來。本文的核心就是下圖:
關于Gradle的Android插件本文不會過多的說明,只給一個拋磚引玉的提示,詳細使用參見文檔API及Gradle配置,其實個性化的構建配置一般都是Gradle與Groovy的編寫,與Android插件沒太多關系,所以重點還在Groovy與Gradle構建。
不過在構建之前還需要先了解一些構建的流程和相關IDE及編譯特性,具體下面會說到。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
2 Android應用編譯框架
在我們開發App時大多數時候的編譯流程都是直接通過IDE的按鈕或者命令行一步到位apk的生成,直觀上的感覺好像源碼到Apk的生成只有一步,實質不是這樣的。從我們點擊按鈕那一刻到生成apk的過程實際是非常復雜的,涉及到很多步驟,下面我們來粗略看看。
2-1 Android應用源碼到生成Apk流程
Android應用的編譯其實就是打包為.apk文件,這個被打包的zpk文件實質其實是一個壓縮包(譬如你可以重命名后綴解壓),這個壓縮包至少包含編譯為.class文件轉換的.dex文件、一個二進制的AndroidManifest.xml文件、編譯的資源文件resources.arsc、未編譯的資源文件等,然后在運行前再對這個壓縮包進行簽名操作即可。具體流程如下圖:
Android構建系統是用來構建、測試、運行和封裝應用程序的一個工具箱,整個構建系統我們可以通過已經集成OK的AS或者命令行來使用,構建過程會涉及許多工具和流程,通過這些工具和流程生成了許多中間文件,然后最終生成了APK。下圖描述了構建過程中涉及的不同工具和過程:
構建系統將依據配置的product flavors、build types和dependencies合并所有的資源,如果此時不同文件夾含有相同名稱或設置的資源則會按照如下規則進行覆蓋:dependencies覆蓋build types,build types覆蓋product flavors,product flavors覆蓋main資源文件夾。下面是對上圖典型構建過程的詳細描述:
-
Android資源打包工具(AAPT)將應用的資源文件(譬如AndroidManifest.xml文件和Activity相關的XML文件等)進行編譯,生成的一個R.java文件就是關聯我們Java代碼與資源文件的基礎。
-
aidl工具將應用中所有.aidl的文件轉換為Java接口。
-
Java編譯器將應用中所有Java代碼(包括R.java和aidl接口)編譯輸出為class類文件。
-
dex工具將應用編譯輸出的類文件(包括第三方jar包)轉換為Dalvik字節碼,.dex文件就可以最終被打包如.apk文件。
-
apkbuilder工具將所有非編譯資源(譬如images)、編譯資源和DEX文件打包成一個APK文件。
-
一旦.apk文件生成以后就必須進行簽名(debug或者release簽名)才能在設備上運行。
-
通過zipalign工具對齊APK,減少APP在設備上運行時的內存占用。
整個構建過程生成的東東默認都在app/build目錄下(包括中間產物和最終產物)。
特別注意: 應用程序構建有一個64K方法限制說法,超過64K會拋出如下構建信息:
Unable to execute dex: method ID not in [0, 0xffff]: 65536.
如果撞上該問題(一般是超大型項目或者瘋狂使用開源框架等就會越界)請點我參考解決。
下面這幅圖就是整個Android應用(不包含NDK部分)的構建編譯框架詳細流程說明(來源于官方):
通過這幅圖可以明顯看出來Android應用程序編譯源碼到最終的apk產物過程實際是十分復雜的,我們平時只是把這些細則交給了IDE和構建框架而已,所以察覺不到這些細則過程。
2-2 Android應用編譯流程相關主要工具
上面簡單說明了從源碼到APK的編譯框架過程,下面簡單說說這一過程中涉及的幾個重要的編譯相關工具,具體如下文。
2-2-1 aapt工具
aapt(Android Asset Packaging Tool)是Android資源打包工具,在SDK的tools目錄下,我們可以使用該工具查看、創建、更新ZIP格式的文件(zip、jar、apk等),也可以將資源文件編譯成二進制文件。平時我們很少直接使用這個工具,通常都向前面編譯流程圖介紹的一樣,編譯框架會自動幫助我們調運這個工具。下面是aapt命令配置OK后運行的情況:
關于aapt命令工具的用法我們直接在終端輸入aapt回車即可看見相關help文檔,這里只給出幾個我們比較常用的命令,其他的參見help文檔。
列出壓縮包的內容 #aapt l[ist] [-v] [-a] file.{zip,jar,apk}
查看APK包中指定內容 #aapt d[ump] file.{zip,jar,apk}
這個比較實用,有時候想快速知道一個app的包名等信息時可以通過該命令快捷獲取。
aapt其他操作命令參數
aapt命令還可以進行打包生成資源壓縮包、給壓縮包刪除或者添加指定文件等操作,這也是編譯構建系統中生成R.java等流程使用的核心工具命令。感興趣的可以自行google通過aapt命令行去手動打包資源文件。
2-2-2 aidl工具
AIDL(Android Interface Definition Language)是一種IDL語言,用來生成可以在Android設備上兩個進程間通信(IPC)的代碼。如果在一個進程中要調用另一個進程的對象則可以使用AIDL生成可序列化的參數進行傳遞。我們通常開發中都會依據AIDL規則編寫相應的aidl文件,然而我們會發現在build目錄下會出現我們aidl對應的java接口文件,這就是因為編譯框架通過了aidl編譯工具將.aidl文件轉換為.java接口文件。
下面就是在SDK的tools目錄下將aidl文件轉為java文件的aidl工具:
關于aidl工具的用法很簡單,幫助文檔說的很明白了,詳細感興趣的可以自己去嘗試一下,這里不再詳細說明;基本不怎么手動去用,我反正沒仔細研究過。
2-2-3 dx工具
我們通過aapt將資源打包同時生成R.java,通過aidl將aidl文件編譯生成對應的java文件,再通過Java編譯器將R.java、Java源碼文件、aidl的Java文件編譯為相關的class文件。這時候對于純Java來說class字節碼就是JVM虛擬機可執行的文件了,然而對于Android的Dalvik和ART虛擬機來說class文件是無法直接執行的,他們可執行的文件其實是dex格式的文件,所以我們需要通過SDK/tools目錄的dx工具將class字節碼編譯為dex執行文件。如下是dx工具命令基本情況:
說到了dx工具就不得不再強調幾個比較重要的概念和基本原理,具體如下詳情。
apk、dex、odex文件關系:
dex是Dalvik VM Executes(Android Dalvik執行程序,現在ART也繼續兼容使用此格式執行程序)的全稱,dex格式文件通常在Android虛擬機中執行時都會先進行優化 ,優化后的文件大小一般都會比原dex文件大。這種優化的發生時機會分兩種情況:
-
系統預置應用:在系統編譯后直接生成以odex為后綴的優化文件,啟動app時不用再解包取dex,效率高,所以在發布系統預置應用時可以看見一個不包含dex文件的apk文件和一個相應的odex文件。
-
非預置應用:編譯直接生成包含dex文件的apk,包含在apk文件里的dex文件會在運行時被優化,優化后的文件將被保存在緩存中。
這也就是為何系統編譯預置app源碼后會看到odex和不含dex的apk文件,而獨立app編譯后只看見內含dex的apk文件的原因。之前在上家公司做盒子開發時有人曾經有過這個疑惑,當然我也有過!
dex文件65535異常方法限制原因:
Android的Dalvik和ART運行時環境都能夠執行dx工具生成的.dex文件,也就是說Dalvik和ART使用了同一套Dalvik指令集。通過相關資料查詢可以知道Dalvik指令集使用了16位寄存器來保存項目中所有的方法引用,2的16次方是65536,也就是說一個dex文件最多只能引用65536個方法,所以對于Dalvik和ART運行時環境來說都有這個局限性。我勒個去!!!這不就是我們有時候編譯項目時拋出Android Dex方法限制異常的原因么(上面也有介紹,不明白上翻回看),也就是說編譯時拋出這個異常是因為項目包含的方法太多導致的,好在Google官方也意識到了這個缺陷,所以他們給出了解決方案,如下:
-
使用ProGuard清除項目中無用方法,使用相關腳本對項目中沒用到的第三方庫中的方法進行清除處理;
-
由于Dalvik運行時環境限制一個apk只能包含一個classes.dex文件,所以我們可以使用Multidex Support Library支持包讓一個apk里支持多個.dex文件,這樣就可以突破65536的限制。
dx過程中這個錯誤非常經典,一般都出現在大量使用了第三方庫的情況下,所以需要注意一下。
2-2-4 apkbuilder工具
關于apkbuilder工具這個叫法其實已經有些過時了,因為比較新版本的SDK中已經將apkbuilder工具去掉了,不過apkbuilder工具的實質其實是對/android-sdk-linux/tools/lib/sdklib.jar中ApkBuilderMain等的一個封裝而已,所以即使沒有了該工具我們也可以自己實現封裝,不過新的編譯框架會自動幫助我們解決這一過程,我們無需手動處理。該過程的實質其實和壓縮工具的性質差不多,只是它將相關資源、dex文件等打包壓縮成了一個指定壓縮方式和深度等的apk文件而已。
2-2-5 keytool與jarsigner工具
對apkbuilder打包壓縮出來的apk進行簽名的實質其實是在應用程序的特定字段寫入特定的標記信息,以便用來表示該軟件已經通過了簽署者審核。簽名的作用主要是識別應用的作者、檢測應用程序是否已經改變、檢測是否為同一個應用等。
一般我們可以通過keytool工具生成簽名私鑰,然后通過jarsigner工具使用私鑰對應用進行簽名。不過這一過程非常簡單,這里就不再啰嗦了,自行腦補。
2-2-6 zipalign工具
zipalign工具可以對打包的應用進行優化,優化過的應用在運行時執行效率可以達到最大限度且會占用更少的RAM(Random Access Memory)內存。zipalign對apk文件中數據進行4字節對齊,也就是說編譯器把4個字節作為一個單位來進行操作,這樣CPU就能對代碼進行高效訪問,因為對齊后Android系統可以通過調用mmap函數讀取文件,也就是說進程可以像讀寫內存一樣操作我們apk中普通文件,所以當對齊的應用在系統中執行時通過共享內存IPC讀取資源就能得到較高的性能,如果沒有對齊處理則必須顯示的調運read等方法去操作數據,也就是說運行過程會比較緩慢且會花費更多的內存,從而導致性能下降。
關于zipalign工具的使用這里也不再啰嗦了,因為通常編譯框架允許我們直接配置腳本而不用手動執行命令。
2-2-7 ProGuard工具
ProGuard是一個壓縮、優化和混淆Java字節碼class文件的工具,它可以刪除無用的類、字段、方法、屬性及沒用的注釋等,最大限度地優化class字節碼文件。它還可以使用簡短的無意義名稱來重命名已經存在的類、字段、方法和屬性。我們通常用它來混淆最終的項目,然后稍微增加項目被反編譯的難度,當然了,對于現在的技術來說反編譯難度這個已經不是問題了,我們還是重點關注他的優化無用資源和簡潔替代吧。
關于ProGuard工具的使用這里也不再啰嗦了,因為通常編譯框架允許我們直接配置腳本而不用手動執行命令。
2-2-8 jobb工具
其實這個工具不屬于正常編譯框架的流程,算是Android的一個拓展特性而已。從Android 2.3版本開始系統增加了一個OBB文件系統(權限訪問限制隔離文件系統)和StorageManager類用來管理外部存儲上的數據安全。
如果你之前在Android手機上安裝過《紀念碑谷》或者《機械迷城》游戲,那你就能對這里講的jobb工具和OBB文件系統有一個很好的理解。還記不記得在安裝幾十兆大小的游戲后你還需要下載一個兩百多兆的zip壓縮包放到文件系統的Android/obb/[GamePackageName]目錄下才能正常玩游戲。之所以這么做是因為我們的游戲工程中包含大量的資源(圖片、視頻、音樂等),直接編譯為APK可能會高達好幾百兆,系統在安裝APK時又會對APK文件大小有一個限制,這么大的APK文件必定會導致Android系統無法正常安裝該APK;相信此時機智的你指定會說,我們把這些資源直接放到SD卡上不就完了?哈哈,你想沒想過一問題,如果直接放到SD卡,系統的音樂、視頻、圖片等管理器豈不是直接可以索引到這些東東了,那得多不好(插一句,還可以將這些資源去掉后綴保存,這樣這些媒體庫就無法索引了,譬如Android系統郵件應用的附件就是這么設計的,真機智!)。好在Android 2.3的OBB文件可以很牛叉的解決這一系列問題。
既然這樣的話,想必OBB文件系統一定會要求存儲的文件必須符合一定的格式,jobb就是解決這個問題的工具。jobb允許我們生成加密或不加密的OBB格式擴展文件,OBB文件可以作為Android應用程序的擴展資源文件,獨立于APK文件存在。下面就是jobb工具的文檔:
關于jobb工具這里就不深入說明了,一般游戲等大資源應用開發中才可能會考慮到這種設計,用到時再腦補也不遲,這里知道有這么回事就行了。
2-3 Android應用編譯Jack和Jill新工具鏈
到這里其實大家對常規的應用編譯框架已經有了一個不錯的認識了,But問題來了,你是不是也覺得當前的Android編譯構建流程相當蛋疼(編譯構建巨慢)呢,其實Google官方似乎也意識到了這個問題,他們還在今年的Google IO大會上給出了當前階段的一些優化交代,其中最值得嘗試和一提的亮點是Jack和Jill兩個新的編譯器(當前官方聲稱還是Experimental試驗性的編譯器,還不夠健壯,還在bug收集階段,當前不支持注解處理,不支持Java 8等,所以還是慎重),官方說這兩個編譯器旨在簡化安卓的編譯流程,說白了也就是嘗試加快編譯構建速度。下面先來看下使用Jack和Jill編譯器的構建流程:
可以看見,Jack是一個基于Java編譯器和ProGuard的工具,但是目前版本還不支持ProGuard的一些高級功能(譬如移除日志代碼)。Jill將Java庫字節碼轉化成名為jayce的中間字節碼.jack文件,Jack對Java源碼和jayce字節碼進行編譯,生成經過優化的dex字節碼。
想嘗鮮使用Jack和Jill你需要保證你的Build Tools version是21.1.1版本或者更高。在Gradle中配置如下:
android { ... buildToolsRevision ‘21.1.2’ defaultConfig { // Enable the experimental Jack build tools. useJack = true } ... }
總歸一句話,現在還年輕,有何成就還得觀望,看后面的發展趨勢吧,反正目前我是沒咋使用他,只是試驗性的嘗鮮了一把而已。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
3 Android應用IDE及編譯相關特性
下面介紹一些使用IDE開發過程中高效的代碼編寫特性,同時包含一些編譯相關的注意特性,具體如下會細說。
3-1 Android應用jar包與aar包
我們在Android Studio中對一個自己的庫進行編譯時會同時生成jar與aar兩個包。他們的具體路徑如下:
包類型 | 在AS中的輸出路徑 |
---|---|
jar | /build/intermediates/bundles/debug[release]/classes.jar |
aar | /build/outputs/aar/library.aar |
他們兩者的區別如下(實質都是壓縮包):
包類型 | 描述 |
---|---|
jar | 只包含class文件,不包含資源文件,用于純Java編寫的庫。 |
aar | 包含所有class及res等全部資源,類似UI庫。 |
其實關于他們二者的區別我們通過解壓縮即可直觀的看出來,這里不再敘述。
3-2 Android Tools Attributes編譯說明
關于Android應用編譯框架中還需要知道一個有用的工具屬性,那就是tools命名空間屬性,他的命名空間URI是http://schemas.android.com/tools,可以說這個命名空間是專門為開發者設計的,只在設計階段有效,運行階段無效。
3-2-1 綁定tools命名空間的方法
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" //綁定命名空間 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > ....
tools屬性可以替換所有Android的屬性,只在設計階段有效,不會被帶入最終的apk中,所以運行時無效。整個tools命名空間的屬性分兩大類,一類是Lint相關的、一類是設計相關的。下面我們列舉一下tools相關的一些實用屬性。
3-2-2 tools:ignore
告訴Lint忽略xml中某些警告。如下用法:
//忽略Lint對于多語言檢測的警告,多個可以用逗號分開 <string name="show_all_apps" tools:ignore="MissingTranslation">All</string>
3-2-3 tools:targetApi
用來指定API等級,功能和Java文件中的@TargetApi注釋類似,值為整數或者含義字符串。如下用法:
<GridLayout tools:targetApi="ICE_CREAM_SANDWICH" >
3-2-4 tools:locale
默認情況下res/values/strings.xml文件中的字符串會進行拼寫檢查,如果本地不是英語則會給出警告,我們可以通過這樣來指定本地語言然后忽略警告。如下:
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="es">
3-2-5 tools:context
這個屬性其實就是關聯Activity屬性,在xml中添加該屬性后預覽該xml文件就能知道采用啥主題來預覽,同時關聯了Activity文件與xml文件,可以從java文件直接跳轉索引。如下:
<android.support.v7.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" ... >
3-2-6 tools:layout
該屬性用在xml的fragment節點中,在開發時告訴IDE該fragment在預覽模式下顯示的樣子。如下:
<fragment android:name="com.example.master.ItemListFragment" tools:layout="@android:layout/list_content" />
3-2-7 tools:listitem / listheader / listfooter
很明顯可以猜到用來給ListView、AdapterView、GridView、ExpandableListView指定預覽時的item和頭底。如下:
<ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@android:layout/simple_list_item_2" />
3-2-8 tools:showIn
該屬性被設置到一給被include的布局的根節點上,預覽時可用。如下:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:showIn="@layout/activity_main" />
3-2-9 tools:menu
用來告訴IDE在預覽時當前xml布局顯示指定的menu樣式。其實如果我們指定了tools:context屬性,IDE會很智能的在我們指定Activity文件中的onCreateOptionsMenu方法中尋找menu樣式預覽。當然了,我們可以通過該屬性覆蓋Activity中的menu預覽。如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:menu="menu1,menu2" />
3-2-10 tools:actionBarNavMode
指定actionbar的預覽模式。可選值為”standard”、”list”、”tabs”。如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:actionBarNavMode="tabs" />
3-2-11 Designtime設計時屬性
tools屬性可以替換所有Android的屬性,只在設計階段有效,不會被帶入最終的apk中,所以運行時無效。譬如我們最常見的在寫一個TextView時不想在xml中給他初始文本內容,又想預覽,這時候就可以用tools屬性替代。如下:
<TextView tools:text="Name:" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:visibility="invisible" />
可以看見,tools命名空間屬性可以加速我們開發的靈活度和可預測度,同時在編譯框架中又會忽略這些屬性,不把他們帶入最終產物apk中,我們可以在開發中酌情選擇是否使用這些屬性。
3-3 Android Annotations注解說明
在Android Support Library的19.1版本中加入了一個新的注解包,這個包用來注解代碼,方便捕獲程序中存在的問題和bug,這個包內部的代碼就已經使用了該注解,在22.2版本中又增加了13個新的注解,這些注解可以方便我們代碼開發與調運規范和bug檢查,具體用法下面會一一講解。
3-3-1 注解Library依賴引用
annotations注解包默認不會自動被包含,不過如果使用appcompat包則會自動包含,因為appcompat包里使用了注解。
在Android工程的Gradle文件中引入注解包如下:
dependencies { compile 'com.android.support:support-annotations:22.2.0' }
如果不是Android工程中想使用該注解包則可以如下寫法(url為本地android sdk的注解包路徑):
repositories { jcenter() maven { url '<your-SDK-path>/extras/android/m2repository' } }
3-3-2 執行注解
當我們在用Android Studio和IntelliJ時如果給使用了注解的方法傳遞錯誤類型參數,則IDE會實時標記提醒錯誤。如果使用的是Gradle 1.3.0版本以上且安裝了Android M Preview Tools以上工具則可以通過命令行調用gradle的lint任務進行檢查(nullness注解會被忽略檢查)。
3-3-3 Null空類型判斷注解
@Nullable 注解用來標注給定的參數或返回值可以為null。
@NonNull 注解用來標注給定的參數或返回值不能為null。
假設一個本地變量值為null,且把它作為參數傳遞給一個方法,且該方法的參數被@NonNull標注,則AS會提醒存在一個潛在的崩潰。如下:
import android.support.annotation.NonNull; import android.support.annotation.Nullable; ... /** * Add support for inflating the <fragment> tag. */ @Nullable @Override public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) { ...
3-3-4 資源類型注解
Android的資源值通常都是通過R文件映射的整型id來關聯的,也就是說獲取一個layout類型的資源參數很容易被誤傳遞一個其他類型的資源參數,因為他們都是整型的資源id,編譯器很難區分。為了解決這種問題可以使用資源類型注解,因為注解提供類型檢查。譬如下面是一個被@LayoutRes注解的整型參數卻傳遞了一個string類型的資源參數,此時IDE會給出錯誤提示:
調運setContentView方法時傳遞錯誤參數:
setContentView的資源注解實現方法:
實際上有很多不同的資源類型注解,譬如@AnimatorRes、@AnimRes、@AnyRes、@ArrayRes、@AttrRes、@BoolRes、@ColorRes、@DimenRes、@DrawableRes、@FractionRes、@IdRes、@IntegerRes、@InterpolatorRes、@LayoutRes、@MenuRes、@PluralsRes、@RawRes、@StringRes、@StyleableRes、@StyleRes、@XmlRes等,一般一個foo類型資源的相應資源類型注解就是@FooRes。除此之外,還有一個名為@AnyRes的特殊資源類型注解,它被用來標注一個未知特殊類型的資源,且必須是一個資源類型。譬如在Resources.getResourceName(@AnyRes int resId)上使用的時候,我們可以通過getResources().getResourceName(R.drawable.icon)和getResources().getResourceName(R.string.app_name)等方式來使用,但卻不能通過getResources().getResourceName(42)來使用。
3-3-5 IntDef/StringDef類型注解
這種類型的注解是基于Intellij的魔數檢查機制的,因為Android開發中很多時候出于性能考慮,我們會使用整型常量代替枚舉類型。譬如AppCompat庫里的一個例子:
import android.support.annotation.IntDef; ... public abstract class ActionBar { ... @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) @Retention(RetentionPolicy.SOURCE) public @interface NavigationMode {} public static final int NAVIGATION_MODE_STANDARD = 0; public static final int NAVIGATION_MODE_LIST = 1; public static final int NAVIGATION_MODE_TABS = 2; @NavigationMode public abstract int getNavigationMode(); public abstract void setNavigationMode(@NavigationMode int mode);
上面非注解的部分是原本的代碼和API,我們通過@interface創建了一個新注解NavigationMode,并且用@IntDef標注它可能的取值,還添加了@Retention(RetentionPolicy.SOURCE)告訴編譯器這個新定義的注解不需要被記錄在生成的.class文件中;使用這個注解后,如果我們返回或傳遞的參數不在指定的常量值中則IDE會給出明顯的錯誤提示。
我們也可以指定整型常量為一個標記性質的類型,因為這樣就可以通過|、&等操作符同時傳遞多個整型常量。如下例子:
@IntDef(flag=true, value={ DISPLAY_USE_LOGO, DISPLAY_SHOW_HOME, DISPLAY_HOME_AS_UP, DISPLAY_SHOW_TITLE, DISPLAY_SHOW_CUSTOM }) @Retention(RetentionPolicy.SOURCE) public @interface DisplayOptions {}
@StringDef和@IntDef的作用基本上一樣,不同的是它針對的是字符串。關于類型注解更多信息點我獲取。
3-3-6 線程注解@UiThread、@WorkerThread等
線程注解是在Support Library 22.2及更高版本中才被支持的,如果我們的方法只能在指定的線程類型中被調用,則就可以使用以下4個注解來標注它們:
-
@UiThread
-
@MainThread
-
@WorkerThread
-
@BinderThread
如果一個類中的所有方法都有相同的線程需求則可以注解類本身。
關于線程注解使用的一個例子是AsyncTask,如下:
@WorkerThread protected abstract Result doInBackground(Params... params); @MainThread protected void onProgressUpdate(Progress... values) { }
如果在使用該方法時沒有按照注解線程執行則會報錯,如下:
3-3-7 約束值注解@Size、@IntRange、@FloatRange
如果我們的參數是float或double類型且限制在一個范圍內,則可以使用@FloatRange注解,如下:
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
如果我們的參數是int或long類型則可以使用@IntRange注解來約束值的范圍,如下:
public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }
對于數據集合或字符串,我們可以用@Size注解來限定集合大小或字符串長度。譬如:
//集合不能為空 @Size(min=1) //字符串最多只能有23個字符 @Size(max=23) //數組只能有2個元素 @Size(2) //數組大小必須是2的倍數 @Size(multiple=2)
3-3-8 權限注解@RequiresPermission
如果我們的方法調用時需要特定權限,則可以用@RequiresPermission進行注解。如下:
@RequiresPermission(Manifest.permission.SET_WALLPAPER) public abstract void setWallpaper(Bitmap bitmap) throws IOException;
如果方法至少需要權限集合中的一個則可以使用anyOf屬性。如下:
@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public abstract Location getLastKnownLocation(String provider);
如果方法同時需要多個權限則可以使用allOf屬性。如下:
@RequiresPermission(allOf = { Manifest.permission.READ_HISTORY_BOOKMARKS, Manifest.permission.WRITE_HISTORY_BOOKMARKS}) public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) {
對于Intent的權限可以直接在定義的Intent常量字符串字段上標注權限需求(通常都已經被@SdkConstant注解標注過了)。如下:
@RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
有時對于ContentProvider的權限可能需要單獨的標注讀和寫權限,所以可以用@Read或者@Write注解。如下:
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
3-3-9 重寫方法注解@CallSuper
如果想在重寫方法中要求必須調運父類的super方法則可以用@CallSuper標注。如下:
@CallSuper protected void onCreate(@Nullable Bundle savedInstanceState) {
3-3-10 @CheckResult、@VisibleForTesting、@Keep
如果我們的方法返回一個值且期望調用者用這個值做些事情則可以使用@CheckResult注解標注這個方法。
如果我們想在測試時使用一個類、方法或者字段則可以用@VisibleForTesting注解進行標明。
特別要說的就是我們還在注解庫里添加了@Keep注解,但是Gradle插件目前版本支持的還不是很好,這個注解標注的東西不會被混淆。
3-3-11 其他注解相關
如果我們在自己的lib庫中使用了上面這些注解,且通過Gradle構建生成了aar包,則在構建時Android Gradle插件會提取注解信息放在aar文件中供使用庫的地方使用。在aar文件中你可以看到一個名為annotations.zip的文件,這個文件記錄的就是使用IntelliJ擴展注解XML格式的注解信息。
PS一句,IntelliJ其實還有他自己的注解,這里不再詳細說明。
可以看見,有了這種注解我們的IDE看起來就更加的智能便捷強大了,同時也幫助我們的編譯框架及早的發現問題,給出警告或者錯誤提示。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
4 Gradle Android構建
其實Android的Gradle構建沒啥太多說的,掌握Groovy語法和Gradle框架后看看Gradle Android Plugs相關DSL特性就行了,用的時候去查API和Android DSL規則即可,重點明白結構和常用的DSL規則即可,這里給出一個常用模板,補充常用實戰基礎示例參見《Android Studio入門到精通》一文的第7小節。
4-1 Gradle Android編譯構建配置基礎
Android Studio的project在project樹根和每個module根都包含一個叫build.gradle的文件,我們所謂的編譯構建配置其實就是通過Gradle的Android插件進行編寫腳本,大多數情況下我們一般只用修改相關module下的build.gradle文件就能滿足需求。如下一個例子:
//添加Gradle的Android構建插件,默認包含一些task和android {...}等特定元素 apply plugin: 'com.android.application' //android元素用來配置Android編譯特有選項 android { //!!!特別注意:我們一定要保證buildToolsVersion版本號總是高于等于compileSdkVersion版本號 //指定編譯目標 compileSdkVersion 19 //指定編譯工具版本,可以通過SDK Manager查看 buildToolsVersion "19.0.0" //defaultConfig元素用來配置核心設置和AndroidManifest.xml文件的動態配置,這里的值會覆蓋AndroidManifest.xml文件中相對應的值。 //!!!特別注意:defaultConfig元素值默認應用于所有build variant,除非我們在build variant中重寫這些值 defaultConfig { //!!!App的唯一id只能在這里聲明,不能在AndroidManifest.xml中 applicationId "com.example.my.app" minSdkVersion 8 targetSdkVersion 19 versionCode 1 versionName "1.0" } //buildTypes元素用來控制如何打包編譯app //默認情況下編譯系統定義了兩中編譯類型:debug和release //debug類型默認會包含調試符和debug key,但是release默認不包含簽名 buildTypes { release { //通過Run ProGuard進行資源混淆!!! minifyEnabled true //getDefaultProguardFile默認獲取SDK路徑下的文件,我們還可以指定一個混淆規則文件,譬如proguard-rules.pro proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } //dependencies在android元素之外且之下,該元素用來指明當前module的依賴關系,構建系統會把這些依賴加入編譯classpath中 dependencies { //Module依賴,編譯時會被包含進去 compile project(":lib") //遠程二進制依賴 compile 'com.android.support:appcompat-v7:19.0.1' //本地二進制依賴 compile fileTree(dir: 'libs', include: ['*.jar']) }
有時候你會在gradle腳本中發現repositories被聲明兩次,這時候你一定有過疑惑的,下面給出這個疑惑的答案:
buildscript塊中的repositories聲明:該聲明的作用是聲明gradle腳本自身要用的資源(譬如依賴項、第三方插件、maven地址等)。
build.gradle文件中的repositories聲明:該聲明作用是項目自身需要的資源聲明。
如果想在Gradle腳本中使用第三方插件,且這些插件、類庫又不用于項目,而是支持其它Gradle腳本運行的,此時我們就該把這種依賴聲明放在buildscript代碼塊中,Gradle在執行腳本時會優先執行buildscript代碼塊中的內容,然后執行剩余的腳本。相反,如果我們在項目中需要使用一些插件、類庫的話,就需要定義在buildscript塊之外的dependencies中。
4-2 Gradle Android構建配置build variants
接下來我們來看看如何通過Gradle構建系統對一套項目工程編譯多個版本(譬如適配多設備類型、不同應用Market發布等),這個構建功能非常簡單、強大和實用。
使用Product flavors進行多版本apk生成的步驟如下:
- 在build文件中定義product flavors;
- 為每一個flavor創建額外的資源目錄;
- 把這些資源加入工程進行指定類型編譯;
先看第一步,如下定義兩中類型的編譯配置:
... android { ... defaultConfig { ... } signingConfigs { ... } buildTypes { ... } productFlavors { demo { applicationId "com.buildsystemexample.app.demo" versionName "1.0-demo" } full { applicationId "com.buildsystemexample.app.full" versionName "1.0-full" } } } ...
上面例子創建了id與version不同的兩個apk;特別注意,我們一般把不同product flavors相同的屬性元素都統一定義在defaultConfig元素中,因為defaultConfig元素的屬性值默認應用于所有build variant,除非我們在build variant中重寫這些值才可以。
接著我們看下第二步和第三步,為每個flavor添加不同的資源。我們可以看見上一步中定義了不同的applicationId,主要區分是demo和full,也就是說我們可以在src目錄下為這兩種情況分別創建相關的資源目錄(譬如這里創建demo和full目錄,我們放置一些特定屬于demo和full的區分資源),完事以后在AS的面板中選擇Build Variants的不同類型編譯即可。
4-3 Gradle Android Plugin DSL
哎呀,這個不說了,自己去看官方文檔吧,Google官方和Gradle官方都有關于Android DSL的詳細說明,心好累,請允許我矯情一回。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
5 Android應用編譯相關總結
這篇博客純屬難產,草稿箱睡了一兩個月了,今天終于生下來了。一直斷斷續續沒有激情,郁悶哇。
上面我們介紹了Android應用源碼生成Apk的過程及其中涉及的相關工具,同時也介紹了應用新的編譯工具Jack和Jill,接著簡單說明了一下當下非常流行熱火的Android Tools Attributes和Android Annotations注解,最后給出了一個簡單的Gradle Android Plugin DSL規范說明。了解了這些流程和簡單原理之后相信對于應用開發和構建會有本質的幫助。
總體來說本文難產,質量不高,湊合著看吧!
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】