編輯:關於Android編程
本文是Futurice公司的Android開發人員總結的最佳實踐,遵循這些准則可以避免重復制造輪子。如果你對iOS或者WindowsPhone開發感興趣,那麼也請看看iOS最佳實踐(https://github.com/futurice/ios-good-practices)和Windows客戶端開發最佳實踐(https://github.com/futurice/win-client-dev-good-practices)。
概要
使用Gradle和推薦的工程結構
把密碼和敏感數據存放在gradle.properties文件中
不要自己實現HTTP客戶端,要使用Volley或者OkHttp庫
使用Jackson庫來解析JSON數據
避免使用Guava,使用少量的函數庫從而避免超出65k方法數限制
使用Fragments來表示UI界面
Activities只用來管理Fragments
布局XML文件是代碼,要組織好它們
使用樣式文件來避免布局XML文件中屬性的重復定義
使用多個樣式文件避免單一大樣式文件的使用
保持colors.xml文件簡短和不重復,只定義顏色值
保持dimens.xml文件不重復,並只定義通用的常量
避免ViewGroups層次結構太深
避免在客戶端側處理WebViews,謹防內存洩漏
使用Robolectric作為單元測試的工具,Robotium作為UI測試的工具
使用Genymotion作為你的模擬器
總是使用ProGuard或者DexGuard
Android SDK
把你的Android SDK目錄放在電腦的主目錄或者其他跟IDE安裝目錄獨立的磁盤位置,某些IDE在安裝時就包含了Android SDK,而且可能把它放在跟IDE相同的目錄下。當你需要升級(或重新安裝)IDE,或者更換IDE時,這種做法是不好的。同樣要避免把AndroidSDK放在另外一個系統層級的目錄中,這樣當你的IDE在user模式下運行而不是root模式時,將需要sudo權限。
構建系統
你的默認選擇應該是Gradle。相比之下,Ant限制更大而且使用起來更繁瑣。使用Gradle可以很簡單的實現:
1)將你的app編譯成不同的版本;
2)實現簡單的類似腳本的任務;
3)管理和下載第三方依賴項;
4)自定義密鑰庫;
5)其他
Google也在積極的開發Android的Gradle插件,以此作為新的標准編譯系統。
工程結構
目前有兩個流行的選擇:以前的Ant和EclipseADT工程結構,以及新的Gradle和Android Studio工程結構。你應該選擇新的工程結構,如果你的工程還在使用舊的結構,那麼應該立即開始將它遷移到新的結構上面來。
舊的工程結構如下所示:
old-structure
├─assets
├─libs
├─res
├─src
│└─com/futurice/project
├─AndroidManifest.xml
├─build.gradle
├─project.properties
└─proguard-rules.pro
old-structure
├─assets
├─libs
├─res
├─src
│└─com/futurice/project
├─AndroidManifest.xml
├─build.gradle
├─project.properties
└─proguard-rules.pro 新的工程結構如下所示:
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
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
主要的區別在於新的結構明確的區分源碼集合(main和androidTest),這是從Gradle引入的概念。例如,你可以在源碼目錄src中添加paid和free兩個子目錄,分別用來存放付費版和免費版app的源碼。
頂層的app目錄有助於把你的app和工程中會引用到的其他庫工程(例如library-foobar)區分開。settings.gradle文件中記錄了這些庫工程的引用,這樣app/build.gradle就能夠引用到了。
Gradle配置
一般結構:參見Google的安卓Gradle指南(http://tools.android.com/tech-docs/new-build-system/user-guide)。
小任務:在Gradle中,我們使用tasks而不是腳本(shell,Python,Perl等使用腳本),詳細的內容可參見Gradle文檔(http://gradle.org/docs/current/userguide/userguide_single.html)。
密碼:發布release版本時,你需要在app目錄下面的build.gradle文件中定義signingConfigs字段,下面這個配置會出現在版本控制系統中,這是你應該避免的:
view plaincopy
signingConfigs{
release{
storeFilefile("myapp.keystore")
storePassword"password123"
keyAlias"thekey"
keyPassword"password789"
}
} view plaincopy
signingConfigs{
release{
storeFilefile("myapp.keystore")
storePassword"password123"
keyAlias"thekey"
keyPassword"password789"
}
} 你應用創建一個gradle.properties文件,該文件不要添加到版本控制系統中,並設置如下:
view plaincopy
KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789 view plaincopy
KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789 gradle會自動導入這個文件,現在你可以在build.gradle中這樣使用:
view plaincopy
signingConfigs{
release{
try{
storeFilefile("myapp.keystore")
storePasswordKEYSTORE_PASSWORD
keyAlias"thekey"
keyPasswordKEY_PASSWORD
}
catch(ex){
thrownewInvalidUserDataException("YoushoulddefineKEYSTORE_PASSWORDandKEY_PASSWORDingradle.properties.")
}
}
} view plaincopy
signingConfigs{
release{
try{
storeFilefile("myapp.keystore")
storePasswordKEYSTORE_PASSWORD
keyAlias"thekey"
keyPasswordKEY_PASSWORD
}
catch(ex){
thrownewInvalidUserDataException("YoushoulddefineKEYSTORE_PASSWORDandKEY_PASSWORDingradle.properties.")
}
}
} 優先選擇Maven依賴而不是導入jar文件。如果你在工程中顯式地包含jar文件,它們會是特定的不可變的版本,例如2.1.1。下載jar包並手動更新是很麻煩的,而這個問題Maven正好幫我們解決了,在Android Gradle構建中也建議這麼做。你可以指定某個版本范圍的jar包,例如2.1.+,這樣Maven會幫我們自動更新和這個版本模式匹配的後續升級。例子如下:
view plaincopy
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.+'
}view plaincopy
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.+'
}
IDE和文本編輯器
使用任何保持良好工程結構的代碼編輯器。代碼編輯器是個人喜好的選擇,你需要做的是保證你所用的編輯器能夠和工程結構以及構建系統良好集成。
當下最受推崇的IDE是Android Studio,因為它是Google開發的,和Gradle耦合最好,默認使用最新的工程結構,已經處於穩定階段,是為Android開發量身定做的IDE。
當然你也可以使用Eclipse ADT,但你需要配置它才能使用Gradle,因為它默認使用的是舊的工程結構和使用Ant進行構建。你甚至可以使用類似Vim,Sublime Text,Emacs等文本編輯器,這種情況下你需要在命令行中使用Gradle和adb。如果你的Eclipse集成Gradle不可用,你的選擇是要麼使用命令行編譯或者把項目遷移到Android Studio中。Android Studio是最好的選擇,因為ADT插件已經被標記為過時了,也就是不會再作後續維護和更新了。
無論你使用哪種方式,需保證的是按照官方的推薦使用新的工程結構和Gradle來構建你的應用,並避免把你特定於編輯器的配置文件加入到版本控制系統中。例如要避免把Ant的build.xml文件添加到版本控制系統中。特別是如果你在Ant中更改了編譯配置,不要忘了同步更新build.gradle文件。最後一點,要對其他開發人員友好,不要迫使他們修改他們所用編輯器的偏好設置。
函數庫
Jackson(http://wiki.fasterxml.com/JacksonHome)是一個把Java對象轉換為JSON字符串或者把JSON字符串轉換成Java對象的Java函數庫。Gson(https://code.google.com/p/google-gson/)也是解決這類問題很流行的選擇之一,但我們發現Jackson更加高性能,因為它支持多種可選的處理JSON的方式:流,內存樹模型和傳統的JSON-POJO數據綁定。盡管如此,Jackson是比Gson更大的函數庫,所以需要根據你項目的具體情況,你可能會選擇GSON來避免65k方法數限制。其他的選擇還有:Json-smart(https://code.google.com/p/json-smart/)和Boon JSON(https://github.com/RichardHightower/boon/wiki/Boon-JSON-in-five-minutes)。
網絡,緩存和圖像。向後端服務器發起網絡請求有很多經過實戰檢驗的解決方案,你應該使用這些解決方案而不是自己實現一個。使用Volley(https://android.googlesource.com/platform/frameworks/volley)或者Retrofit(http://square.github.io/retrofit/)吧!除了網絡請求,Volley還提供了幫助類用於加載和緩存圖像。如果你選擇Retrofit,那麼可以考慮使用Picasso(http://square.github.io/picasso/)作為加載和緩存圖像的函數庫,並結合OkHttp(http://square.github.io/okhttp/)實現高效的HTTP請求。Retrofit,Picasso和OkHttp這三款函數庫都是同一家公司開發的,所以它們能夠很好的互補。Volley也能使用OkHttp來實現網絡連接(http://stackoverflow.com/questions/24375043/how-to-implement-android-volley-with-okhttp-2-0/24951835)。
RxJava是一個響應式編程的函數庫,也就是可以處理異步事件。這是一個強大和有前途的編程范式,但由於它是如此的不同,因此會顯得不好理解。在使用這個函數庫搭建你的應用的框架時,我們建議你要保持謹慎的態度。我們有幾個項目已經使用RxJava來實現,如果你需要幫助可以聯系以下這些人:Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen。我們已經寫了一些博客文章來進行介紹(1)http://blog.futurice.com/tech-pick-of-the-week-rx-for-net-and-rxjava-for-android;2)http://blog.futurice.com/top-7-tips-for-rxjava-on-android;3)https://gist.github.com/staltz/868e7e9bc2a7b8c1f754;4)http://blog.futurice.com/android-development-has-its-own-swift)。
如果你之前沒有使用RxJava的經驗,那麼開始時可以僅在網絡請求API的響應處使用。如果有經驗了,可以將RxJava應用在簡單UI事件的處理,例如點擊事件或者搜索框中的輸入事件。如果你對自己的RxJava技能很自信而且想把RxJava應用到整個項目架構中,那麼在代碼難以理解的部分要編寫Javadocs。要記住對RxJava不熟悉的程序員可能在維護工程的初期會很痛苦。盡你所能幫助他們理解你的代碼和RxJava。
Retrolambda(https://github.com/evant/gradle-retrolambda)是兼容在Android中和JDK8之前的Java版本中使用Lambda表達式語法的一個Java函數庫。它幫助你的代碼保持緊湊和可讀,特別是當你使用函數式編程風格時,例如使用RxJava。要使用這個庫,需要安裝JDK8,在AndroidStudio工程結構對話框中設置SDK的位置,並設置JAVA8_HOME和JAVA7_HOME環境變量,然後在工程的build.gradle中設置如下:
view plaincopy
dependencies{
classpath'me.tatarka:gradle-retrolambda:2.4.+'
}view plaincopy
dependencies{
classpath'me.tatarka:gradle-retrolambda:2.4.+'
} 接著在各個模塊的build.gradle中增加配置如下:
view plaincopy
applyplugin:'retrolambda'
android{
compileOptions{
sourceCompatibilityJavaVersion.VERSION_1_8
targetCompatibilityJavaVersion.VERSION_1_8
}
retrolambda{
jdkSystem.getenv("JAVA8_HOME")
oldJdkSystem.getenv("JAVA7_HOME")
javaVersionJavaVersion.VERSION_1_7
}view plaincopy
applyplugin:'retrolambda'
android{
compileOptions{
sourceCompatibilityJavaVersion.VERSION_1_8
targetCompatibilityJavaVersion.VERSION_1_8
}
retrolambda{
jdkSystem.getenv("JAVA8_HOME")
oldJdkSystem.getenv("JAVA7_HOME")
javaVersionJavaVersion.VERSION_1_7
}
Android Studio提供了支持Java8 lambdas的代碼輔助功能。如果你剛接觸lambdas,可以參見下面的建議來開始:
1)任何只有一個函數的接口(Interface)是“lambda友好”的,可以被折疊為更緊湊的語法格式;
2)如果對參數或諸如此類的用法還存有懷疑的話,可以編寫一個普通的匿名內部類然後讓Android Studio幫你把它折疊成lambda表達式的形式。
要注意dex文件方法數限制的問題,避免使用太多的第三方函數庫。當Android應用打包成一個dex文件時,存在最多65536個引用方法數的限制問題。當超出這個限制時,你將在編譯階段看到fatalerror。因此,應該使用盡可能少的第三方函數庫,並使用dex-method-counts工具(https://github.com/mihaip/dex-method-counts)來決定使用哪些函數庫的組合以避免不超過該限制。特別要避免使用Guava函數庫,因為它包含的方法數超過13k。
Activities and Fragments
在Android中實現UI界面默認應該選擇Fragments。Fragments是可復用用戶界面,能夠在你的應用中很好的組合。我們建議使用Fragments代替Activities來表示用戶界面,下面是幾點原因:
1)多窗口布局的解決方案。最初引入Fragment的原因是為了把手機應用適配到平板電腦屏幕上,這樣你可以在平板電腦屏幕上具有A和B兩個窗口,而到了手機屏幕上A或者B窗口獨占整個手機屏幕。如果你的應用在最開始的時候是基於Fragments實現的,那麼以後要適配到其他不同類型的屏幕上面會容易得多。
2)不同界面之間的通信。Android API沒有提供在Activity之間傳遞復雜數據(例如某些Java對象)的正確方法。對於Fragments而已,你可以使用Activity的實例作為它的子Fragments之間通信的通道。雖然這種方法比Activities之間的通信更好,但你可能願意考慮Event Bus框架,例如使用Otto(https://square.github.io/otto/)或者green robotEventBus(https://github.com/greenrobot/EventBus)作為更簡潔的方法。如果你想避免添加多一個函數庫的話,RxJava也可以用於實現Event Bus。
3)Fragments足夠通用,它不一定需要UI界面。你可以使用沒有UI界面的Fragment(http://developer.android.com/guide/components/fragments.html)來完成Activity的後台工作。你可以進一步拓展這個功能,創建一個專門的後台Fragment來管理Activity的子Fragments之間的切換邏輯,這樣就不用在Activity中實現這些邏輯了(http://stackoverflow.com/questions/12363790/how-many-activities-vs-fragments/12528434)。
4)在Fragments裡面也可以管理ActionBar。你可以選擇創建一個沒有UI界面的Fragment專門來管理ActionBar,或者選擇讓每個Fragments在宿主Activity的ActionBar中添加它們自己的action items。更多請參考http://www.grokkingandroid.com/adding-action-items-from-within-fragments/。
我們建議不要大量的使用嵌套Fragments,這會導致matryoshkabugs(http://delyan.me/android-s-matryoshka-problem/)。只有在必要的時候(例如在Fragment屏幕中內嵌水平滑動的ViewPager)或者確實是明智的決定時才使用嵌套Fragments。
從軟件架構的層面看,你的app應該具有一個包含大部分業務相關fragments的頂級activity。當然你也可以有其他輔助activities,這些activities與主activity的具有簡單的數據通信,例如通過Intent.setData()或者Intent.setAction()等等。
Java包結構
Android應用的Java包結構大致類似MVC模式。在Android中Fragment和Activity實際上是controller類(http://www.informit.com/articles/article.aspx?p=2126865),另一方面,它們顯然也是用戶界面的一部分,因此也是View類。
因此,很難嚴格界定Fragments(或者Activities)是controllers類還是views類。最好把Fragments類單獨放在fragments包裡面。如果你遵循前面段落的建議的話(只有一個主Activity),Activities可以放在頂層的包裡面。如果你計劃會存在多個activities,那麼就將Activity放在單獨的activities包裡面。
另一方面,整個包結構看起來很像經典的MVC框架,models包目錄存放網絡請求API響應經過JSON解析器填充後得到的POJOs對象。views包目錄存放自定義的views,notifications,action bar views,widgets等等。Adapters處於灰色地帶,介於data和views之間,然而,它們一般需要通過getView()函數導出視圖,所以你可以在views包中增加adapters子包。
有的controller類是應用范圍的並和Android系統緊密關聯,它們可以放在managers包裡面。其他的數據處理類,例如“DateUtils”,可以放在utils包裡面。與服務器端響應交互的類放在network包裡面。
總之,整個包結構從服務器端到用戶界面劃分如下所示:
view plaincopy
com.futurice.project
├─network
├─models
├─managers
├─utils
├─fragments
└─views
├─adapters
├─actionbar
├─widgets
└─notificationsview plaincopy
com.futurice.project
├─network
├─models
├─managers
├─utils
├─fragments
└─views
├─adapters
├─actionbar
├─widgets
└─notifications
資源
命名。遵循以類型作為前綴命名的慣例,即type_foo_bar.xml。例子如下:fragment_contact_details.xml,view_primary_button.xml,activity_main.xml。
組織布局XMLs。如果你對如何格式化布局XML文件不大清楚的話,那麼下面的慣例或許可以幫你:
1)每行一個屬性,縮進四個空格
2)android:id始終作為第一個屬性
3)android:layout_****屬性放在頭部
4)style屬性放在尾部
5)結束標簽/>放在單獨一行,便於新增屬性或者重新排列屬性
6)考慮使用Android Studio中的Designtimeattributes(http://tools.android.com/tips/layout-designtime-attributes),而不使用android:text硬編碼。
[html]view plaincopy
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"
android:orientation="vertical"
>
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="@string/name"
style="@style/FancyText"
/>
[html]view plaincopy
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"
android:orientation="vertical"
>
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="@string/name"
style="@style/FancyText"
/>
一般來說android:layout_****屬性應該定義在XML布局文件中,而其他的android:****屬性應該放在樣式xml文件中。這條法則也有例外,但一般情況是這樣的
。這種做法是為了在布局文件中只保留布局屬性(位置,留白,大小)和內容屬性,而其他外觀細節(顏色,填充,字體)放到樣式文件中。
例外的有:
1)android:id必須放在layout文件中
2)在LinearLayout中的android:orientation一般放在layout文件中更有意思
3)android:text必須放在layout文件中,因為它定義了內容
4)有時創建通用的style文件並定義android:layout_width和android:layout_height屬性更有意思,但默認情況下這兩個屬性應該放在layout文件中。
使用styles。幾乎所有工程都需要正確的使用styles,因為它是讓view具有相同外觀的常見的方法。你的應用的文本內容應該至少具有一個公用的樣式,例如:
[html]view plaincopy
@dimen/font_normal
@color/basic_black
[html]view plaincopy
@dimen/font_normal
@color/basic_black
用在TextView上面如下:
[html]view plaincopy
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/price"
style="@style/ContentText"
/>[html]view plaincopy
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/price"
style="@style/ContentText"
/> 你可能需要對buttons做類似的工作,但別就此停住。繼續把相關的和重復的android:****屬性分組到公用的style文件中。
把一個大的style文件細分成多個文件。你沒有必要維護單獨一個styles.xml文件,Android SDK能很好的支持其他文件。styles文件的名字並不一定要是styles.xml,起作用的是xml文件裡面的
幾乎所有的應用開發者都知道“用戶體驗”的重要性,要提升用戶體驗就離不開一個完備的監控和上報系統,這其中日志(包括Crash上報)是最基本的問題跟蹤和解決手段。本文接下來將
1.概述在Android系統中,多媒體文件通常在開機和SD卡掛載的時候進行掃描操作,目的是為了讓多媒體應用便捷地使用和管理多媒體文件。設想一下如果進入多媒體應用才開始掃描
首先,我們看一下什麼是serializer,serializer就是串行化,又名序列化。它可並不只是簡單的把對象保存在存儲器上,它可以使我們在流中傳輸對象,使對象變的可以
前言 這篇文章可以說是java基礎的范疇,為了下一篇Android開發中的常用設計模式做一下鋪墊,也順便反思一下。 正文 設計模式分類 分類方式是多樣的,