【譯】[干貨](méi) Android 開(kāi)發(fā)規(guī)范與應(yīng)用

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 PracticesWindows client Good Practices 這兩篇文章。

摘要

  • 使用 Gradle 和它推薦的工程結(jié)構(gòu)
  • 把密碼和敏感數(shù)據(jù)放在gradle.properties
  • 不要自己寫(xiě) HTTP 客戶(hù)端,使用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í)避免在客戶(hù)端做處理,當(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是在普通用戶(hù)绊寻,不是在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。如果那樣的話(huà)锻离,你需要使用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ò)客戶(hù)端敌厘。使用 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ù),換句話(huà)說(shuō)毯焕,能處理異步的事件衍腥。
這是一個(gè)強(qiáng)大的和有前途的模式,同時(shí)也可能會(huì)造成混淆纳猫,因?yàn)樗侨绱说牟煌?br> 我們建議在使用這個(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ì)話(huà)框中把它設(shè)置成為SDK路徑捎泻,同時(shí)設(shè)置JAVA8_HOMEJAVA7_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用戶(hù)接口來(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可能分別充滿(mǎn)
    整個(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)?br> 有時(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).
換句話(huà)說(shuō),他們是用戶(hù)接口的部分攘须,同樣也是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包下面。

總而言之历谍,以最接近用戶(hù)而不是最接近后端去安排他們现拒。

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的這篇博客鏈接)退唠。
<?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>

作為一個(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ū)?code>android: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文件惊楼,例如:

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

應(yīng)用到TextView 中:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/price"
    style="@style/ContentText"
    />

你或許需要為按鈕控件做同樣的事情,不要停止在那里秸讹。將一組相關(guān)的和重復(fù)android:****的屬性放到一個(gè)通用的style中檀咙。

將一個(gè)大的style文件分割成多個(gè)文件 你可以有多個(gè)styles.xml 文件。Android SDK支持其它文件璃诀,styles這個(gè)文件名稱(chēng)并沒(méi)有作用弧可,起作用的是在文件
里xml的<style>標(biāo)簽。因此你可以有多個(gè)style文件styles.xml,style_home.xml,style_item_details.xml,styles_forms.xml劣欢。
不用于資源文件路徑需要為系統(tǒng)構(gòu)建起的有意義棕诵,在res/values目錄下的文件可以任意命名。

colors.xml是一個(gè)調(diào)色板 在你的colors.xml文件中應(yīng)該只是映射顏色的名稱(chēng)一個(gè)RGBA值凿将,而沒(méi)有其它的校套。不要使用它為不同的按鈕來(lái)定義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>

使用這種格式牧抵,你會(huì)非常容易的開(kāi)始重復(fù)定義RGBA值笛匙,這使如果需要改變基本色變的很復(fù)雜。同時(shí)犀变,這些定義是跟一些環(huán)境關(guān)聯(lián)起來(lái)的妹孙,如button或者comment,
應(yīng)該放到一個(gè)按鈕風(fēng)格中,而不是在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>

向應(yīng)用設(shè)計(jì)者那里要這個(gè)調(diào)色板,名稱(chēng)不需要跟"green", "blue", 等等相同映琳。
"brand_primary", "brand_secondary", "brand_negative" 這樣的名字也是完全可以接受的机隙。
像這樣規(guī)范的顏色很容易修改或重構(gòu)蜘拉,會(huì)使應(yīng)用一共使用了多少種不同的顏色變得非常清晰。
通常一個(gè)具有審美價(jià)值的UI來(lái)說(shuō)有鹿,減少使用顏色的種類(lèi)是非常重要的旭旭。

像對(duì)待colors.xml一樣對(duì)待dimens.xml文件 與定義顏色調(diào)色板一樣,你同時(shí)也應(yīng)該定義一個(gè)空隙間隔和字體大小的“調(diào)色板”葱跋。
一個(gè)好的例子持寄,如下所示:

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

布局時(shí)在寫(xiě) margins 和 paddings 時(shí),你應(yīng)該使用spacing_****尺寸格式來(lái)布局娱俺,而不是像對(duì)待String字符串一樣直接寫(xiě)值稍味。
這樣寫(xiě)會(huì)非常有感覺(jué),會(huì)使組織和改變風(fēng)格或布局是非常容易荠卷。

避免深層次的視圖結(jié)構(gòu) 有時(shí)候?yàn)榱藬[放一個(gè)視圖模庐,你可能?chē)L試添加另一個(gè)LinearLayout。你可能使用這種方法解決:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <RelativeLayout
        ...
        >

        <LinearLayout
            ...
            >

            <LinearLayout
                ...
                >

                <LinearLayout
                    ...
                    >
                </LinearLayout>

            </LinearLayout>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>

即使你沒(méi)有非常明確的在一個(gè)layout布局文件中這樣使用油宜,如果你在Java文件中從一個(gè)view inflate(這個(gè)inflate翻譯不過(guò)去掂碱,大家理解就行) 到其他views當(dāng)中,也是可能會(huì)發(fā)生的慎冤。

可能會(huì)導(dǎo)致一系列的問(wèn)題疼燥。你可能會(huì)遇到性能問(wèn)題,因?yàn)樘幚砥鹦枰幚硪粋€(gè)復(fù)雜的UI樹(shù)結(jié)構(gòu)蚁堤。
還可能會(huì)導(dǎo)致以下更嚴(yán)重的問(wèn)題StackOverflowError.

因此盡量保持你的視圖tree:學(xué)習(xí)如何使用RelativeLayout,
如何 optimize 你的布局 和如何使用
<merge> 標(biāo)簽.

小心關(guān)于WebViews的問(wèn)題. 如果你必須顯示一個(gè)web視圖醉者,
比如說(shuō)對(duì)于一個(gè)新聞文章,避免做客戶(hù)端處理HTML的工作披诗,
最好讓后端工程師協(xié)助湃交,讓他返回一個(gè) "" HTML。
WebViews 也能導(dǎo)致內(nèi)存泄露
當(dāng)保持引他們的Activity藤巢,而不是被綁定到ApplicationContext中的時(shí)候搞莺。
當(dāng)使用簡(jiǎn)單的文字或按鈕時(shí),避免使用WebView掂咒,這時(shí)使用TextView或Buttons更好才沧。

測(cè)試框架

Android SDK的測(cè)試框架還處于初級(jí)階段,特別是關(guān)于UI測(cè)試方面绍刮。Android Gradle
目前實(shí)現(xiàn)了一個(gè)叫connectedAndroidTest的測(cè)試温圆,
使用一個(gè)JUnit 為Android提供的擴(kuò)展插件 extension of JUnit with helpers for Android.可以跑你生成的JUnit測(cè)試,

只當(dāng)做單元測(cè)試時(shí)使用 Robolectric 孩革,views 不用
它是一個(gè)最求提供"不連接設(shè)備的"為了加速開(kāi)發(fā)的測(cè)試岁歉,
非常時(shí)候做 models 和 view models 的單元測(cè)試。
然而膝蜈,使用Robolectric測(cè)試時(shí)不精確的锅移,也不完全對(duì)UI測(cè)試熔掺。
當(dāng)你對(duì)有關(guān)動(dòng)畫(huà)的UI元素、對(duì)話(huà)框等非剃,測(cè)試時(shí)會(huì)有問(wèn)題置逻,
這主要是因?yàn)槟闶窃?“在黑暗中工作”(在沒(méi)有可控的界面情況下測(cè)試)

**Robotium 使寫(xiě)UI測(cè)試非常簡(jiǎn)單。
** 對(duì)于UI測(cè)試你不需 Robotium 跑與設(shè)備連接的測(cè)試备绽。
但它可能會(huì)對(duì)你有益券坞,是因?yàn)樗性S多來(lái)幫助類(lèi)的獲得和分析視圖,控制屏幕肺素。
測(cè)試用例看起來(lái)像這樣簡(jiǎn)單:

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"));

模擬器

如果你全職開(kāi)發(fā)Android App,那么買(mǎi)一個(gè)Genymotion emulatorlicense吧恨锚。
Genymotion 模擬器運(yùn)行更快的秒幀的速度,比起典型的AVD模擬器倍靡。他有演示你APP的工具猴伶,高質(zhì)量的模擬網(wǎng)絡(luò)連接,GPS位置菌瘫,等等蜗顽。它同時(shí)還有理想的連接測(cè)試布卡。
你若涉及適配使用很多不同的設(shè)備雨让,買(mǎi)一個(gè)Genymotion 版權(quán)是比你買(mǎi)很多真設(shè)備便宜多的。

注意:Genymotion模擬器沒(méi)有裝載所有的Google服務(wù)忿等,如Google Play Store和Maps栖忠。你也可能需
要測(cè)試Samsung指定的API,若這樣的話(huà)你還是需要購(gòu)買(mǎi)一個(gè)真實(shí)的Samsung設(shè)備贸街。

混淆配置

ProGuard 是一個(gè)在Android項(xiàng)目中廣泛使用的壓縮和混淆打包的源碼的工具庵寞。

你是否使用ProGuard取決你項(xiàng)目的配置,當(dāng)你構(gòu)建一個(gè)release版本的apk時(shí)薛匪,通常你應(yīng)該配置gradle文件捐川。

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

為了決定哪些代碼應(yīng)該被保留,哪些代碼應(yīng)該被混淆逸尖,你不得不指定一個(gè)或多個(gè)實(shí)體類(lèi)在你的代碼中古沥。
這些實(shí)體應(yīng)該是指定的類(lèi)包含main方法,applets娇跟,midlets岩齿,activities,等等苞俘。
Android framework 使用一個(gè)默認(rèn)的配置文件盹沈,可以在SDK_HOME/tools/proguard/proguard-android.txt
目錄下找到。自定義的工程指定的 project-specific 混淆規(guī)則吃谣,如在my-project/app/proguard-rules.pro中定義乞封,
會(huì)被添加到默認(rèn)的配置中做裙。

關(guān)于 ProGuard 一個(gè)普遍的問(wèn)題,是看應(yīng)用程序是否崩潰并報(bào)ClassNotFoundException 或者 NoSuchFieldException 或類(lèi)似的異常歌亲,
即使編譯是沒(méi)有警告并運(yùn)行成功菇用。
這意味著以下兩種可能:

  1. ProGuard 已經(jīng)移除了類(lèi),枚舉陷揪,方法惋鸥,成員變量或注解,考慮是否是必要的悍缠。
  2. ProGuard 混淆了類(lèi)卦绣,枚舉,成員變量的名稱(chēng)飞蚓,但是這些名字又被拿原始名稱(chēng)使用了滤港,比如通過(guò)Java的反射。

檢查app/build/outputs/proguard/release/usage.txt文件看有問(wèn)題的對(duì)象是否被移除了趴拧。
檢查 app/build/outputs/proguard/release/mapping.txt 文件看有問(wèn)題的對(duì)象是否被混淆了溅漾。

In order to prevent ProGuard from stripping away needed classes or class members, add a keep options to your proguard config:
以防 ProGuard 剝離 需要的類(lèi)和類(lèi)成員,添加一個(gè) keep選項(xiàng)在你的 proguard 配置文件中:

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

防止 ProGuard 混淆 一些類(lèi)和成員著榴,添加 keepnames:

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

查看this template's ProGuard config 中的一些例子添履。
更多例子請(qǐng)參考Proguard

在構(gòu)建項(xiàng)目之初脑又,發(fā)布一個(gè)版本 來(lái)檢查ProGuard規(guī)則是否正確的保持了重要的部分暮胧。
同時(shí)無(wú)論何時(shí)你添加了新的類(lèi)庫(kù),做一個(gè)發(fā)布版本问麸,同時(shí)apk在設(shè)備上跑起來(lái)測(cè)試一下往衷。
不要等到你的app要發(fā)布 "1.0"版本了才做版本發(fā)布,那時(shí)候你可能會(huì)碰到好多意想不到的異常严卖,需要一些時(shí)間去修復(fù)他們席舍。

