Android開發最佳實踐



原文鏈接:https://github.com/futurice/android-best-practices

轉載來源:http://blog.csdn.net/asce1885/article/details/43699715

本文是Futurice公司的Android開發人員總結的最佳實踐,遵循這些準則可以避免重複製造輪子。如果你對iOS或者Windows Phone開發感興趣,那麼也請看看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時,這種做法是不好的。同樣要避免把Android SDK放在另外一個系統層級的目錄中,這樣當你的IDE在user模式下運行而不是root模式時,將需要sudo權限。

構建系統

你的默認選擇應該是Gradle。相比之下,Ant限制更大而且使用起來更繁瑣。使用Gradle可以很簡單的實現:
1)將你的app編譯成不同的版本;
2)實現簡單的類似腳本的任務
3)管理和下載第三方依賴項;
4)自定義密鑰庫;
5)其他
Google也在積極的開發Android的Gradle插件,以此作爲新的標準編譯系統。

工程結構

目前有兩個流行的選擇:以前的Ant和Eclipse ADT工程結構,以及新的Gradle和Android Studio工程結構。你應該選擇新的工程結構,如果你的工程還在使用舊的結構,那麼應該立即開始將它遷移到新的結構上面來。
舊的工程結構如下所示:
  1. old-structure  
  2. ├─ assets  
  3. ├─ libs  
  4. ├─ res  
  5. ├─ src  
  6. │  └─ com/futurice/project  
  7. ├─ AndroidManifest.xml  
  8. ├─ build.gradle  
  9. ├─ project.properties  
  10. └─ proguard-rules.pro  
old-structure
├─ assets
├─ libs
├─ res
├─ src
│  └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro
新的工程結構如下所示:
  1. new-structure  
  2. ├─ library-foobar  
  3. ├─ app  
  4. │  ├─ libs  
  5. │  ├─ src  
  6. │  │  ├─ androidTest  
  7. │  │  │  └─ java  
  8. │  │  │     └─ com/futurice/project  
  9. │  │  └─ main  
  10. │  │     ├─ java  
  11. │  │     │  └─ com/futurice/project  
  12. │  │     ├─ res  
  13. │  │     └─ AndroidManifest.xml  
  14. │  ├─ build.gradle  
  15. │  └─ proguard-rules.pro  
  16. ├─ build.gradle  
  17. └─ 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#N10CBF)。
密碼:發佈release版本時,你需要在app目錄下面的build.gradle文件中定義signingConfigs字段,下面這個配置會出現在版本控制系統中,這是你應該避免的:
  1. signingConfigs {  
  2.     release {  
  3.         storeFile file("myapp.keystore")  
  4.         storePassword "password123"  
  5.         keyAlias "thekey"  
  6.         keyPassword "password789"  
  7.     }  
  8. }  
signingConfigs {
    release {
        storeFile file("myapp.keystore")
        storePassword "password123"
        keyAlias "thekey"
        keyPassword "password789"
    }
}
你應用創建一個gradle.properties文件,該文件不要添加到版本控制系統中,並設置如下:
  1. KEYSTORE_PASSWORD=password123  
  2. KEY_PASSWORD=password789  
KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789
gradle會自動導入這個文件,現在你可以在build.gradle中這樣使用:
  1. signingConfigs {  
  2.     release {  
  3.         try {  
  4.             storeFile file("myapp.keystore")  
  5.             storePassword KEYSTORE_PASSWORD  
  6.             keyAlias "thekey"  
  7.             keyPassword KEY_PASSWORD  
  8.         }  
  9.         catch (ex) {  
  10.             throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")  
  11.         }  
  12.     }  
  13. }  
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依賴而不是導入jar文件。如果你在工程中顯式地包含jar文件,它們會是特定的不可變的版本,例如2.1.1。下載jar包並手動更新是很麻煩的,而這個問題Maven正好幫我們解決了,在Android Gradle構建中也建議這麼做。你可以指定某個版本範圍的jar包,例如2.1.+,這樣Maven會幫我們自動更新和這個版本模式匹配的後續升級。例子如下:
  1. dependencies {  
  2.     compile 'com.netflix.rxjava:rxjava-core:0.19.+'  
  3.     compile 'com.netflix.rxjava:rxjava-android:0.19.+'  
  4.     compile 'com.fasterxml.jackson.core:jackson-databind:2.4.+'  
  5.     compile 'com.fasterxml.jackson.core:jackson-core:2.4.+'  
  6.     compile 'com.fasterxml.jackson.core:jackson-annotations:2.4.+'  
  7.     compile 'com.squareup.okhttp:okhttp:2.0.+'  
  8.     compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.+'  
  9. }  
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#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,在Android Studio工程結構對話框中設置SDK的位置,並設置JAVA8_HOME和JAVA7_HOME環境變量,然後在工程的build.gradle中設置如下:

  1. dependencies {  
  2.     classpath 'me.tatarka:gradle-retrolambda:2.4.+'  
  3. }  
