Android開發最佳實踐---Futurice之見

原文鏈接:https://github.com/futurice/android-best-practices
本文是Futurice公司的Android開發人員總結的最佳實踐,遵循這些準則可以避免重複製造輪子。如果你對iOS或者WindowsPhone開發感興趣,那麼也請看看iOS最佳實踐Windows客戶端開發最佳實踐
第一版翻譯自:http://blog.csdn.net/asce1885 Android開發技術日新月異, Github上也有較大更新, 故對原文有增刪

CSDN的markdown不支持頁面內跳轉, 概要裏的跳轉都是無效的. 如果您想看概要中每一條對應的詳細指導, 可以到https://github.com/oncealong/android-best-practices/blob/master/translations/Chinese/Readme.cn.new.md.

概要

  1. 使用Gradle和推薦的工程結構
  2. 把密碼和敏感數據存放在gradle.properties文件中
  3. 使用Jackson或者Gson庫來解析JSON數據
  4. 不要自己實現HTTP客戶端,要使用Volley或者OkHttp庫
  5. 避免使用Guava, 使用少量的函數庫從而避免超出65k方法數限制.
  6. 使用Fragments來表示UI界面
  7. Activities只用來管理Fragments
  8. 佈局XML文件是代碼,要組織好它們
  9. 使用樣式文件來避免佈局XML文件中屬性的重複定義
  10. 使用多個樣式文件避免單一大樣式文件的使用
  11. 保持colors.xml文件簡短和不重複,只定義顏色值
  12. 保持dimens.xml文件不重複,並只定義通用的常量
  13. 避免ViewGroups層次結構太深
  14. 避免在客戶端側處理WebViews,謹防內存泄漏
  15. 使用Robolectric作爲單元測試的工具,Robotium作爲UI測試的工具
  16. 使用Genymotion作爲你的模擬器
  17. 總是使用ProGuard或者DexGuard
  18. 使用SharedPreferences處理簡單的數據持久化, 使用ContentProciders處理複雜的數據持久化
  19. 使用Stetho調試你的程序.

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工程結構。你應該選擇新的工程結構,如果你的工程還在使用舊的結構,那麼應該立即開始將它遷移到新的結構上面來。

舊的工程結構如下所示:

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  

主要的區別在於新的結構明確的區分源碼集合(mainandroidTest),這是從Gradle引入的概念。例如,你可以在源碼目錄src中添加paidfree兩個子目錄,分別用來存放付費版和免費版app的源碼。

頂層的app目錄有助於把你的app和工程中會引用到的其他庫工程(例如library-foobar)區分開。settings.gradle文件中記錄了這些庫工程的引用,這樣app/build.gradle就能夠引用到了。

Gradle配置

一般結構參見Google的安卓Gradle指南
小任務:在Gradle中,我們使用tasks而不是腳本(shell,Python,Perl等使用腳本),詳細的內容可參見Gradle文檔
密碼:發佈release版本時,你需要在app目錄下面的build.gradle文件中定義signingConfigs字段,下面這個配置會出現在版本控制系統中,這是你應該避免的:

signingConfigs {  
    release {  
        storeFile file("myapp.keystore")  
        storePassword "password123"  
        keyAlias "thekey"  
        keyPassword "password789"  
    }  
}  

你應該創建一個gradle.properties文件,該文件不要添加到版本控制系統中,並設置如下:

KEYSTORE_PASSWORD=password123  
KEY_PASSWORD=password789  

gradle會自動導入這個文件,現在你可以在build.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依賴而不是導入jar文件。如果你在工程中顯式地包含jar文件,它們會是特定的不可變的版本,例如2.1.1。下載jar包並手動更新是很麻煩的,而這個問題Maven正好幫我們解決了,在Android Gradle構建中也建議這麼做。例子如下:

dependencies {  
    compile 'com.squareup.okhttp:okhttp:2.2.0'
    compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
}  

避免Maven的動態依賴解析 避免使用動態版本號, 如2.1.+, 因爲這可能會導致不同或者不穩定的構建, 也可能會導致不同構建中行爲的微妙且難以追蹤的差異. 使用靜態版本號, 如2.1.1有助於創建一個更穩定, 更可預測, 可重現問題的開發環境.

對不同的發佈版本使用不同的打包名字debug構建類型使用applicationIdSuffix, 從而可以在相同的設備上安裝debugrelease的apk(如果你要自定義構建類型, 也這樣嘗試). 當app在商店發佈後, 這會在app的生命週期中格外有價值.