Tips每次發(fā)布新版本都要寫(xiě) mapping.txt。每發(fā)布一個(gè)版本哮笆,如果用戶(hù)遇到一個(gè)bug来颤,同時(shí)提交了一個(gè)混淆過(guò)的堆棧跟蹤。
通過(guò)保留mapping.txt文件疟呐,來(lái)確定你可以調(diào)試的問(wèn)題脚曾。

DexGuard 若果你需要核心工具來(lái)優(yōu)化,和專(zhuān)門(mén)混淆的發(fā)布代碼启具,考慮使用DexGuard,
一個(gè)商業(yè)軟件本讥,ProGuard 也是有他們團(tuán)隊(duì)開(kāi)發(fā)的。
它會(huì)很容易將Dex文件分割,來(lái)解決65K個(gè)方法限制問(wèn)題拷沸。

致謝

感謝Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori M?ntyl?, Mark Voit, Andre Medeiros, Paul Houghton 這些人和Futurice 開(kāi)發(fā)者分享他們的Android開(kāi)發(fā)經(jīng)驗(yàn)色查。

License

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

最后打一個(gè)廣告

純凈日?qǐng)?bào) https://github.com/laucherish/PureZhihuD
一個(gè)采用 RxJava + Retrofit + OkHttp 框架實(shí)現(xiàn)的開(kāi)源軟件

純凈日?qǐng)?bào)界面
最美的風(fēng)景在路上
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市撞芍,隨后出現(xiàn)的幾起案子秧了,更是在濱河造成了極大的恐慌,老刑警劉巖序无,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件验毡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帝嗡,警方通過(guò)查閱死者的電腦和手機(jī)晶通,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哟玷,“玉大人狮辽,你說(shuō)我怎么就攤上這事〕补眩” “怎么了喉脖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抑月。 經(jīng)常有香客問(wèn)我树叽,道長(zhǎng),這世上最難降的妖魔是什么爪幻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任菱皆,我火速辦了婚禮须误,結(jié)果婚禮上挨稿,老公的妹妹穿的比我還像新娘。我一直安慰自己京痢,他們只是感情好奶甘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著祭椰,像睡著了一般臭家。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上方淤,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天钉赁,我揣著相機(jī)與錄音,去河邊找鬼携茂。 笑死你踩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播带膜,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吩谦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了膝藕?” 一聲冷哼從身側(cè)響起式廷,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芭挽,沒(méi)想到半個(gè)月后滑废,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袜爪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年策严,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饿敲。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妻导,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怀各,到底是詐尸還是另有隱情倔韭,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布瓢对,位于F島的核電站寿酌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏硕蛹。R本人自食惡果不足惜醇疼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望法焰。 院中可真熱鬧秧荆,春花似錦、人聲如沸埃仪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卵蛉。三九已至颁股,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傻丝,已是汗流浹背甘有。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留葡缰,地道東北人亏掀。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓允睹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親幌氮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缭受,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評(píng)論 25 707
  • 從Futurice公司Android開(kāi)發(fā)者中學(xué)到的經(jīng)驗(yàn)。遵循以下準(zhǔn)則该互,避免重復(fù)發(fā)明輪子米者。若您對(duì)開(kāi)發(fā)iOS或Wind...
    兔子吃過(guò)窩邊草閱讀 937評(píng)論 0 7
  • 轉(zhuǎn)載文章,Android開(kāi)發(fā)過(guò)程中的經(jīng)驗(yàn)總結(jié)宇智。原文鏈接遵循以下準(zhǔn)則蔓搞,避免重復(fù)發(fā)明輪子。若您對(duì)開(kāi)發(fā)iOS或Windo...
    王鵬程O(píng)range閱讀 1,187評(píng)論 0 22
  • Android 開(kāi)發(fā)最佳實(shí)踐 轉(zhuǎn)載于https://github.com/futurice/android-bes...
    Di_xin閱讀 680評(píng)論 0 12
  • 大家好随橘。 我叫阿Cat喂分。 今天和大家分享一下………我一生中的宿敵 自稱(chēng)西湖區(qū)宋慧喬的…爆Co。
    阿阿阿Cat閱讀 404評(píng)論 0 0