「译」Android最佳实践指南——GitHub Star 7000+

Updated on 2016/2/14 更新Stetho 相关,markdown不支持锚 -_-||||||||||||
Updated on 2016/1/15 表明谷歌对ADT的废弃态度,新增段落:对于非发布版本的构建使用不同的包名
不定时同步更新原文
欢迎转载,但请保留译注者链接:http://www.jianshu.com/p/613d28a3c8a0

Lessons learned from Android developers in Futurice. Avoid reinventing the wheel by following these guidelines. If you are interested in iOS or Windows Phone development, be sure to check also our iOS Good Practices and Windows App Development Best Practices documents.

Summary 概要

使用Gradle和它推荐的项目结构

将密码和敏感数据放在gradle.properties中

不要自己写Http客户端,使用Volley或OkHttp

使用Jackson来解析JSON

由于65k方法数限制,避免使用Guava并维持数量较少的库引用

使用Fragment呈现UI

Activity仅用于管理Fragment

Layout XML同样也是代码,好好组织它们

使用style来避免Layout XML中的重复属性

使用多个style文件避免生成一个庞然大物

保持colors.xml简短并谨记DRY,只在其中定义基础色彩

同样保持dimens.xml DRY,仅定义一般常量

不要制造过深的ViewGroup层级

避免WebView的客户端侧处理,并知晓它可能导致内存泄漏

使用Robolectric做单元测试,Robotium做UI测试

模拟器使用Genymotion

总是使用 ProGuard 或 DexGuard

简单的数据持久化使用SharedPreferences,其他的使用ContentProvider

使用Stetho进行应用debug


Android SDK

将 Android SDK 放在你的home目录或是其他应用无关的位置。某些IDE安装的时候就包含了SDK,并且会将其放置在与IDE相同的目录下。当你需要升级(或重装,或改变)IDE时这就成了一件坏事。同时还要避免将SDK放在另一个系统级别的目录下,那样很可能会让你在使用user权限运行IDE时需要用到sudo权限。

Build system 构建系统

你的默认选择应该是 Gradle。Ant的限制要多并且语句还更冗长。使用Gradle,能够简单做到:

  • 使用不同的flavours或variants来构建你的app
  • 制作简单的script-like的tasks
  • 管理并下载依赖
  • 自定义keystores
  • 还有更多

Android's Gradle plugin同时在被Google做为新标准构建系统积极开发中

Project structure 项目结构

有两种广泛使用的选择:旧式的Ant & Eclipse ADT project structure,和新式的Gradle & Android Studio project structure。你应该选择新式,如果你还在使用旧式,考虑将之做为宝贵遗产并转向新式吧。

Old structure:

old-structure
├─ assets
├─ libs
├─ res
├─ src
│  └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro

New structure:

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

主要不同点在于新式使用了来自Gradle的概念,更清晰地分开了'source sets' (main, androidTest)。举个例子,你可以添加source sets 'paid' 和 'free' 到 src中作为构建 paid 版本 和 free 版本的代码目录。
使用一个top-level app对于将你的app从那些需要引用的 库项目 (e.g., library-foobar) 中区分开来很有效。settings.gradle中写着那些 能被app/build.gradle引用的 库项目 的引用。

Gradle configuration Gradle配置

General structure. Follow Google's guide on Gradle for Android

Small tasks. 与这些 (shell, Python, Perl, etc) 脚本不同,你能用Gradle来安排tasks。Just follow Gradle's documentation for more details.

Passwords.
在app的 build.gradle中你需要为release版本的构建定义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),下载与处理 jars 的更新将会是一件笨重累赘的事,这个问题在 Maven 中被解决得很好,这也是 Android Gradle builds 所鼓励的方式。看下面这个例子:

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

避免 Maven 的动态依赖
避免使用动态依赖的库版本, 像是 2.1.+ ,因为这可能会导致不同的、不稳定的构建,或是在数次构建之间表现出细微的、不可追踪的差异行为。使用静态版本像是2.1.1会创建更稳定的、可预期的和可重复的开发环境。

对于非发布版本的构建使用不同的包名
debugbuild type使用applicationIdSuffix ,这能够让debug还有release版本的apk同时安装在同一部设备上(如果你有任何需要的话,还能将此技巧应用于自定义的 build 类型)。对于一个 app 的生命周期来说,当它被发布到市场之后,这一特性将变得非常有价值。

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

        release {
            // ...
        }
    }
}

使用不同的icons来区分安装在设备上的不同构建版本app——比如说使用不同的色彩或是使用一个覆盖的"debug"标签。对于Gradle来说,这非常容易,你只需要将debugicon 放在app/src/debug/res,而release icon 放在 app/src/release/res。你还可以针对不同的构建版本更改应用名change app name,versionName也可以改变(就像上面这个例子做的那样)。

IDEs and text editors IDE和文本编辑器

无论使用什么编辑器,它必须能针对项目结构让人愉快地使用
文本编辑器是一个很个人的选择,依据项目结构和构建系统来让编辑器起到作用同时也是你的责任。