android {
    buildTypes {
        debug {
            applicationIdSuffix '.debug'
            versionNameSuffix '-DEBUG'
        }

        release {
            // ...
        }
    }
}

使用不同的圖標來區別不同的構建類型—例如使用不同的顏色, 或者用一個debug標籤覆蓋圖標. Gradle使這些輕而易舉: 在默認的project結構上, 只要把debug圖標放在app/src/debug/res, 把release圖標放在app/src/release/res. 你也可以改變app名字來對應不同的構建類型, 也可以使用上文提到的versionName.

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是一個把Java對象轉換爲JSON字符串或者把JSON字符串轉換成Java對象的Java函數庫。Gson也是解決這類問題很流行的選擇之一,但我們發現Jackson更加高性能,因爲它支持多種可選的處理JSON的方式:流,內存樹模型和傳統的JSON-POJO數據綁定。儘管如此,Jackson是比Gson更大的函數庫,所以需要根據你項目的具體情況,你可能會選擇GSON來避免65k方法數限制。其他的選擇還有:Json-smartBoon JSON

網絡,緩存和圖像。向後端服務器發起網絡請求有很多經過實戰檢驗的解決方案,你應該使用這些解決方案而不是自己實現一個。使用Volley或者Retrofit吧!除了網絡請求,Volley還提供了幫助類用於加載和緩存圖像。如果你選擇Retrofit,那麼可以考慮使用Picasso作爲加載和緩存圖像的函數庫,並結合OkHttp實現高效的HTTP請求。Retrofit,Picasso和OkHttp這三款函數庫都是同一家公司開發的,所以它們能夠很好的互補。Volley也能使用OkHttp來實現網絡連接

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是兼容在Android中和JDK8之前的Java版本中使用Lambda表達式語法的一個Java函數庫。它幫助你的代碼保持緊湊和可讀,特別是當你使用函數式編程風格時,例如使用RxJava。要使用這個庫,需要安裝JDK8,在Android Studio工程結構對話框中設置SDK的位置,並設置JAVA8_HOMEJAVA7_HOME環境變量,然後在工程的build.gradle中設置如下:

dependencies {  
    classpath 'me.tatarka:gradle-retrolambda:2.4.+'  
}  

接着在各個模塊的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的代碼輔助功能。如果你剛接觸lambdas,可以參見下面的建議來開始:
1)任何只有一個函數的接口(Interface)是“lambda友好”的,可以被摺疊爲更緊湊的語法格式;
2)如果對參數或諸如此類的用法還存有懷疑的話,可以編寫一個普通的匿名內部類然後讓Android Studio幫你把它摺疊成lambda表達式的形式。

要注意dex文件方法數限制的問題,避免使用太多的第三方函數庫。當Android應用打包成一個dex文件時,存在最多65536個引用方法數的限制問題。當超出這個限制時,你將在編譯階段看到fatal error。因此,應該使用儘可能少的第三方函數庫,並使用dex-method-counts工具來決定使用哪些函數庫的組合以避免不超過該限制。特別要避免使用Guava函數庫,因爲它包含的方法數超過13k。(注:現在已經有了方法來解決這個問題, 比如multidex)

Activities and Fragments

[注:因爲對activity和fragment優劣的比較一直在持續, 這裏和以前的版本改動較大, 但是這裏的結論仍不一定是最優結論. 讀者需要辯證的看待, 所以先給出最新的翻譯, 並把以前的翻譯保留, 以便讀者辯證思考.]

關於如何使用Activity和Fragment來最好的組織Android架構, 開發社區和Futurice公司的開發者都還沒有一致的看法. Squrare公司甚至有一個基於Views架構的庫, 這直接繞過了Fragments, 但是這種做法在開發社區不值得大範圍推廣使用.

