Android开发最佳实践(未完待译)

本文介绍了Android开发中的最佳实践,包括使用Gradle构建系统、管理密码和敏感数据、解析JSON数据、处理网络请求等内容。强调了合理使用Activity和Fragment的重要性,并提供了资源文件管理的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本人第一次翻译英文技术文章,深感翻译之不易。断断续续好几次,坚持要将这篇文章翻译完。水平有限,有不足之处还请提出.
原文地址:Best practices in Android development

目录:

  1. 使用Gradle和它推荐的项目结构。
  2. 将密码和敏感数据放在gradle.properties中。
  3. 使用Jackson库来解析JSON数据。
  4. 不要自己写HTTP client,使用Volley或者OKHttp库。
  5. 由于65k的方法限制,避免使用Guava库并使用尽可能少的库。
  6. 用Fragment来显示UI界面。
  7. Activity只用于管理Fragment。
  8. layout xml文件也是代码,好好管理它们。
  9. 使用style来避免在layout xml中的重复属性。
  10. 使用多个style文件来避免一个单独而巨大的style文件。
  11. 保持你的colors.xml简短干净,只定义调色板颜色。
  12. 同样保持你的dimens.xml干净,定义一般的常量。
  13. 不要做深层级布局结构的ViewGroup。
  14. 避免客户端处理WebView的内容,并注意内存泄露。
  15. 使用Robolectric进行单元测试,使用Robotium进行连接设备(UI)的测试。
  16. 使用Genymotion作为你的模拟器。
  17. 一直使用ProGuard或者DexGuard。
  18. 对简单的长久保存,使用SharedPreference。否则使用ContentProvider来保存。
  19. 使用Stetho来调试你的应用。

Android SDK

把你的Android SDK文件夹放在你的主目录或者其他独立于应用的位置。当安装了一些带有SDK的IDE时,可能会将SDK放到IDE同一目录下。当你需要升级(或者重装)、或者改变IDE时SDK可能会损坏。也避免将SDK放在另一个系统级的目录下,因为如果你的IDE运行在你的用户界别而非root界别的权限时,这可能需要sudo权限。

构建系统

你的构建系统首选应该是Gradle,Ant构建系统有很多限制而且也很啰嗦。而Gradle的简单之处在于:

  • 为你的APP构建不同的风格或变化。
  • 创建简单的类脚本任务 。
  • 管理和下载依赖库
  • 自定义keystore
  • 其他好处

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

主要的不同之处在于新的项目结构明确分离了‘source sets’(main,androidTest),这是一个来自Gradle的概念。比如,你可以添加资源配置‘paid’和‘free’到src中,添加后你的app将会有支付和免费风味的资源代码。

有一个顶层级别app目录用于区分你的app和其他被你的APP引用的library项目(比如上面的library-foobar)。setting.gradle会对这些library项目保持引用,并使app/build.gradle可以引用这些library。

Gradle配置

一般结构:详看Google’s guide on Gradle for Android

小的任务,替代shell, Python, Perl等等这些脚本,你可以在Gradle里完成任务。详见Gradle’s documentation

密码:在你的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 dependency解析来替代导入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这种静态版本可以帮助我们创建一个更加稳定、可预见和可重复的开发环境。

为non-release builds使用不同的包名。使用applicationIdSuffix调试构建类型,它能够在同一台设备上安装debug和release版本的apk(如果你需要,对于自定义构建类型也可以这么做)。在APP上线后,这将做对APP的生命周期也是特别有用的。

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

        release {
            // ...
        }
    }
}

使用不同的图标来区分装在同一台设备上的不同构建方式的APP——比如对图标使用不同的颜色或者在图标上覆盖一个标签。Gradle让这一切变得简单。在默认的项目结构下,将debug的图标放在app/src/bebug/res目录下而发布版本图标放在app/src/release/res目录下。你也可以根据构建类型改变APP的命名以及版本号。

IDE和文本编辑器

无论使用什么编辑器,项目结构必须美观。用什么编辑器是个人喜好,根据项目结构和构建系统找到适合你的编辑器是你的责任。

当下最值得推荐的IDE是Android Studio,因为这是Google开发的、与Gradle紧密合作的、默认使用了新的项目结构的、趋于稳定的并且是为Android开发量身定制的IDE。

使用Eclipse ADT作为Android开发不再是个好的选择了。因为Google在2015年末已经停止了ADT的支持并呼吁用户尽早转移到Android Studio上来。你仍可以使用Eclipse,但是由于它的使用老的项目结构并使用Ant来构建,你需要配置它后才能用Gradle工作。如果配置失败则需要使用命令行来build。

你甚至可以用一个像Vim、Sublime Text或者Emacs这类普通的文本编辑器。那样,你将需要在命令行中使用Gradle和adb。

无论你用什么,Gradle和新的项目结构仍然是构建应用的官方方法。避免添加你的特殊编辑配置文件到版本控制系统。例如,不要添加一个Ant的build。xml文件.尤其不要忘记保持build.gradle的更新和功能如果你在Ant中修改了配置。优待其他开发者,别强迫他们去改变他们更喜欢的工具。

Libraries

Jackson是一个能将JSON与对象相互转换的Java库。对于解决这个问题,Gson是一个普遍的选择。然而我们发现Jackson有更好的性能自从它支持了可供选择的处理JSON方式:流、树形内存模型和传统的JSON-POJO数据绑定。记住上面所说的,尽管Jackson比Gson更大,在你的项目中你或许也喜欢用Gson来避免65k的方法限制。其他解析JSON的替代库:Json-smart和Boon JSON

网络、缓存和图片。有许多向后端服务器发送请求的交互解决方案,你应该考虑使用哪种方式实现你的连接。使用Volley或者Retrofit。Volley还能帮你加载和缓存图片。假如你选择Retrofit,考虑使用Picasso来加载和缓存图片,并使用OkHttp来处理HTTP请求。所有这三个Retrofit、Picasso和OkHttp都是一家公司写的,所以它们能很好的相互配合。OkHttp也能被用在Volley的网络连接上。

RxJava是相应式编程的库,或者说是处理异步线程的库。它是一个强大而有前景的库。由于它很难掌握所以他经常会变得很混乱。我们建议在使用这个库来设计整个应用之前需要谨慎行事。我们有许多用RxJava做完的项目,如果你需要帮助和这些人中的一个聊聊吧:Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen.我们已经写了一个博客贴在这里。[1],[2],[3],[4].

如果你之前对Rx毫无经验,从API响应开始使用它,或者从简单的UI事件处理开始使用它:比如在一个搜索区域点击事件或者输入事件。如果你对的Rx技术有足够自信并想要在整个项目结构中都使用它,然后在所有棘手的部分都用Java代码来写。记得其他不熟悉RxJava的程序员可能在在做项目时变得十分困难。尽力帮助他们理解你的代码并一起用Rx工作。

Retrolambda是一个让Android和其他JDK8之前的插件使用Lambda表达式语法的Java库。他让你的代码保持高严密性和高可读性。如果你对一个使用了RxJava的例子使用了一个功能性的样式,使用这个库,安装JDK8,并在Android Studio的Project Structure对话框中设置JDK8作为你的SDK地址,并设置JAVA8_HOME和JAVA7_HOME环境变量。然后在你项目的根目录的build.gradle中添加:

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

然后在另一个module的build.grald中添加:

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方法限制,避免使用太多library库。当Android应用被打包成一个dex文件,有一个65536个引用方法的硬性限制。如果你超过了这个限制你会看到一个致命的错误。因此,使用最少量的library并使用dex-method-counts工具来判断哪个library是在这个限制下可用的。尤其不要使用Guava库,因为它包含了13000个方法。

Activity和Fragment

开发社区和Futurice的开发人员都没有对如何用Activity和Fragment构建出最佳的Android结构达成共识。Square甚至还有一个大多用View搭建应用结构的库,绕过了对Fragment的需求,但这仍然不是一个开发社区中广泛使用和值得推荐的做法。

由于Android API的历史,你可以认为Fragment是屏幕中的一个UI模块。话句话说,Fragment通常与UI有关。Activity可以被认为是控制器。由于Activity和Fragment的生命周期和状态管理让他们变得尤为重要。然而,你也许会看到他们角色的变化:Activity可能被当做UI界面(提供屏幕间的转换)而Fragment可能被当做控制器。我们建议谨慎行事并做出明智的决定因为选择只使用Activity、只使用Fragment或者只使用View的界面结构都会有所缺陷。所以应该同时使用这些UI界面。这里有一些需要注意的建议:

  • 避免使用嵌套Fragment因为这会引起matryoshka bugs,只有当嵌套Fragment是有意义的(比如在当做屏幕的Fragment中有个水平滑动的带有Fragment的Viewpager)或者当嵌套Fragment是一个好主意时才使用它。
  • 避免在Activity中有太多代码。如果可以,保持他们主要作用是作为一个轻量级的容器、用于控制Activity在应用中的生命周期或实现其他重要的Android 接口API。最好用单个Fragment的Activity代替单个Activity——将UI代码写在Fragment中。这使得Activity变得可复用的。这使得我们可以改变Activity在一个标签式布局或者多个Fragment模块布局中。不要写一个没有响应Fragment的Activity,除非你觉得这是一个明智的决定。
  • 不要滥用Android-level API比如在你的app的内部工作中过于依赖Intent。你可能会影响到Android系统或者其他应用出现一些bug或卡顿现象。例如,假如你的app使用intent来做package之间的交互,如果你的app是开启启动的,你可能会在用户体验中引起几秒钟的卡顿。

Java packages architecture

Java结构对于Android应用类似于Model-View-Controller。在Android中,Fragment和Activity实际上都是controller类。另一方面说,它们也是用户界面的一部分,因此,它们也是views。

所以很难区分Fragment(或者Activity)到底是controller还是views。最好将Fragment放在它们的fragments包中,Activity可以放在顶层包目录中就像我们建议的那样。如果你打算创建超过2到3个Activity,也可以将这些Activity放在一个activities包中。
否则,这个结构可能看着像一个MVC结构,创建一个models包,它包含有通过API请求获得、通过JSON解析器计算出的数据集合。然后创建一个views包,它包含有你的自定义View、Notification、action bar views、widget等。Adapter是一个特例,包含data数据和view界面。然而,它普遍需要通过getView()方法来导出一些view。所以你可以将adapters放在views包的子目录中。

有些Controller类会在整个应用中使用或者与Android系统紧密相连。这些类可以放在managers包中。其他杂七杂八的数据处理类,如“DataUtils”,放在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

组织好layout.xml,如果你不确定如何定义layout.xml的格式,下面的规则可能有所帮助。

  • 每行一个属性,用4个空格缩进。
  • android:id总是放在第一个属性
  • android:layout_****属性放在上方
  • style属性放在下面
  • 结束标记/>单独一行放置便于点击和添加属性。
  • 相比android:text中写内容,考虑对Android Studio尽可能使用layout-designtime-attrributes(就是@string/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_****应该在layout XML中定义。然而其他属性android:****应该放在style XML中。也有特例,但是在一般工作中适用。这个建议就是保持只有layout属性(positioniong,margin,sizing)和content属性写在layout文件中,而保持所有的界面细节(colors,padding,font)在style文件中。

特例有以下几点:

  • android:id明显应该放在layout文件中
  • 对于LinearLayoutandroid:orientation一般放在layout文件中
  • android:text应该放在layout文件中因为它要定义文本内容。
  • 有时候创建一个通用的style定义android:layout_widthandroid:layout_height是有意义的,但是他们默认还是应该出现在layout文件中。

使用Style样式。几乎每个项目都会适当的使用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文件分割一个大的style文件。你不必只有一个style.xml文件。Android SDK支持其他外部文件,文件名style没有什么神奇的地方,只要是在文件中有<style>这个XML标签的都可以。因此你可以有各种style文件styles.xml,styles_home.xml,styles_item_details.xml。不想有些对构建系统有特殊意义的resource文件夹,在res/values中的文件名是可以随意命名。

colors.xml是一块调色板。在你的color.xml中应该只有一组由颜色名字命名的RGBA值而没有其他的。不要在color.xml中为不同类型的按钮定义默认的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 style中,而不是在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审美,有必要减少使用的颜色种类。

像color.xml一样对待dimens.xml。你需要定义间距和字体大小的”调色板”,定义的原则基本上和color是一样的,下面就是一个好的dimens的例子:

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

你应该在布局时对margin和padding使用spacing_****命名的通用尺寸,而不要像对待strings那样逐个将dimen值敲进去,这使得dimen更具一致性,而且使dimen易于组织和改变样式和布局。

strings.xml

用类似namespaces的关键字类命名你的string,别怕有一个两个重复的关键字。预言师复杂的,所以namespaces必须联系上下文。

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>

不要用全大写来写string值,坚持一种文本写法(比如首字母大写)。如果你需要string显示为全大写,使用TextView的textAllCaps属性。

Bad

<string name="error_message_call">CALL FAILED</string>

Good

<string name="error_message_call">Call failed</string>

避免深入的View结构。有时你或许会为了安排一个界面添加一个新的LinearLayout。这种情况可能发生如下:

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

    <RelativeLayout
        ...
        >

        <LinearLayout
            ...
            >

            <LinearLayout
                ...
                >

                <LinearLayout
                    ...
                    >
                </LinearLayout>

            </LinearLayout>

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值