Android 開(kāi)發(fā)最佳實(shí)踐
從Futurice公司Android開(kāi)發(fā)者中學(xué)到的經(jīng)驗(yàn)。 遵循以下準(zhǔn)則,避免重復(fù)發(fā)明輪子。若您對(duì)開(kāi)發(fā)iOS或Windows Phone 有興趣, 請(qǐng)看iOS Good Practices 和 Windows client Good Practices 這兩篇文章。
摘要
使用 Gradle 和它推薦的工程結(jié)構(gòu)
把密碼和敏感數(shù)據(jù)放在gradle.properties
不要自己寫(xiě) HTTP 客戶端,使用Volley或OkHttp庫(kù)
使用Jackson庫(kù)解析JSON數(shù)據(jù)
避免使用Guava同時(shí)使用一些類(lèi)庫(kù)來(lái)避免65k method limit(一個(gè)Android程序中最多能執(zhí)行65536個(gè)方法)
使用 Fragments來(lái)呈現(xiàn)UI視圖
使用 Activities 只是為了管理 Fragments
Layout 布局是 XMLs代碼,組織好它們
在layoutout XMLs布局時(shí),使用styles文件來(lái)避免使用重復(fù)的屬性
使用多個(gè)style文件來(lái)避免單一的一個(gè)大style文件
保持你的colors.xml 簡(jiǎn)短DRY(不要重復(fù)自己),只是定義調(diào)色板
總是使用dimens.xml DRY(不要重復(fù)自己),定義通用常數(shù)
不要做一個(gè)深層次的ViewGroup
在使用WebViews時(shí)避免在客戶端做處理,當(dāng)心內(nèi)存泄露
使用Robolectric單元測(cè)試,Robotium 做UI測(cè)試
使用Genymotion 作為你的模擬器
總是使用ProGuard 和 DexGuard混淆來(lái)項(xiàng)目
Android SDK
將你的Android SDK放在你的home目錄或其他應(yīng)用程序無(wú)關(guān)的位置。 當(dāng)安裝有些包含SDK的IDE的時(shí)候,可能會(huì)將SDK放在IDE同一目錄下,當(dāng)你需要升級(jí)(或重新安裝)IDE或更換的IDE時(shí),會(huì)非常麻煩。 此外,若果你的IDE是在普通用戶,不是在root下運(yùn)行,還要避免吧SDK放到一下需要sudo權(quán)限的系統(tǒng)級(jí)別目錄下。
構(gòu)建系統(tǒng)
你的默認(rèn)編譯環(huán)境應(yīng)該是Gradle. Ant 有很多限制,也很冗余。使用Gradle,完成以下工作很方便:
構(gòu)建APP不同版本的變種
制作簡(jiǎn)單類(lèi)似腳本的任務(wù)
管理和下載依賴(lài)
自定義秘鑰
更多
同時(shí),Android Gradle插件作為新標(biāo)準(zhǔn)的構(gòu)建系統(tǒng)正在被Google積極的開(kāi)發(fā)。
工程結(jié)構(gòu)
有兩種流行的結(jié)構(gòu):老的Ant & Eclipse ADT 工程結(jié)構(gòu),和新的Gradle & Android Studio 工程結(jié)構(gòu), 你應(yīng)該選擇新的工程結(jié)構(gòu),如果你的工程還在使用老的結(jié)構(gòu),考慮放棄吧,將工程移植到新的結(jié)構(gòu)。
老的結(jié)構(gòu):
old-structure
├─ assets
├─ libs
├─ res
├─ src
│ └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro
新的結(jié)構(gòu)
new-structure
├─ library-foobar
├─ app
│ ├─ libs
│ ├─ src
│ │ ├─ androidTest
│ │ │ └─ java
│ │ │ └─ com/futurice/project
│ │ └─ main
│ │ ├─ java
│ │ │ └─ com/futurice/project
│ │ ├─ res
│ │ └─ AndroidManifest.xml
│ ├─ build.gradle
│ └─ proguard-rules.pro
├─ build.gradle
└─ settings.gradle
主要的區(qū)別在于,新的結(jié)構(gòu)明確的分開(kāi)了'source sets' (main,androidTest),Gradle的一個(gè)理念。 你可以做到,例如,添加源組‘paid’和‘free’在src中,這將成為您的應(yīng)用程序的付費(fèi)和免費(fèi)的兩種模式的源代碼。
你的項(xiàng)目引用第三方項(xiàng)目庫(kù)時(shí)(例如,library-foobar),擁有一個(gè)頂級(jí)包名app從第三方庫(kù)項(xiàng)目區(qū)分你的應(yīng)用程序是非常有用的。 然后settings.gradle不斷引用這些庫(kù)項(xiàng)目,其中app/build.gradle可以引用。
Gradle 配置
常用結(jié)構(gòu) 參考Google's guide on Gradle for Android
小任務(wù) 除了(shell, Python, Perl, etc)這些腳本語(yǔ)言,你也可以使用Gradle 制作任務(wù)。 更多信息請(qǐng)參考Gradle's documentation。
密碼 在做版本release時(shí)你app的 build.gradle你需要定義 signingConfigs.此時(shí)你應(yīng)該避免以下內(nèi)容:
不要做這個(gè) . 這會(huì)出現(xiàn)在版本控制中。
signingConfigs {
release {
storeFile file("myapp.keystore")
storePassword "password123"
keyAlias "thekey"
keyPassword "password789"
}
}
而是,建立一個(gè)不加入版本控制系統(tǒng)的gradle.properties文件。
KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789
那個(gè)文件是gradle自動(dòng)引入的,你可以在buld.gradle文件中使用,例如:
signingConfigs {
release {
try {
storeFile file("myapp.keystore")
storePassword KEYSTORE_PASSWORD
keyAlias "thekey"
keyPassword KEY_PASSWORD
}
catch (ex) {
throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
}
}
}
使用 Maven 依賴(lài)方案代替使用導(dǎo)入jar包方案 如果在你的項(xiàng)目中你明確使用率 jar文件,那么它們可能成為永久的版本,如2.1.1.下載jar包更新他們是很繁瑣的, 這個(gè)問(wèn)題Maven很好的解決了,這在Android Gradle構(gòu)建中也是推薦的方法。你可 以指定版本的一個(gè)范圍,如2.1.+,然后Maven會(huì)自動(dòng)升級(jí)到制定的最新版本,例如:
dependencies {
compile 'com.netflix.rxjava:rxjava-core:0.19.+'
compile 'com.netflix.rxjava:rxjava-android:0.19.+'
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.+'
compile 'com.fasterxml.jackson.core:jackson-core:2.4.+'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.4.+'
compile 'com.squareup.okhttp:okhttp:2.0.+'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.+'
}
IDEs and text editors
IDE集成開(kāi)發(fā)環(huán)境和文本編輯器
無(wú)論使用什么編輯器,一定要構(gòu)建一個(gè)良好的工程結(jié)構(gòu) 編輯器每個(gè)人都有自己的 選擇,讓你的編輯器根據(jù)工程結(jié)構(gòu)和構(gòu)建系統(tǒng)運(yùn)作,那是你自己的責(zé)任。
當(dāng)下首推Android Studio,因?yàn)樗怯晒雀栝_(kāi)發(fā),最接近Gradle,默認(rèn)使用最新的工程結(jié)構(gòu),已經(jīng)到beta階段 (目前已經(jīng)有release 1.0了),它就是為Android開(kāi)發(fā)定制的。
你也可以使用Eclipse ADT ,但是你需要對(duì)它進(jìn)行配置,因?yàn)樗褂昧伺f的工程結(jié)構(gòu) 和Ant作為構(gòu)建系統(tǒng)。你甚至可以使用純文版編輯器如Vim,Sublime Text,或者Emacs。如果那樣的話,你需要使用Gardle和adb命令行。如果使用Eclipse集成Gradle 不適合你,你只是使用命令行構(gòu)建工程,或遷移到Android Studio中來(lái)吧。
無(wú)論你使用何種開(kāi)發(fā)工具,只要確保Gradle和新的項(xiàng)目結(jié)構(gòu)保持官方的方式構(gòu)建應(yīng)用程序,避免你的編輯器配置文件加入到版本控制。例如,避免加入Ant build.xml文件。 特別如果你改變Ant的配置,不要忘記保持build.gradle是最新和起作用的。同時(shí),善待其他開(kāi)發(fā)者,不要強(qiáng)制改變他們的開(kāi)發(fā)工具和偏好。
類(lèi)庫(kù)
Jackson 是一個(gè)將java對(duì)象轉(zhuǎn)換成JSON與JSON轉(zhuǎn)化java類(lèi)的類(lèi)庫(kù)。Gson 是解決這個(gè)問(wèn)題的流行方案,然而我們發(fā)現(xiàn)Jackson更高效,因?yàn)樗С痔娲姆椒ㄌ幚鞪SON:流、內(nèi)存樹(shù)模型,和傳統(tǒng)JSON-POJO數(shù)據(jù)綁定。不過(guò),請(qǐng)記住, Jsonkson庫(kù)比起GSON更大,所以根據(jù)你的情況選擇,你可能選擇GSON來(lái)避免APP 65k個(gè)方法限制。其它選擇: Json-smart and Boon JSON
網(wǎng)絡(luò)請(qǐng)求,緩存,圖片 執(zhí)行請(qǐng)求后端服務(wù)器,有幾種交互的解決方案,你應(yīng)該考慮實(shí)現(xiàn)你自己的網(wǎng)絡(luò)客戶端。使用 Volley 或Retrofit。Volley 同時(shí)提供圖片緩存類(lèi)。若果你選擇使用Retrofit,那么考慮使用Picasso 來(lái)加載圖片和緩存,同時(shí)使用OkHttp作為高效的網(wǎng)絡(luò)請(qǐng)求。Retrofit,Picasso和OkHttp都是有同一家公司開(kāi)發(fā)(注: 是由Square 公司開(kāi)發(fā)),所以它們能很好的在一起運(yùn)行。OkHttp 同樣可以和Volley在一起使用 Volley.
RxJava 是函數(shù)式反應(yīng)性的一個(gè)類(lèi)庫(kù),換句話說(shuō),能處理異步的事件。 這是一個(gè)強(qiáng)大的和有前途的模式,同時(shí)也可能會(huì)造成混淆,因?yàn)樗侨绱说牟煌?我們建議在使用這個(gè)庫(kù)架構(gòu)整個(gè)應(yīng)用程序之前要謹(jǐn)慎考慮。 有一些項(xiàng)目是使用RxJava完成的,如果你需要幫助可以跟這些人取得聯(lián)系: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. 我們也寫(xiě)了一些博客: [1], [2], [3], [4].
如若你之前有使用過(guò)Rx的經(jīng)歷,開(kāi)始從API響應(yīng)應(yīng)用它。 另外,從簡(jiǎn)單的UI事件處理開(kāi)始運(yùn)用,如單擊事件或在搜索欄輸入事件。 若對(duì)你的Rx技術(shù)有信心,同時(shí)想要將它應(yīng)用到你的整體架構(gòu)中,那么請(qǐng)?jiān)趶?fù)雜的部分寫(xiě)好Javadocs文檔。 請(qǐng)記住其他不熟悉RxJava的開(kāi)發(fā)人員,可能會(huì)非常難理解整個(gè)項(xiàng)目。 盡你的的全力幫助他們理解你的代碼和Rx。
Retrolambda 是一個(gè)在Android和預(yù)JDK8平臺(tái)上的使用Lambda表達(dá)式語(yǔ)法的Java類(lèi)庫(kù)。 它有助于保持你代碼的緊湊性和可讀性,特別當(dāng)你使用如RxJava函數(shù)風(fēng)格編程時(shí)。 使用它時(shí)先安裝JDK8,在Android Studio工程結(jié)構(gòu)對(duì)話框中把它設(shè)置成為SDK路徑,同時(shí)設(shè)置JAVA8_HOME和JAVA7_HOME環(huán)境變量, 然后在工程根目錄下配置 build.gradle:
dependencies {
classpath 'me.tatarka:gradle-retrolambda:2.4.+'
}
同時(shí)在每個(gè)module 的build.gradle中添加
apply plugin: 'retrolambda'
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
retrolambda {
jdk System.getenv("JAVA8_HOME")
oldJdk System.getenv("JAVA7_HOME")
javaVersion JavaVersion.VERSION_1_7
}
Android Studio 提供Java8 lambdas表帶是代碼提示支持。如果你對(duì)lambdas不熟悉,只需參照以下開(kāi)始學(xué)習(xí)吧:
任何只包含一個(gè)接口的方法都是"lambda friendly"同時(shí)代碼可以被折疊成更緊湊的語(yǔ)法
如果對(duì)參數(shù)或類(lèi)似有疑問(wèn),就寫(xiě)一個(gè)普通的匿名內(nèi)部類(lèi),然后讓Android Status為你生成一個(gè)lambda。
當(dāng)心dex方法數(shù)限制,同時(shí)避免使用過(guò)多的類(lèi)庫(kù) Android apps,當(dāng)打包成一個(gè)dex文件時(shí),有一個(gè)65535個(gè)應(yīng)用方法強(qiáng)硬限制[1] [2] [3]。 當(dāng)你突破65k限制之后你會(huì)看到一個(gè)致命錯(cuò)誤。因此,使用一個(gè)正常范圍的類(lèi)庫(kù)文件,同時(shí)使用dex-method-counts 工具來(lái)決定哪些類(lèi)庫(kù)可以再65k限制之下使用,特別的避免使用Guava類(lèi)庫(kù),因?yàn)樗^(guò)13k個(gè)方法。
Activities and Fragments
Fragments應(yīng)該作為你實(shí)現(xiàn)UI界面默認(rèn)選擇。你可以重復(fù)使用Fragments用戶接口來(lái) 組合成你的應(yīng)用。我們強(qiáng)烈推薦使用Fragments而不是activity來(lái)呈現(xiàn)UI界面,理由如下:
提供多窗格布局解決方案 Fragments 的引入主要將手機(jī)應(yīng)用延伸到平板電腦,所以在平板電腦上你可能有A、B兩個(gè)窗格,但是在手機(jī)應(yīng)用上A、B可能分別充滿 整個(gè)屏幕。如果你的應(yīng)用在最初就使用了fragments,那么以后將你的應(yīng)用適配到其他不同尺寸屏幕就會(huì)非常簡(jiǎn)單。
屏幕間數(shù)據(jù)通信 從一個(gè)Activity發(fā)送復(fù)雜數(shù)據(jù)(例如Java對(duì)象)到另外一個(gè)Activity,Android的API并沒(méi)有提供合適的方法。不過(guò)使用Fragment,你可以使用 一個(gè)activity實(shí)例作為這個(gè)activity子fragments的通信通道。即使這樣比Activity與Activity間的通信好,你也想考慮使用Event Bus架構(gòu),使用如 Otto 或者 greenrobot EventBus作為更簡(jiǎn)潔的實(shí)現(xiàn)。 如果你希望避免添加另外一個(gè)類(lèi)庫(kù),RxJava同樣可以實(shí)現(xiàn)一個(gè)Event Bus。
Fragments 一般通用的不只有UI 你可以有一個(gè)沒(méi)有界面的fragment作為Activity提供后臺(tái)工作。 進(jìn)一步你可以使用這個(gè)特性來(lái)創(chuàng)建一個(gè)fragment 包含改變其它fragment的邏輯 而不是把這個(gè)邏輯放在activity中。
甚至ActionBar 都可以使用內(nèi)部fragment來(lái)管理 你可以選擇使用一個(gè)沒(méi)有UI界面的fragment來(lái)專(zhuān)門(mén)管理ActionBar,或者你可以選擇使用在每個(gè)Fragment中 添加它自己的action 來(lái)作為父Activity的ActionBar.參考.
很不幸,我們不建議廣泛的使用嵌套的fragments,因?yàn)?有時(shí)會(huì)引起matryoshka bugs。我們只有當(dāng)它有意義(例如,在水平滑動(dòng)的ViewPager在 像屏幕一樣fragment中)或者他的確是一個(gè)明智的選擇的時(shí)候才廣泛的使用fragment。
在一個(gè)架構(gòu)級(jí)別,你的APP應(yīng)該有一個(gè)頂級(jí)的activity來(lái)包含絕大部分業(yè)務(wù)相關(guān)的fragment。你也可能還有一些輔助的activity ,這些輔助的activity與主activity 通信很簡(jiǎn)單限制在這兩種方法 Intent.setData() 或 Intent.setAction()或類(lèi)似的方法。
Java 包結(jié)構(gòu)
Android 應(yīng)用程序在架構(gòu)上大致是Java中的Model-View-Controller結(jié)構(gòu)。 在Android 中 Fragment和Activity通常上是控制器類(lèi)(http://www.informit.com/articles/article.aspx?p=2126865). 換句話說(shuō),他們是用戶接口的部分,同樣也是Views視圖的部分。
正是因?yàn)槿绱耍藕茈y嚴(yán)格的將fragments (或者 activities) 嚴(yán)格的劃分成 控制器controlloers還是視圖 views。 最還是將它們放在自己?jiǎn)为?dú)的 fragments 包中。只要你遵循之前提到的建議,Activities 則可以放在頂級(jí)目錄下。 若果你規(guī)劃有2到3個(gè)以上的activity,那么還是同樣新建一個(gè)activities包吧。
然而,這種架構(gòu)可以看做是另一種形式的MVC, 包含要被解析API響應(yīng)的JSON數(shù)據(jù),來(lái)填充的POJO的models包中。 和一個(gè)views包來(lái)包含你的自定義視圖、通知、導(dǎo)航視圖,widgets等等。 適配器Adapter是在數(shù)據(jù)和視圖之間。然而他們通常需要通過(guò)getView()方法來(lái)導(dǎo)出一些視圖, 所以你可以將adapters包放在views包里面。
一些控制器角色的類(lèi)是應(yīng)用程序級(jí)別的,同時(shí)是接近系統(tǒng)的。 這些類(lèi)放在managers包下面。 一些繁雜的數(shù)據(jù)處理類(lèi),比如說(shuō)"DateUtils",放在utils包下面。 與后端交互負(fù)責(zé)網(wǎng)絡(luò)處理類(lèi),放在network包下面。
總而言之,以最接近用戶而不是最接近后端去安排他們。
com.futurice.project
├─ network
├─ models
├─ managers
├─ utils
├─ fragments
└─ views
├─ adapters
├─ actionbar
├─ widgets
└─ notifications
資源文件 Resources
命名 遵循前綴表明類(lèi)型的習(xí)慣,形如type_foo_bar.xml。例如:fragment_contact_details.xml,view_primary_button.xml,activity_main.xml.
組織布局文件 若果你不確定如何排版一個(gè)布局文件,遵循一下規(guī)則可能會(huì)有幫助。
每一個(gè)屬性一行,縮進(jìn)4個(gè)空格
android:id 總是作為第一個(gè)屬性
android:layout_**** 屬性在上邊
style 屬性在底部
關(guān)閉標(biāo)簽/>單獨(dú)起一行,有助于調(diào)整和添加新的屬性
考慮使用Designtime attributes 設(shè)計(jì)時(shí)布局屬性,Android Studio已經(jīng)提供支持,而不是硬編碼android:text (譯者注:墻內(nèi)也可以參考stormzhang的這篇博客鏈接)。
作為一個(gè)經(jīng)驗(yàn)法則,android:layout_****屬性應(yīng)該在 layout XML 中定義,同時(shí)其它屬性android:**** 應(yīng)放在 styler XML中。此規(guī)則也有例外,不過(guò)大體工作 的很好。這個(gè)思想整體是保持layout屬性(positioning, margin, sizing) 和content屬性在布局文件中,同時(shí)將所有的外觀細(xì)節(jié)屬性(colors, padding, font)放 在style文件中。
例外有以下這些:
android:id 明顯應(yīng)該在layout文件中
layout文件中android:orientation對(duì)于一個(gè)LinearLayout布局通常更有意義
android:text 由于是定義內(nèi)容,應(yīng)該放在layout文件中
有時(shí)候?qū)ndroid:layout_width 和 android:layout_height屬性放到一個(gè)style中作為一個(gè)通用的風(fēng)格中更有意義,但是默認(rèn)情況下這些應(yīng)該放到layout文件中。
使用styles 幾乎每個(gè)項(xiàng)目都需要適當(dāng)?shù)氖褂胹tyle文件,因?yàn)閷?duì)于一個(gè)視圖來(lái)說(shuō)有一個(gè)重復(fù)的外觀是很常見(jiàn)的。 在應(yīng)用中對(duì)于大多數(shù)文本內(nèi)容,最起碼你應(yīng)該有一個(gè)通用的style文件,例如: