1. 程式人生 > >Android 開發最佳實踐經驗分享

Android 開發最佳實踐經驗分享

從Futurice公司Android開發者中學到的經驗。 遵循以下準則,避免重複發明輪子。若你對開發iOS或Windows Phone 有興趣, 請看iOS Good Practices 和 Windows client Good Practices 這兩篇文章。

摘要

  • 使用 Gradle 和它推薦的工程結構

  • 把密碼和敏感資料放在gradle.properties

  • 不要自己寫 HTTP 客戶端,使用Volley或OkHttp庫

  • 使用Jackson庫解析JSON資料

  • 避免使用Guava同時使用一些類庫來避免65k method limit(一個Android程式中最多能執行65536個方法)

  • 使用 Fragments來呈現UI檢視

  • 使用 Activities 只是為了管理 Fragments

  • Layout 佈局是 XMLs程式碼,組織好它們

  • 在layoutout XMLs佈局時,使用styles檔案來避免使用重複的屬性

  • 使用多個style檔案來避免單一的一個大style檔案

  • 保持你的colors.xml 簡短DRY(不要重複自己),只是定義調色盤

  • 總是使用dimens.xml DRY(不要重複自己),定義通用常數

  • 不要做一個深層次的ViewGroup

  • 在使用WebViews時避免在客戶端做處理,當心記憶體洩露

  • 使用Robolectric單元測試,Robotium 做UI測試

  • 使用Genymotion 作為你的模擬器

  • 總是使用ProGuard 和 DexGuard混淆來專案

Android SDK

將你的Android SDK放在你的home目錄或其他應用程式無關的位置。 當安裝有些包含SDK的IDE的時候,可能會將SDK放在IDE同一目錄下,當你需要升級(或重新安裝)IDE或更換的IDE時,會非常麻煩。 此外,如果你的IDE是在普通使用者下執行,而不是在root下執行,還要避免把SDK放到一下需要sudo許可權的系統級別目錄下。

構建系統

你的預設編譯環境應該是Gradle. Ant 有很多限制,也很冗餘。使用Gradle,完成以下工作很方便:

  • 構建APP不同版本的變種

  • 製作簡單類似指令碼的任務

  • 管理和下載依賴

  • 自定義祕鑰

  • 更多

同時,Android Gradle外掛作為新標準的構建系統正在被Google積極的開發。

工程結構

有兩種流行的結構:老的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

主要的區別在於,新的結構明確的分開了'source sets' (main,androidTest),這是Gradle的一個理念。 通過這個你可以做到,例如,新增源組‘paid’和‘free’在src中,讓你的應用程式具有付費和免費的兩種模式的原始碼。

你的專案引用第三方專案庫時(例如,library-foobar),擁有一個頂級包名app從第三方庫專案區分你的應用程式是非常有用的。 然後settings.gradle不斷引用這些庫專案,其中app/build.gradle可以引用。

Gradle 配置

常用結構 參考Google's guide on Gradle for Android

小任務 除了(shell, Python, Perl, etc)這些指令碼語言,你也可以使用Gradle 製作任務。 更多資訊請參考Gradle's documentation。

密碼 在做版本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自動引入的,你可以在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 依賴方案代替使用匯入jar包方案 如果在你的專案中你明確使用某些 jar檔案,那麼它們可能成為固定的版本,如2.1.1.下載jar包更新他們是很繁瑣的, 這個問題Maven很好的解決了,這在Android Gradle構建中也是推薦的方法。你可 以指定版本的一個範圍,如2.1.+,然後Maven會自動升級到制定的最新版本,例如:

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整合開發環境和文字編輯器

無論使用什麼編輯器,一定要構建一個良好的工程結構。 編輯器每個人都有自己的 選擇,讓你的編輯器根據工程結構和構建系統運作,那是你自己的責任。

當下首推Android Studio,因為他是由谷歌開發,很好地支援Gradle,包含很多有用的檢測和分析工具,預設使用最新的工程結構,它就是為Android開發定製的。

你也可以使用純文版編輯器如Vim,Sublime Text,或者Emacs。如果那樣的話,你需要使用Gradle和adb命令列。

不再推薦使用Eclipse和ADT開發,因為谷歌在2015年年末結束了對ADT的支援,並呼籲開發者儘快遷移到Android Studio。

無論你使用何種開發工具,避免將你的編輯器配置檔案(比如Android Studio的iml檔案)加入到版本控制,因為這些檔案通常包含與本地機器有關的配置,可能會影響你的同事。

最後,善待其他開發者,不要強制改變他們的開發工具和偏好。

類庫

Jackson 是一個將java物件轉換成JSON與JSON轉化java類的類庫。Gson 是解決這個問題的流行方案,然而我們發現Jackson更高效,因為它支援替代的方法處理JSON:流、記憶體樹模型,和傳統JSON-POJO資料繫結。不過,請記住, Jsonkson庫比起GSON更大,所以根據你的情況選擇,你可能選擇GSON來避免APP 65k個方法的限制。其它選擇: Json-smart and Boon JSON

網路請求,快取,圖片 執行請求後端伺服器,有幾種互動的解決方案,你應該考慮實現你自己的網路客戶端。使用 Volley 或Retrofit。Volley 同時提供圖片快取類。如果你選擇使用Retrofit,那麼考慮使用Picasso 來載入圖片和快取,同時使用OkHttp作為高效的網路請求。Retrofit,Picasso和OkHttp都是同一家公司開發(注: 是由Square 公司開發),所以它們能很好的在一起執行。OkHttp 同樣可以和Volley在一起使用 Volley.

RxJava 是函式式反應性的一個類庫,換句話說,能處理非同步的事件。 這是一個強大的和有前途的模式,同時也可能會造成混淆,因為它是如此的不同。 我們建議在使用這個庫架構整個應用程式之前要謹慎考慮。 有一些專案是使用RxJava完成的,如果你需要幫助可以跟這些人取得聯絡: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. 我們也寫了一些部落格: [1],[2],[3],[4].

如若你之前有使用過Rx的經歷,開始從API響應應用它。 另外,從簡單的UI事件處理開始運用,如單擊事件或在搜尋欄輸入事件。 若對你的Rx技術有信心,同時想要將它應用到你的整體架構中,那麼請在複雜的部分寫好Javadocs文件。 請記住其他不熟悉RxJava的開發人員,可能會非常難理解整個專案。 盡你的的全力幫助他們理解你的程式碼和Rx。

Retrolambda 是一個在Android和預JDK8平臺上的使用Lambda表示式語法的Java類庫。 它有助於保持你程式碼的緊湊性和可讀性,特別當你使用如RxJava函式風格程式設計時。 使用它時先安裝JDK8,在Android Studio工程結構對話方塊中把它設定成為SDK路徑,同時設定JAVA8_HOMEJAVA7_HOME環境變數, 然後在工程根目錄下配置 build.gradle:

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

同時在每個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錶帶是程式碼提示支援。如果你對lambdas不熟悉,只需參照以下開始學習吧:

  • 任何只包含一個介面的方法都是"lambda friendly"同時代碼可以被摺疊成更緊湊的語法

  • 如果對引數或類似有疑問,就寫一個普通的匿名內部類,然後讓Android Studio 為你生成一個lambda。

當心dex方法數限制,同時避免使用過多的類庫 Android apps,當打包成一個dex檔案時,有一個65535個應用方法強硬限制[1] [2] [3]。 當你突破65k限制之後你會看到一個致命錯誤。因此,使用一個正常範圍的類庫檔案,同時使用dex-method-counts 工具來決定哪些類庫可以再65k限制之下使用,特別的避免使用Guava類庫,因為它包含超過13k個方法。

Activities and Fragments

Fragments應該作為你實現UI介面預設選擇。你可以重複使用Fragments使用者介面來 組合成你的應用。我們強烈推薦使用Fragments而不是activity來呈現UI介面,理由如下:

  • 提供多窗格佈局解決方案 Fragments 的引入主要將手機應用延伸到平板電腦,所以在平板電腦上你可能有A、B兩個窗格,但是在手機應用上A、B可能分別充滿 整個螢幕。如果你的應用在最初就使用了fragments,那麼以後將你的應用適配到其他不同尺寸螢幕就會非常簡單。

  • 螢幕間資料通訊 從一個Activity傳送複雜資料(例如Java物件)到另外一個Activity,Android的API並沒有提供合適的方法。不過使用Fragment,你可以使用 一個activity例項作為這個activity子fragments的通訊通道。即使這樣比Activity與Activity間的通訊好,你也想考慮使用Event Bus架構,使用如 Otto 或者 greenrobot EventBus作為更簡潔的實現。 如果你希望避免新增另外一個類庫,RxJava同樣可以實現一個Event Bus。

  • Fragments 一般通用的不只有UI 你可以有一個沒有介面的fragment作為Activity提供後臺工作。 進一步你可以使用這個特性來建立一個fragment 包含改變其它fragment的邏輯 而不是把這個邏輯放在activity中。

  • 甚至ActionBar 都可以使用內部fragment來管理 你可以選擇使用一個沒有UI介面的fragment來專門管理ActionBar,或者你可以選擇使用在每個Fragment中 新增它自己的action 來作為父Activity的ActionBar.參考.

很不幸,我們不建議廣泛的使用巢狀的fragments,因為 有時會引起matryoshka bugs。我們只有當它有意義(例如,在水平滑動的ViewPager在 像螢幕一樣fragment中)或者他的確是一個明智的選擇的時候才廣泛的使用fragment。

在一個架構級別,你的APP應該有一個頂級的activity來包含絕大部分業務相關的fragment。你也可能還有一些輔助的activity ,這些輔助的activity與主activity 通訊很簡單限制在這兩種方法Intent.setData() 或 Intent.setAction()或類似的方法。

Java 包結構

Android 應用程式在架構上大致是Java中的Model-View-Controller結構。 在Android 中 Fragment和Activity通常上是控制器類(http://www.informit.com/articles/article.aspx?p=2126865). 換句話說,他們是使用者介面的部分,同樣也是Views檢視的部分。

正是因為如此,才很難嚴格的將fragments (或者 activities) 嚴格的劃分成 控制器controlloers還是檢視 views。 最還是將它們放在自己單獨的 fragments 包中。只要你遵循之前提到的建議,Activities 則可以放在頂級目錄下。 如果你規劃有2到3個以上的activity,那麼還是同樣新建一個activities包吧。

然而,這種架構可以看做是另一種形式的MVC, 包含要被解析API響應的JSON資料,來填充的POJO的models包中。 和一個views包來包含你的自定義檢視、通知、導航檢視,widgets等等。 介面卡Adapter是在資料和檢視之間。然而他們通常需要通過getView()方法來匯出一些檢視, 所以你可以將adapters包放在views包裡面。

一些控制器角色的類是應用程式級別的,同時是接近系統的。 這些類放在managers包下面。 一些繁雜的資料處理類,比如說"DateUtils",放在utils包下面。 與後端互動負責網路處理類,放在network包下面。

總而言之,以最接近使用者而不是最接近後端去安排他們。

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

資原始檔 Resources

  • 命名 遵循字首表明型別的習慣,形如type_foo_bar.xml。例如:fragment_contact_details.xml,view_primary_button.xml,activity_main.xml.

組織布局檔案 如果你不確定如何排版一個佈局檔案,遵循一下規則可能會有幫助。

  • 每一個屬性一行,縮排4個空格

  • android:id 總是作為第一個屬性

  • android:layout_**** 屬性在上邊

  • style 屬性在底部

  • 關閉標籤/>單獨起一行,有助於調整和新增新的屬性

  • 考慮使用Designtime attributes 設計時佈局屬性,Android Studio已經提供支援,而不是硬編碼android:text (譯者注:牆內也可以參考stormzhang的這篇部落格連結)。

<?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_****屬性應該在 layout XML 中定義,同時其它屬性android:**** 應放在 styler XML中。此規則也有例外,不過大體工作 的很好。這個思想整體是保持layout屬性(positioning, margin, sizing) 和content屬性在佈局檔案中,同時將所有的外觀細節屬性(colors, padding, font)放 在style檔案中。

例外有以下這些:

  • android:id 明顯應該在layout檔案中

  • layout檔案中android:orientation對於一個LinearLayout佈局通常更有意義

  • android:text 由於是定義內容,應該放在layout檔案中

  • 有時候將android:layout_width 和 android:layout_height屬性放到一個style中作為一個通用的風格中更有意義,但是預設情況下這些應該放到layout檔案中。

使用styles 幾乎每個專案都需要適當的使用style檔案,因為對於一個檢視來說有一個重複的外觀是很常見的。 在應用中對於大多數文字內容,最起碼你應該有一個通用的style檔案,例如:

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

你或許需要為按鈕控制元件做同樣的事情,不要停止在那裡。將一組相關的和重複android:****的屬性放到一個通用的style中。

將一個大的style檔案分割成多個檔案 你可以有多個styles.xml 檔案。Android SDK支援其它檔案,styles這個檔名稱並沒有作用,起作用的是在檔案 裡xml的<style>標籤。因此你可以有多個style檔案styles.xml,style_home.xml,style_item_details.xml,styles_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, 應該放到一個按鈕風格中,而不是在color.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來說,減少使用顏色的種類是非常重要的。

像對待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>

佈局時在寫 margins 和 paddings 時,你應該使用spacing_****尺寸格式來佈局,而不是像對待String字串一樣直接寫值。 這樣寫會非常有感覺,會使組織和改變風格或佈局是非常容易。

避免深層次的檢視結構 有時候為了擺放一個檢視,你可能嘗試新增另一個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(這個inflate翻譯不過去,大家理解就行) 到其他views當中,也是可能會發生的。

可能會導致一系列的問題。你可能會遇到效能問題,因為處理起需要處理一個複雜的UI樹結構。 還可能會導致以下更嚴重的問題StackOverflowError.

因此儘量保持你的檢視tree:學習如何使用RelativeLayout, 如何 optimize 你的佈局 和如何使用<merge> 標籤.

小心關於WebViews的問題. 如果你必須顯示一個web檢視, 比如說對於一個新聞文章,避免做客戶端處理HTML的工作, 最好讓後端工程師協助,讓他返回一個 "" HTML。 當繫結WebViews到引用它的Activity,而不是繫結到ApplicationContext時。 WebViews 也能導致記憶體洩露。 當使用簡單的文字或按鈕時,避免使用WebView,這時使用TextView或Buttons更好。

測試框架

Android SDK的測試框架還處於初級階段,特別是關於UI測試方面。Android Gradle 目前實現了一個叫connectedAndroidTest的測試, 它使用一個JUnit 為Android提供的擴充套件外掛 extension of JUnit with helpers for Android.可以跑你生成的JUnit測試,

只當做單元測試時使用 Robolectric ,views 不用 它是一個最求提供"不連線裝置的"為了加速開發的測試, 非常時候做 models 和 view models 的單元測試。 然而,使用Robolectric測試時不精確的,也不完全對UI測試。 當你對有關動畫的UI元素、對話方塊等,測試時會有問題, 這主要是因為你是在 “在黑暗中工作”(在沒有可控的介面情況下測試)

Robotium 使寫UI測試非常簡單。 對於UI測試你不需 Robotium 跑與裝置連線的測試。 但它可能會對你有益,是因為它有許多來幫助類的獲得和分析檢視,控制螢幕。 測試用例看起來像這樣簡單:

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 emulatorlicense吧。 Genymotion 模擬器執行更快的秒幀的速度,比起典型的AVD模擬器。他有演示你APP的工具,高質量的模擬網路連線,GPS位置,等等。它同時還有理想的連線測試。 你若涉及適配使用很多不同的裝置,買一個Genymotion 版權是比你買很多真裝置便宜多的。

注意:Genymotion模擬器沒有裝載所有的Google服務,如Google Play Store和Maps。你也可能需 要測試Samsung指定的API,若這樣的話你還是需要購買一個真實的Samsung裝置。

混淆配置

ProGuard 是一個在Android專案中廣泛使用的壓縮和混淆打包的原始碼的工具。

你是否使用ProGuard取決你專案的配置,當你構建一個release版本的apk時,通常你應該配置gradle檔案。

buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles 'proguard-rules.pro'
    }
}

為了決定哪些程式碼應該被保留,哪些程式碼應該被混淆,你不得不指定一個或多個實體類在你的程式碼中。 這些實體應該是指定的類包含main方法,applets,midlets,activities,等等。 Android framework 使用一個預設的配置檔案,可以在SDK_HOME/tools/proguard/proguard-android.txt 目錄下找到。自定義的工程指定的 project-specific 混淆規則,如在my-project/app/proguard-rules.pro中定義, 會被新增到預設的配置中。

