原文地址:https://github.com/futurice/android-best-practices/blob/master/translations/Chinese/README.cn.mdhtml
從Futurice公司Android開發人員中學到的經驗。 遵循下面準則,避免反覆發明輪子。java
若您對開發iOS或Windows Phone 有興趣, 請看iOS Good Practices 和 Windows client Good Practices 這兩篇文章。android
將你的Android SDK放在你的home文件夾或其它應用程序無關的位置。 當安裝有些包括SDK的IDE的時候,可能會將SDK放在IDE同一文件夾下,當你需要升級(或又一次安裝)IDE或更換的IDE時。會很麻煩。 此外,若果你的IDE是在普通用戶。不是在root下執行。還要避免吧SDK放到一下需要sudo權限的系統級別文件夾下。ios
你的默認編譯環境應該是Gradle. Ant 有很是多限制,也很是冗餘。使用Gradle。完畢下面工做很是方便:git
同一時候,Android Gradle插件做爲新標準的構建系統正在被Google積極的開發。github
有兩種流行的結構:老的Ant & Eclipse ADT project結構,和新的Gradle & Android Studio project結構, 你應該選擇新的project結構,假設你的project還在使用老的結構。考慮放棄吧,將project移植到新的結構。web
老的結構:shell
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的一個理念。json
你可以作到。好比。加入源組‘paid’和‘free’在src中,這將成爲您的應用程序的付費和免費的兩種模式的源碼。
你的項目引用第三方項目庫時(好比,library-foobar)。擁有一個頂級包名app
從第三方庫項目區分你的應用程序是很實用的。
而後settings.gradle
不斷引用這些庫項目,當中app/build.gradle
可以引用。
常用結構 參考Google's guide on Gradle for Android
小任務 除了(shell, Python, Perl, etc)這些腳本語言,你也可以使用Gradle 製做任務。
不少其它信息請參考Gradle's documentation。
password 在作版本號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.+'
}
無論使用什麼編輯器,必定要構建一個良好的project結構 編輯器每個人都有本身的 選擇,讓你的編輯器依據project結構和構建系統運做。那是你本身的責任。
當下首推Android Studio,因爲他是由谷歌開發,最接近Gradle,默認使用最新的project結構,已經到beta階段 (眼下已經有release 1.0了),它就是爲Android開發定製的。
你也可以使用Eclipse ADT ,但是你需要對它進行配置,因爲它使用了舊的project結構 和Ant做爲構建系統。你甚至可以使用純文版編輯器如Vim,Sublime Text,或者Emacs。假設那樣的話。你需要使用Gardle和adb
命令行。假設使用Eclipse集成Gradle 不適合你,你僅僅是使用命令行構建project。或遷移到Android Studio中來吧。
無論你使用何種開發工具。僅僅要確保Gradle和新的項目結構保持官方的方式構建應用程序,避免你的編輯器配置文件增長到版本號控制。好比,避免增長Ant build.xml
文件。 特別假設你改變Ant的配置,不要忘記保持build.gradle
是最新和起做用的。同一時候,善待其它開發人員,不要強制改變他們的開發工具和偏好。
Jackson 是一個將java對象轉換成JSON與JSON轉化java類的類庫。
Gson 是解決問題的流行方案,然而咱們發現Jackson更高效,因爲它支持替代的方法處理JSON:流、內存樹模型,和傳統JSON-POJO數據綁定。只是,請記住, Jsonkson庫比起GSON更大,因此依據你的狀況選擇,你可能選擇GSON來避免APP 65k個方法限制。其餘選擇: Json-smart and Boon JSON
網絡請求。緩存,圖片 運行請求後端server,有幾種交互的解決方式,你應該考慮實現你本身的網絡client。
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 Studioproject結構對話框中把它設置成爲SDK路徑,同一時候設置JAVA8_HOME
和JAVA7_HOME
環境變量, 而後在project根文件夾下配置 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不熟悉,僅僅需參照下面開始學習吧:
小心dex方法數限制,同一時候避免使用過多的類庫 Android apps,當打包成一個dex文件時,有一個65535個應用方法強硬限制[1] [2] [3]。
當你突破65k限制以後你會看到一個致命錯誤。所以,使用一個正常範圍的類庫文件。同一時候使用dex-method-counts 工具來決定哪些類庫可以再65k限制之下使用。特別的避免使用Guava類庫。因爲它包括超過13k個方法。
Fragments應該做爲你實現UI界面默認選擇。你可以反覆使用Fragments用戶接口來 組合成你的應用。咱們強烈推薦使用Fragments而不是activity來呈現UI界面,理由例如如下:
提供多窗格佈局解決方式 Fragments 的引入主要將手機應用延伸到平板電腦。因此在平板電腦上你可能有A、B兩個窗格,但是在手機應用上A、B可能分別充滿 整個屏幕。
假設你的應用在最初就使用了fragments。那麼之後將你的應用適配到其它不一樣尺寸屏幕就會很easy。
屏幕間數據通訊 從一個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 通訊很是easy限制在這兩種方法 Intent.setData()
或 Intent.setAction()
或相似的方法。
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
type_foo_bar.xml
。好比:fragment_contact_details.xml
,view_primary_button.xml
,activity_main.xml
.
組織布局文件 若果你不肯定怎樣排版一個佈局文件。遵循一下規則可能會有幫助。
android:id
老是做爲第一個屬性android:layout_****
屬性在上邊style
屬性在底部/>
單獨起一行。有助於調整和加入新的屬性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文件裏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"
/>
你也許需要爲button控件作相同的事情,不要中止在那裏。將一組相關的和反覆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值。而沒有其餘的。
不要使用它爲不一樣的button來定義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>
使用這樣的格式,你會很是easy的開始反覆定義RGBA值,這使假設需要改變基本色變的很是複雜。同一時候。這些定義是跟一些環境關聯起來的。如button
或者comment
, 應該放到一個button風格中,而不是在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" 這種名字也是全然可以接受的。 像這樣規範的顏色很easy改動或重構,會使應用一共使用了多少種不一樣的顏色變得很清晰。 一般一個具備審美價值的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字符串同樣直接寫值。 這樣寫會頗有感受,會使組織和改變風格或佈局是很easy。
避免深層次的視圖結構 有時候爲了擺放一個視圖,你可能嘗試加入還有一個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視圖。 比方說對於一個新聞文章。避免作client處理HTML的工做, 最好讓後端project師協助,讓他返回一個 "純" HTML。 WebViews 也能致使內存泄露 當保持引他們的Activity,而不是被綁定到ApplicationContext中的時候。 當使用簡單的文字或button時,避免使用WebView,這時使用TextView或Buttons更好。
Android SDK的測試框架還處於0基礎階段,特別是關於UI測試方面。Android Gradle 眼下實現了一個叫connectedAndroidTest
的測試, 它使用一個JUnit 爲Android提供的擴展插件 extension of JUnit with helpers for Android.可以跑你生成的JUnit測試,
僅僅當作單元測試時使用 Robolectric ,views 不用 它是一個最求提供"不鏈接設備的"爲了加速開發的測試, 很時候作 models 和 view models 的單元測試。 然而,使用Robolectric測試時不精確的,也不全然對UI測試。
當你對有關動畫的UI元素、對話框等,測試時會有問題, 這主要是因爲你是在 「在黑暗中工做」(在沒有可控的界面狀況下測試)
*Robotium 使寫UI測試很easy。 * 對於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指定的 project-specific 混淆規則,如在my-project/app/proguard-rules.pro
中定義, 會被加入到默認的配置中。
關於 ProGuard 一個廣泛的問題,是看應用程序是否崩潰並報ClassNotFoundException
或者 NoSuchFieldException
或相似的異常, 即便編譯是沒有警告並執行成功。 這意味着下面兩種可能:
檢查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 keep
options to your proguard config: 以防 ProGuard 剝離 需要的類和類成員,加入一個 keep
選項在你的 proguard 配置文件裏:
-keep class com.futurice.project.MyClass { *; }
防止 ProGuard 混淆 一些類和成員,加入 keepnames
:
-keepnames class com.futurice.project.MyClass { *; }
查看this template's ProGuard config 中的一些樣例。 不少其它樣例請參考Proguard。
在構建項目之初,公佈一個版本號 來檢查ProGuard規則是否正確的保持了重要的部分。 同一時候無論什麼時候你加入了新的類庫。作一個公佈版本號,同一時候apk在設備上跑起來測試一下。
不要等到你的app要公佈 "1.0"版本號了才作版本號公佈,那時候你可能會碰到好多意想不到的異常,需要一些時間去修復他們。
Tips每次公佈新版本號都要寫 mapping.txt
。每公佈一個版本號,假設用戶遇到一個bug。同一時候提交了一個混淆過的堆棧跟蹤。
經過保留mapping.txt
文件,來肯定你可以調試的問題。
DexGuard 若果你需要核心工具來優化。和專門混淆的公佈代碼,考慮使用DexGuard, 一個商業軟件,ProGuard 也是有他們團隊開發的。 它會很是easy將Dex文件切割,來解決65K個方法限制問題。
感謝Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton 這些人和Futurice 開發人員分享他們的Android開發經驗。
Futurice Oy Creative Commons Attribution 4.0 International (CC BY 4.0)
Translated to Chinese by andyiac