由於Android API的歷史, Fragments可以粗略的認爲用來組裝成UI. 或者說, Fragments 一般都是和UI相關的. Activities可以粗略的認爲是控制器, 它們用於管理生命週期和狀態. 然而, 你可能會看到這些角色各有變種, activities可能用來承擔UI角色(提供畫面之間的轉換), fragments可能作爲控制器單獨使用. 我們只能建議小心使用, 採取明智的決策, 因爲fragments-only架構 或者 activities-only架構或者views-only架構都有缺點.下面是一些關於使用時小心什麼的建議, 但是還請你們持懷疑態度看待這些想法.

  • 避免大範圍的使用嵌套fragments, 這會導致matryoshka bugs。只有在必要的時候(例如在Fragment屏幕中內嵌水平滑動的ViewPager)或者確實是明智的決定時才使用嵌套Fragments。
  • 避免在activities中放太多代碼. 只要有可能, 儘量把它們作爲輕量級容器, 主要用來負責生命週期的控制和其他重要的Android接口API. 優先選擇 single-fragment activity, 把UI相關的代碼放在activity的fragment中. 當你需要改變這個fragment讓它駐留在分頁佈局或平板佈局時, 這讓代碼可重用性提高. 避免使用沒有對應fragment的activity, 除非你知道你在做一個英明決策.
  • 不要濫用Android系統級別的API, 如嚴重依賴Intent來進行app內部工作. 這樣可能會影響Android系統或其他的應用, 造成bug或者反應滯後. 例如, 衆所周知, 如果你的app使用Intent在不同包中進行通信, 當系統重啓後, 打開app可能會引發multi-second 滯後.

以前的看法:
在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或者green robot
EventBus作爲更簡潔的方法。如果你想避免多添加一個函數庫的話,RxJava也可以用於實現Event Bus。
3)Fragments足夠通用,它不一定需要UI界面。你可以使用沒有UI界面的Fragment來完成Activity的後臺工作。你可以進一步拓展這個功能,創建一個專門的後臺Fragment來管理Activity的子Fragments之間的切換邏輯,這樣就不用在Activity中實現這些邏輯了。
4)在Fragments裏面也可以管理ActionBar。你可以選擇創建一個沒有UI界面的Fragment專門來管理ActionBar,或者選擇讓每個Fragments在宿主Activity的ActionBar中添加它們自己的action
items。更多請參考http://www.grokkingandroid.com/adding-action-items-from-within-fragments/
我們建議不要大量的使用嵌套Fragments,這會導致matryoshka
bugs。只有在必要的時候(例如在Fragment屏幕中內嵌水平滑動的ViewPager)或者確實是明智的決定時才使用嵌套Fragments。
從軟件架構的層面看,你的app應該具有一個包含大部分業務相關fragments的頂級activity。當然你也可以有其他輔助activities,這些activities與主activity的具有簡單的數據通信,例如通過Intent.setData()或者Intent.setAction()等等。

Java包結構

Android應用的Java包結構大致類似MVC模式。在Android中Fragment和Activity實際上是controller類,另一方面,它們顯然也是用戶界面的一部分,因此也是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包裏面。
總之,整個包結構從服務器端到用戶界面劃分如下所示:

com.futurice.project  
├─ network  
├─ models  
├─ managers  
├─ utils  
├─ fragments  
└─ views  
   ├─ adapters  
   ├─ actionbar  
   ├─ widgets  
   └─ notifications  

資源

命名。遵循以類型作爲前綴命名的慣例,即type_foo_bar.xml。例子如下:fragment_contact_details.xmlview_primary_button.xmlactivity_main.xml
組織布局XMLs。如果你對如何格式化佈局XML文件不大清楚的話,那麼下面的慣例或許可以幫你:
1)每行一個屬性,縮進四個空格
2)android:id始終作爲第一個屬性
3)android:layout_****屬性放在頭部
4)style屬性放在尾部
5)結束標籤/>放在單獨一行,便於新增屬性或者重新排列屬性
6)考慮使用Android Studio中的Designtime attributes,而不使用android:text硬編碼。

<?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文件中。這條法則也有例外,但一般情況是這樣的。這種做法是爲了在佈局文件中只保留佈局屬性(位置,留白,大小)和內容屬性,而其他外觀細節(顏色,填充,字體)放到樣式文件中。
例外的有:
- android:id必須放在layout文件中
- 在LinearLayout中的android:orientation一般放在layout文件中更有意思
- android:text必須放在layout文件中,因爲它定義了內容
- 有時創建通用的style文件並定義android:layout_widthandroid:layout_height屬性更有意義,但默認情況下這兩個屬性應該放在layout文件中。

使用styles。幾乎所有工程都需要正確的使用styles,因爲它是讓view具有相同外觀的常見的方法。你的應用的文本內容應該至少具有一個公用的樣式,例如:

<style name="ContentText">  
    <item name="android:textSize">@dimen/font_normal</item>  
    <item name="android:textColor">@color/basic_black</item>  
</style>  

用在TextView上面如下:

<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.xmlstyles_home.xmlstyles_item_details.xmlstyles_forms.xml等。和資源目錄名不同(編譯系統需要根據資源目錄名找到資源),res/values裏面的文件名可以隨意命名。

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