目前最推荐的IDE是 Android Studio,因为它由Google开发,与Gradle关系最紧密,默认使用新式项目结构,针对Android开发量身定做。

使用 Eclipse ADT 来进行Android开发不再是一个好的选择。2015年,谷歌终止了对ADT的支持,并催促开发者尽快向Android Studio迁徙。Google ended ADT support at the end of 2015and urges users tomigrate to Android Studioas soon as possible.你也可以继续使用它,但是需要一番配置,因为它采用旧式项目结构与Ant构建。如果 Eclipse 的 Gradle 集成令你使用得不愉快,你的选择只有使用命令行来构建。

你也能只使用一个单纯的文本编辑器像是Vim,Sublime Text, 或 Emacs。在这种情况下,你需要在命令行环境下使用 Gradle 和 adb

无论你使用什么,总得确保使用 Gradle 和新式项目结构 来构建应用,注意不要将编辑器相关的配置文件加到版本控制系统中。比如,避免添加Ant 的 build.xml 文件。
如果你在 Ant 中更改配置, 一定不要忘记让build.gradle保持 up-to-date 和 functioning 。

还有,善待其他的开发者,不要强迫他们改变他们个性化的工具配置。

Libraries 库

Jackson 是一个用于 Object 与 JSON 间相互转换的Java库。Gson 也是一个作为解决此问题广受欢迎的存在。然而我们发现 Jackson 表现更好,因为它提供了可选择的方式来处理 JSON : streaming, in-memory tree model, and traditional JSON-POJO data binding。所以Jackson 的体积会比 GSON 要大。取决于你的实际情况,你可能倾向于选择 GSON 以避免 65k 方法数限制。其他选项还有: Json-smart and Boon JSON

网络,缓存和图像.
这儿有好几种经过实战检验的用于后端服务器请求的解决方案,你将使用哪一种取决于你自己将要实现的客户端。使用 Volley 或 Retrofit. Volley 额外提供了 helpers 以解决 载入和缓存图像。要是你选择 Retrofit, 考虑用 Picasso 来做这些, 同时用 OkHttp 来完成高效 HTTP 请求。Retrofit, Picasso 和 OkHttp 这三个工具由同一家公司开发,所以他们能完美地补足彼此。 OkHttp can also be used in connection with Volley.

RxJava 是一个响应式编程框架,换句话说,处理异步事件。 它是一种强大并有前途的范例,可能从可读性上讲不是那么理想因为它是如此的不同。我们推荐你在使用这个库来构筑整个应用之前抱持着足够的警惕。有一些项目通过使用 RxJava 构筑, 如果你需要帮助可以和他们之中的人交谈: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. 我们写了一些博文发表在这上面: [1], [2], [3], [4].

如果你从前没有运用 Rx 的经历,只需要从将它作为对 API 的回应开始即可。你也可以选择从作为简单 UI 事件处理开始,像是 search field 上的点击或者输入事件。如果你对自己的 Rx 技能足够自信并决定将它应用到整个应用构筑中,一定要针对所有不易理解的部分写Javadoc。用心记住其他不熟悉 Rx 的程序员可能会对维护项目感到无比头大。尽你的全力来帮助他们理解你的代码还有 Rx 。

Retrolambda 是一个用来让 JDK8 之前的 Android 或是其他平台支持Lambda表达式语法的库。它用于保持你的代码紧凑并可读,特别是当你使用函数式风格编写代码比如说 RxJava 。为了使用它, 你需要安装 JDK8, 在 Android Studio 的 Project Structure dialog 设置它作为你的 SDK Location , 然后设置环境变量 JAVA8_HOMEJAVA7_HOME , 接着在项目根目录的 build.gradle 引用依赖:

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

并在每一个模块的 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 lambda 的代码协助支持,如果你是 lambda 的新手,只需从以下建议中开始:

  • 任何只有一个方法的接口都是 "lambda friendly" 的,亦即能被收缩成更紧凑的语法格式
  • 如果对 参数 或其他 的什么拿不准,那么就写一个普通的匿名内部类并让 Android Studio 为你将它收缩成 lambda 格式

注意 dex 方法数限制,避免使用过多库
Android apps 当被打包成 dex file 时, 有一个固定的引用方法数限制:65536 [1] [2] [3]. 如果你超出了这一限制,就会在编译时遇见一个致命错误。出于这个理由,使用尽可能少的库,并且使用这个工具 dex-method-counts 来决定使用哪些库集合来保证低于这一限制。特别要避免使用 Guava library, 因为它包含超过 13k 方法.

Activities and Fragments

针对怎样最佳地通过 Fragments 和 Activities 来组织 Android 架构尚无统一结论,这一点不论在社区还是在 Futurice 的开发者中都是一样。Square 甚至开发了一个库用来最大化地通过View来构筑应用架构 a library for building architectures mostly with Views,以此绕过对于 Fragment 的依赖,但这在社区中仍未被作为广泛推荐的方案。

出于Android API的历史,你能自然地想到将Fragments作为屏幕上的UI碎片。换句话说,Fragments通常与UI相关联。Activities能被自然地想到作为控制器,从生命周期和状态管理上的重要性来说。然而,你很可能遇见角色产生变化的情况:activities可能被作为UI角色(delivering transitions between screens),而fragments能被单独作为控制器 fragments might be used solely as controllers。我们推荐谨慎启航,获知尽可能多的消息然后作出决定,因为无论是选择fragments-only、activities-only还是views-only架构,都存在着其缺陷。这里对于需要小心些什么有一些建议,但你需要持保留态度吸收它们:

  • 避免广泛使用嵌套fragments nested fragments , 这可能会发生 matryoshka bugs 。 要么在有意义的时候使用嵌套fragments (举个例子, 在一个screen-like 的fragment中需要一些 fragments 放在一个水平方向滑动的 ViewPager 中) ,要么就确保这是一个深思熟虑后的决定。
  • 避免放太多代码在activities中。任何情况下只要可能,让它们作为轻量containers,其存在意义首要在于应用的生命周期循环以及其他重要的Android-interfacing APIs。采用单fragment的activity而不是一个单纯的activity,这样可以将UI代码放在fragment中。当你需要改变它以重新放置到一个标签布局或是一个多fragment表格屏幕中去的时候,这使得它能够被复用。避免持有一个无对应fragment的activity,除非你完全知晓这样做的后果。
  • 不要让你的应用的内部工作滥用Android-level APIs,像是重度依赖于Intent。这可能会影响到Android OS或是其他应用,制造bugs或者延迟。举个例子,如果你的应用使用Intent作为内部通信手段,可能会招致多秒延迟——如果它在OS启动后紧接着被用户打开的话。

Java packages architecture Java分包架构

在Java分包架构方面,Android只能算是粗略接近MVC模型Model-View-Controller。在Android中,Fragment和Activity是实际上的控制器类Fragment and Activity are actually controller classes。从另一方面来说,它们又明显是用户接口的部分,所以同时也是视图。

出于这一理由,无法将fragments (or activities)严格划分为控制器或是视图。让它们保持自己的fragments package更好一些。Activities能放在最高级package中只要你遵循之前部分的建议。如果你计划超过两个或三个Activities,那么再加一个 activities package。

另外,也可以像经典的MVC那样来进行分包架构,通过使用一个models package包含POJOs(这些POJOs由JSON解析器解析API responses转化生成),和一个views package包含你的自定义Views,notifications, action bar views, widgets, etc。Adapters算是一个麻烦,存在于数据和视图之间。然而,典型情况是它们会通过getView()方法输出一些视图,因此你可以把adapters subpackage将在views里面。

一些application-wide的和接近于Android系统的控制器类可以放置在一个managers package中。混杂的数据处理类,像是"DateUtils",放在utils package中。那些用于与后端交互的类则放在network package中。

总的来说,序列是从 closest-to-backend 到 closest-to-the-user:

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

Resources 资源

命名 遵循类型前缀惯例,像是 type_foo_bar.xml. Examples: fragment_contact_details.xml, view_primary_button.xml, activity_main.xml.

组织 layout XMLs. 如果你不确定如何格式化 layout XML, 以下惯例会有所帮助:

  • 一个属性一行,4 空格缩进
  • android:id 总是作为第一个属性
  • android:layout_**** 这类属性放在最上面
  • style 属性放在最下面
  • Tag closer /> 拥有自己的一行, 以使顺序清晰和添加属性变得容易
  • 与其使用硬编码 android:text, 不如考虑使用设计时属性 Designtime attributes ,其受 Android Studio支持.



    

    


作为一个经验法则,android:layout_****应该在layout XML中定义,同时其他的属性android:****应该放在style XML中。这条法则会有例外,但总体而言工作得很好。这个想法是为了仅将layout (positioning, margin, sizing)和content属性放在layout files中,而外观详情 (colors, padding, font) 放在 styles files中。

那些例外是:

  • android:id 明显应该放在 layout files 中
  • android:orientation 属性对于 LinearLayout 来说一般 放在 layout files 中更有意义
  • android:text 应该放在 layout files 中因为它定义了 content
  • 有些情况下让 style 来定义 android:layout_widthandroid:layout_height 常量会很有用,但默认情况下它们应该出现在 layout files 中

使用 styles. 几乎每一个项目都需要适当地使用 style,因为对于 view 来说有着重复的外观是非常常见的事,看下面这个例子:


该 style 被用于 TextViews:


你很可能需要为buttons做一些相同的事,不要在这里停下。从宏观角度上提炼出一组相关联的、重复的android:****属性到一个公共的 style 中去。

把一个大的 style 文件分割成多个
你无须拘泥于单个 styles.xml 文件。 Android SDK 支持其他不符合这一命名规则的文件,关于文件名 styles什么魔法也没有,起效果的是文件中的 XML tags