dependencies {
    classpath 'me.tatarka:gradle-retrolambda:2.4.+'
}
接着在各個模塊的build.gradle中增加配置如下:

  1. apply plugin: 'retrolambda'  
  2.   
  3. android {  
  4.     compileOptions {  
  5.     sourceCompatibility JavaVersion.VERSION_1_8  
  6.     targetCompatibility JavaVersion.VERSION_1_8  
  7. }  
  8.   
  9. retrolambda {  
  10.     jdk System.getenv("JAVA8_HOME")  
  11.     oldJdk System.getenv("JAVA7_HOME")  
  12.     javaVersion JavaVersion.VERSION_1_7  
  13. }  
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的代碼輔助功能。如果你剛接觸lambdas,可以參見下面的建議來開始:
1)任何只有一個函數的接口(Interface)是“lambda友好”的,可以被摺疊爲更緊湊的語法格式;
2)如果對參數或諸如此類的用法還存有懷疑的話,可以編寫一個普通的匿名內部類然後讓Android Studio幫你把它摺疊成lambda表達式的形式。
要注意dex文件方法數限制的問題,避免使用太多的第三方函數庫。當Android應用打包成一個dex文件時,存在最多65536個引用方法數的限制問題。當超出這個限制時,你將在編譯階段看到fatal error。因此,應該使用儘可能少的第三方函數庫,並使用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 robot EventBus(https://github.com/greenrobot/EventBus)作爲更簡潔的方法。如果你想避免添加多一個函數庫的話,RxJava也可以用於實現Event Bus。
3)Fragments足夠通用,它不一定需要UI界面。你可以使用沒有UI界面的Fragment(http://developer.android.com/guide/components/fragments.html#AddingWithoutUI)來完成Activity的後臺工作。你可以進一步拓展這個功能,創建一個專門的後臺Fragment來管理Activity的子Fragments之間的切換邏輯,這樣就不用在Activity中實現這些邏輯了(http://stackoverflow.com/questions/12363790/how-many-activities-vs-fragments/12528434#12528434)。
4)在Fragments裏面也可以管理ActionBar。你可以選擇創建一個沒有UI界面的Fragment專門來管理ActionBar,或者選擇讓每個Fragments在宿主Activity的ActionBar中添加它們自己的action items。更多請參考http://www.grokkingandroid.com/adding-action-items-from-within-fragments/
我們建議不要大量的使用嵌套Fragments,這會導致matryoshka bugs(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包裏面。
總之,整個包結構從服務器端到用戶界面劃分如下所示:

  1. com.futurice.project  
  2. ├─ network  
  3. ├─ models  
  4. ├─ managers  
  5. ├─ utils  
  6. ├─ fragments  
  7. └─ views  
  8.    ├─ adapters  
  9.    ├─ actionbar  
  10.    ├─ widgets  
  11.    └─ notifications  
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中的Designtime attributes(http://tools.android.com/tips/layout-designtime-attributes),而不使用android:text硬編碼。
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:orientation="vertical"  
  8.     >  
  9.   
  10.     <TextView  
  11.         android:id="@+id/name"  
  12.         android:layout_width="match_parent"  
  13.         android:layout_height="wrap_content"  
  14.         android:layout_alignParentRight="true"  
  15.         android:text="@string/name"  
  16.         style="@style/FancyText"  
  17.         />  
  18.   
  19.     <include layout="@layout/reusable_part" />  
  20.   
  21. </LinearLayout>  
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        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"
        />

    <include layout="@layout/reusable_part" />

</LinearLayout>
一般來說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具有相同外觀的常見的方法。你的應用的文本內容應該至少具有一個公用的樣式,例如:

  1. <style name="ContentText">  
  2.     <item name="android:textSize">@dimen/font_normal</item>  
  3.     <item name="android:textColor">@color/basic_black</item>  
  4. </style>  
<style name="ContentText">
    <item name="android:textSize">@dimen/font_normal</item>
    <item name="android:textColor">@color/basic_black</item>
</style>
用在TextView上面如下:

  1. <TextView  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content"  
  4.     android:text="@string/price"  
  5.     style="@style/ContentText"  
  6.     />  
<TextView
    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文件裏面的<style>標籤。因此,你的樣式文件命名可以是styles.xml,styles_home.xml,styles_item_details.xml,styles_forms.xml等。和資源目錄名不同(編譯系統需要根據資源目錄名找到資源),res/values裏面的文件名可以隨意命名。

colors.xml是調色板。在你的colors.xml文件中除了定義顏色名字到RGBA顏色值的映射外,不應該定義其他的東西。不用使用它來定義不同類型按鈕的RGBA顏色值。
不要這樣做:

  1. <resources>  
  2.     <color name="button_foreground">#FFFFFF</color>  
  3.     <color name="button_background">#2A91BD</color>  
  4.     <color name="comment_background_inactive">#5F5F5F</color>  
  5.     <color name="comment_background_active">#939393</color>  
  6.     <color name="comment_foreground">#FFFFFF</color>  
  7.     <color name="comment_foreground_important">#FF9D2F</color>  
  8.     ...  
  9.     <color name="comment_shadow">#323232</color>  
<resources>
    <color name="button_foreground">#FFFFFF</color>
    <color name="button_background">#2A91BD</color>
    <color name="comment_background_inactive">#5F5F5F</color>
    <color name="comment_background_active">#939393</color>
    <color name="comment_foreground">#FFFFFF</color>
    <color name="comment_foreground_important">#FF9D2F</color>
    ...
    <color name="comment_shadow">#323232</color>

這樣的使用很容易重複定義相同的RGBA值,這導致如果需要更改一個基本色值時會很麻煩。而且上面的這些定義是和上下文相關的,例如“button”,“comment”,這些應該放到button樣式文件中,而不是colors.xml文件中。
應該這樣做:
  1. <resources>  
  2.   
  3.     <!-- grayscale -->  
  4.     <color name="white"     >#FFFFFF</color>  
  5.     <color name="gray_light">#DBDBDB</color>  
  6.     <color name="gray"      >#939393</color>  
  7.     <color name="gray_dark" >#5F5F5F</color>  
  8.     <color name="black"     >#323232</color>  
  9.   
  10.     <!-- basic colors -->  
  11.     <color name="green">#27D34D</color>  
  12.     <color name="blue">#2A91BD</color>  
  13.     <color name="orange">#FF9D2F</color>  
  14.     <color name="red">#FF432F</color>  
  15.   
  16. </resources>  
<resources>

    <!-- grayscale -->
    <color name="white"     >#FFFFFF</color>
    <color name="gray_light">#DBDBDB</color>
    <color name="gray"      >#939393</color>
    <color name="gray_dark" >#5F5F5F</color>
    <color name="black"     >#323232</color>

    <!-- basic colors -->
    <color name="green">#27D34D</color>
    <color name="blue">#2A91BD</color>
    <color name="orange">#FF9D2F</color>
    <color name="red">#FF432F</color>

</resources>
嚮應用的設計師要以上這些色值定義。命名不需要爲顏色名字,如“green”,“blue”等,例如“brand_primary”,“brand_secondary”,“brand_negative”這樣的命名也是完全可以接受的。這樣來格式化顏色值使得以後如果要修改或者重構顏色時很容易,同時應用中使用了多少種顏色也是一目瞭然的。對於一個美觀的UI,減少使用的顏色種類是很重要的。

dimens.xml文件跟colors.xml文件具有相同的用法。你應該定義一個典型的間距和字體大小的模版,目的基本上和colors.xml文件一樣,好的dimens.xml文件例子如下:

  1. <resources>  
  2.   
  3.     <!-- font sizes -->  
  4.     <dimen name="font_larger">22sp</dimen>  
  5.     <dimen name="font_large">18sp</dimen>  
  6.     <dimen name="font_normal">15sp</dimen>  
  7.     <dimen name="font_small">12sp</dimen>  
  8.   
  9.     <!-- typical spacing between two views -->  
  10.     <dimen name="spacing_huge">40dp</dimen>  
  11.     <dimen name="spacing_large">24dp</dimen>  
  12.     <dimen name="spacing_normal">14dp</dimen>  
  13.     <dimen name="spacing_small">10dp</dimen>  
  14.     <dimen name="spacing_tiny">4dp</dimen>  
  15.   
  16.     <!-- typical sizes of views -->  
  17.     <dimen name="button_height_tall">60dp</dimen>  
  18.     <dimen name="button_height_normal">40dp</dimen>  
  19.     <dimen name="button_height_short">32dp</dimen>  
  20.   
  21. </resources>  
<resources>

    <!-- font sizes -->
    <dimen name="font_larger">22sp</dimen>
    <dimen name="font_large">18sp</dimen>
    <dimen name="font_normal">15sp</dimen>
    <dimen name="font_small">12sp</dimen>

    <!-- typical spacing between two views -->
    <dimen name="spacing_huge">40dp</dimen>
    <dimen name="spacing_large">24dp</dimen>
    <dimen name="spacing_normal">14dp</dimen>
    <dimen name="spacing_small">10dp</dimen>
    <dimen name="spacing_tiny">4dp</dimen>

    <!-- typical sizes of views -->
    <dimen name="button_height_tall">60dp</dimen>
    <dimen name="button_height_normal">40dp</dimen>
    <dimen name="button_height_short">32dp</dimen>

</resources>
佈局文件的邊距和填充應該使用spacing_****尺寸定義,而不是使用硬編碼(類似字符串硬編碼)。這樣會帶來統一的外觀,同時使得組織和修改樣式和佈局更簡單。

避免過深的views層級。有時你可能會被誘導在LinearLayout中再增加一層LinearLayout,例如爲了完成一組views的排列。這種情況類似如下:

  1. <LinearLayout  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:orientation="vertical"  
  5.     >  
  6.   
  7.     <RelativeLayout  
  8.         ...  
  9.         >  
  10.   
  11.         <LinearLayout  
  12.             ...  
  13.             >  
  14.   
  15.             <LinearLayout  
  16.                 ...  
  17.                 >  
  18.   
  19.                 <LinearLayout  
  20.                     ...  
  21.                     >  
  22.                 </LinearLayout>  
  23.   
  24.             </LinearLayout>  
  25.   
  26.         </LinearLayout>  
  27.   
  28.     </RelativeLayout>  
  29.   
  30. </LinearLayout>  
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <RelativeLayout
        ...
        >

        <LinearLayout
            ...
            >

            <LinearLayout
                ...
                >

                <LinearLayout
                    ...
                    >
                </LinearLayout>

            </LinearLayout>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>
即使你沒有很明顯的在Layout文件中看到這種情況,但可能最終發生在Java代碼中將某個view inflate到另外的view中。

這可能會引起一些問題,你可能會遇到性能問題,因爲處理器需要處理很複雜的UI樹層級。另一個更嚴重的問題是可能會引起StackOverflowError(http://stackoverflow.com/questions/2762924/java-lang-stackoverflow-error-suspected-too-many-views)。

因此,儘量使你的view具有扁平的層級:學習怎樣使用RelativeLayout(https://developer.android.com/guide/topics/ui/layout/relative.html),怎樣優化佈局(http://developer.android.com/training/improving-layouts/optimizing-layout.html)和使用<merge>標籤(http://stackoverflow.com/questions/8834898/what-is-the-purpose-of-androids-merge-tag-in-xml-layouts)。

小心WebView相關的問題。當你需要展示一個web頁面時,例如新聞文章,要避免在客戶端側對HTML進行簡化處理,相反,應該向服務器端請求經過簡化後的HTML。當WebView持有所在Activity Context引用而不是Application Context引用時,也可能導致內存泄漏。要避免在簡單文本或者按鈕使用WebView,應該使用TextView和Button。

測試框架

Android SDK的測試框架還很簡單,尤其是UI測試相關的。Android Gradle目前實現了一個叫做connectedAndroidTest(http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Testing)的測試任務,它能夠使用JUnit的Android擴展(http://developer.android.com/reference/android/test/package-summary.html)來運行你創建的JUnit測試用例。這意味着你需要連接設備或者模擬器來運行測試用例,遵循官方幫助指南來進行測試(1)http://developer.android.com/tools/testing/testing_android.html;2)http://developer.android.com/tools/testing/activity_test.html)。

使用Robolectric來進行單元測試,而不是UI測試。這是一個爲了提高開發速度,專注於提供“獨立於設備”的測試框架,尤其適用於models和view models的單元測試。但是Robolectric對於UI測試的支持是不準確和不完善的。使用Robolectric進行動畫,對話框等相關的UI元素測試時會遇到問題,你將看不到屏幕相應的UI元素被測試實時操縱,你將類似於在黑暗中行走。

Robotium簡化了UI測試。你不需要Robotium來執行UI測試用例,但它提供了很多幫助工具用來獲取和分析views,控制屏幕等,這一點對你可能很有幫助。測試用例很簡單,如下所示:

  1. solo.sendKey(Solo.MENU);  
  2. solo.clickOnText("More"); // searches for the first occurence of "More" and clicks on it  
  3. solo.clickOnText("Preferences");  
  4. solo.clickOnText("Edit File Extensions");  
  5. Assert.assertTrue(solo.searchText("rtf"));  
solo.sendKey(Solo.MENU);
solo.clickOnText("More"); // searches for the first occurence of "More" and clicks on it
solo.clickOnText("Preferences");
solo.clickOnText("Edit File Extensions");
Assert.assertTrue(solo.searchText("rtf"));
模擬器

如果你的工作是開發android app,那麼買一個Genymotion模擬器(http://www.genymotion.com/)的licence吧。Genymotion模擬器比AVD模擬器具有更快的幀率,而且具有演示app,模擬網絡連接質量,GPS位置等工具。它也是用於連接測試的理想工具。使用Genymotion模擬器,你可以模擬很多不同類型的設備,所以購買一個Genymotion模擬器licence比買多個真實設備更划算。

要注意的是:Genymotion模擬器沒有移植所有的Google服務,例如Google Play Stoe和Google Maps。你可能需要測試三星特有的API,這時需要購買一臺真實的三星設備。

Proguard配置

在Android工程中ProGuard(http://proguard.sourceforge.net/)被用於壓縮和混淆打包後的代碼。ProGuard的使用可以在工程配置文件中設置。一般情況下當構建一個release版本的apk時,你需要配置Gradle使用ProGuard。

  1. buildTypes {  
  2.     debug {  
  3.         minifyEnabled false  
  4.     }  
  5.     release {  
  6.         signingConfig signingConfigs.release  
  7.         minifyEnabled true  
  8.         proguardFiles 'proguard-rules.pro'  
  9.     }  
  10. }  
buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles 'proguard-rules.pro'
    }
}
爲了決定哪些代碼需要保留,哪些代碼需要丟棄或者混淆,你需要在你的代碼中指定一個或者多個入口點。這些入口點典型的就是具有main函數,applets,midlets,activities等的類。Android框架使用的默認配置文件是SDK_HOME/tools/proguard/proguard-android.txt。自定義的工程特有的proguard規則文件定義爲my-project/app/proguard-rules.pro,將會拼接到默認配置中。

Proguard相關的一個常見問題是在應用啓動時出現crash,錯誤類型是ClassNotFoundException或者NoSuchFieldException等,即使編譯是成功的,原因不過如下兩種:
1)ProGuard把需要用到的類,枚舉,方法,變量或者註解等給移除了;
2)ProGuard把相應的類,枚舉或者變量名給混淆(重命名)了,但調用者還是使用它原來的名字,例如Java反射的情況。
檢查app/build/outputs/proguard/release/usage.txt文件看出問題的對象是否被移除了;檢查app/build/outputs/proguard/release/mapping.txt文件看出問題的對象是否被混淆了。爲了防止ProGuard把需要的類或者類成員移除了,需要在ProGuard配置文件中增加keep選項:

  1. -keep class com.futurice.project.MyClass { *; }  
-keep class com.futurice.project.MyClass { *; }
爲了防止ProGuard把需要的類或者類變量混淆了,要增加keepnames選項:

  1. -keepnames class com.futurice.project.MyClass { *; }  
-keepnames class com.futurice.project.MyClass { *; }
一些例子可以從ProGuard配置模版(https://github.com/futurice/android-best-practices/blob/master/templates/rx-architecture/app/proguard-rules.pro)上面找到,更多例子參見ProGuard官方例子(http://proguard.sourceforge.net/#manual/examples.html)。

在你的項目早期,執行一個release構建來檢查ProGuard規則是否正確的保持了不需要移除或者混淆的東西。當你增加新的函數庫,也要執行新的Release構建並在設備上測試生成的apk來確保沒有問題。不要等到你的app要發佈1.0版本了纔想到要執行一個release構建,這時你可能會得到及其不愉快的驚喜,並花一段時間來修復這些問題。

貼士:保存每個你發佈給最終用戶的apk包對應的mapping.txt文件。保存mapping.txt的原因在於當你的用戶上傳混淆過的crash日誌時,你可以很容易的進行調試。

DexGuard:如果你需要能對你發佈的代碼進行優化,尤其是混淆的核心工具的話,可以考慮DexGuard(http://www.saikoa.com/dexguard),這是由ProGuard同一團隊發佈的商業軟件。它還可以很容易的對分割Dex文件以解決65k函數個數限制問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章