<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文件中。
應該這樣做:

<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文件例子如下:

<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_****尺寸定義,而不是使用硬編碼(類似字符串硬編碼)。這樣會帶來統一的外觀,同時使得組織和修改樣式和佈局更簡單。

strings.xml
用像命名空間的文字來給你的strings命名, 不要害怕幾個不同的名字有重複的值. 語言如此複雜, 你需要命名空間打破含糊不清的表達方式.
Bad:

<string name="network_error">Network error</string>
<string name="call_failed">Call failed</string>
<string name="map_failed">Map loading failed</string>

Good:

<string name="error.message.network">Network error</string>
<string name="error.message.call">Call failed</string>
<string name="error.message.map">Map loading failed</string>

不要用全大寫的方式. 堅持普通文本約定(比如首字母大寫). 如果你需要用全部大寫的方式顯示文本, 設置TextView的textAllCaps屬性.
Bad:

<string name="error.message.call">CALL FAILED</string>

Good:

<string name="error.message.call">Call failed</string>


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

<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
因此,儘量使你的view具有扁平的層級:學習怎樣使用RelativeLayout,怎樣優化佈局和使用<merge>標籤

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

測試框架

Android SDK的測試框架還很簡單,尤其是UI測試相關的。Android Gradle目前實現了一個叫做connectedAndroidTest的測試任務,它能夠使用JUnit的Android擴展來運行你創建的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,控制屏幕等,這一點對你可能很有幫助。測試用例很簡單,如下所示:

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模擬器的licence吧。Genymotion模擬器比AVD模擬器具有更快的幀率,而且具有演示app,模擬網絡連接質量,GPS位置等工具。它也是用於連接測試的理想工具。使用Genymotion模擬器,你可以模擬很多不同類型的設備,所以購買一個Genymotion模擬器licence比買多個真實設備更划算。
要注意的是:Genymotion模擬器沒有移植所有的Google服務,例如Google Play Stoe和Google Maps。你可能需要測試三星特有的API,這時需要購買一臺真實的三星設備。

Proguard配置

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

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選項:

-keep class com.futurice.project.MyClass { *; }  

爲了防止ProGuard把需要的類或者類變量混淆了,要增加keepnames選項:

-keepnames class com.futurice.project.MyClass { *; }  

一些例子可以從ProGuard配置模版上面找到,更多例子參見ProGuard官方例子

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

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

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

數據存儲

SharedPreferences

如果你需要保存簡單的標誌, 並且你的程序是單進程的, SharedPreferences就足夠使用了. 這是個很好的默認選擇.

當如下情況時, 你可能不想使用SharedPreferences:
- 性能: 你的數據很複雜,或者數據量很大.
- 多個進程可以訪問數據: 你有窗體小部件或者遠程服務, 它們運行在自己的進程中, 並且需要同步數據.

ContentProviders

當SharedPreferences滿足不了你, 你應該考慮使用平臺標準ContentProviders, 它更快並且是進程安全的.

使用ContentProviders唯一的問題是有大量的樣板文件代碼來建立ContentProviders, 但是沒有好的入門指南. 但是通過使用庫,如Schematic, 是可以直接生成ContentProviders的, 這大大降低了工作成本.

你還需要親自寫一些解析代碼來從Sqlite的列中讀取數據, 或存到Sqlite中. 可以使用Gson來序列化數據對象, 並且只保留結果字符串. 通過這種方式, 你損失了一些性能, 但是你不必對實體類的所有域聲明一行以讀取它.

使用ORM框架

我們一般不推薦使用對象關係映射庫, 除非你有非一般複雜的數據, 並且你迫切需求ORM框架的幫助. 使用ORM會讓代碼變得複雜, 並且需要學習如何使用. 如果你確定需要使用ORM, 你應該關注它是否是進程安全的, 令人驚訝的是, 很多已有的ORM解決方案不是進程安全的.

使用Stetho

Stetho是Facebook發佈的Android 應用調試橋, 和Chrome桌面瀏覽器的開發者工具集成使用. 通過Stetho, 你可以輕鬆檢查你的應用, 尤其是網絡方面的問題. Stetho也讓你更方便的檢查,編輯SQLite數據庫和shared preferences. 但是, 你應該確保Stetho只會在debug模式下啓用, 不要存在於release版本中.

致謝

Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton and other Futurice developers for sharing their knowledge on Android development.

License

Futurice Oy Creative Commons Attribution 4.0 International (CC BY 4.0)

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