關於 ProGuard 一個普遍的問題,是看應用程式是否崩潰並報ClassNotFoundException 或者 NoSuchFieldException 或類似的異常, 即使編譯是沒有警告並執行成功。 這意味著以下兩種可能:

  1. ProGuard 已經移除了類,列舉,方法,成員變數或註解,考慮是否是必要的。

  2. ProGuard 混淆了類,列舉,成員變數的名稱,但是這些名字又被拿原始名稱使用了,比如通過Java的反射。

檢查app/build/outputs/proguard/release/usage.txt檔案看有問題的物件是否被移除了。 檢查 app/build/outputs/proguard/release/mapping.txt 檔案看有問題的物件是否被混淆了。

In order to prevent ProGuard from stripping away needed classes or class members, add a keepoptions to your proguard config: 以防 ProGuard 剝離 需要的類和類成員,新增一個 keep選項在你的 proguard 配置檔案中:

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

防止 ProGuard 混淆 一些類和成員,新增 keepnames:

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

更多例子請參考Proguard。

在構建專案之初,釋出一個版本 來檢查ProGuard規則是否正確的保持了重要的部分。 同時無論何時你添加了新的類庫,做一個釋出版本,同時apk在裝置上跑起來測試一下。 不要等到你的app要釋出 "1.0"版本了才做版本釋出,那時候你可能會碰到好多意想不到的異常,需要一些時間去修復他們。

Tips每次釋出新版本都要寫 mapping.txt。每釋出一個版本,如果使用者遇到一個bug,同時提交了一個混淆過的堆疊跟蹤。 通過保留mapping.txt檔案,來確定你可以除錯的問題。

DexGuard 如果你需要核心工具來優化,和專門混淆的釋出程式碼,考慮使用DexGuard, 一個商業軟體,ProGuard 也是有他們團隊開發的。 它會很容易將Dex檔案分割,來解決65K個方法限制問題。

資料儲存

SharedPreferences

如果你只是需要持久化儲存簡單的標記位,並且你的應用執行在單一程序,那麼SharedPreferences可能就滿足了你的需求。它是一個非常好的選擇。

這裡有兩個使你可能不使用SharedPreferences的原因:

  • Performance: Your data is complex or there is a lot of it

  • 效能問題:你的很多資料結構負責的資料需要儲存。

  • Multiple processes accessing the data: You have widgets or remote services that run in their own processes and require synchronized data

  • 多執行緒訪問資料:你有多個控制元件或者執行在各自執行緒上的遠端的服務需要同步資料。

ContentProviders

如果SharedPreferences不足以滿足你的需求,那麼你可以使用平臺標準的ContentProviders,它不僅快速,並且執行緒安全。

使用ContentProviders的唯一問題是建立他們需要大量的模板程式碼,並且少有高質量的教程。如果可以,我們可以通過使用第三方庫Schematic,極大降低了冗餘操作,去生成ContentProviders.

你可能仍然需要親自寫一些解析程式碼去從Sqlite讀取資料物件,或者進行相反的操作。如果可以序列化資料物件,例如通過Gson,只持久化儲存最終是字串。通過這種方式雖然會降低效能,但是從另一個角度來講,你不需要為每一個數據結構宣告表結構。

使用ORM我們通常不推薦使用物件關係對映第三方庫除非你有非常複雜的資料結構,並且你確定你真的需要它。他們通常比較複雜,並且需要時間去學習。如果你決定了在你的應用中使用ORM,你應該注意它是否是執行緒安全的,而對於目前大多數ORM解決方案都是非執行緒安全的。

使用StethoStetho 是一個Facebook 開源的Android除錯工具,它是Chrome Developer Tools的擴充套件。通過它可以檢測應用的網路情況。它也允許你可以檢測應用的資料庫,shared preferences。但是,你應該確保Stetho只有在Debug狀態下得以開啟,而不是在正式釋出版本中。

使用LeakCanaryLeakCanary 是可以在應用執行中檢測,定位記憶體洩露的Java庫。使用它應是你開發應用過程中的一部分。更多詳細的配置和使用情況請參照wiki。你只需要記得它在你的正式版本中你是不需要配置的。

致謝

感謝Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton 這些人和Futurice 開發者分享他們的Android開發經驗。