最佳实践(1):安卓开发

这篇文章主要为Futurice公司Android开发者总结的经验教训。遵循这些规范可以避免无谓的重复劳动。如果对iOS或Windows Phone平台的开发感兴趣,请查看《iOS开发最佳实践》和《Windows客户端最佳实践》。

欢迎反馈,但请先阅读反馈规范

摘要

  • 使用Gradle和Gradle默认的项目结构
  • 将密码和敏感数据放在gradle.properties中
  • 不要实现自己的HTTP客户端,使用Volley或者OkHttp库
  • 使用Jackson库解析JSON数据
  • 由于65K的方法空间限制,避免使用Guava并使用尽可能少的库
  • 用Fragment来显示UI
  • Activity只用来管理Fragment
  • XML也是代码,管理好XML代码
  • 使用样式来减少布局XML代码中重复属性
  • 将样式写在多个文件中,避免把样式全部写在单一的大文件当中
  • 保持colors.xml文件的简短干净,只定义调色板
  • 同样也保持dimens.xml简短干净,只定义通用的常量
  • 避免深层级的ViewGroup
  • 避免客户端处理WebView要显示的内容,并且注意内存泄露
  • 使用Robolectric进行单元测试,使用Robotium进行连接设备(UI)的测试
  • 使用Genymotion模拟器
  • 一直使用ProGuard或者DexGuard

Android SDK

Android SDK存放在home目录或者其他跟应用开发无关的位置。一些IDE在安装时包含了SDK,这时SDK可能存放在IDE的安装目录下。而这是很不好的做法,特别是当你需要升级(或者重新安装)或更换IDE时。同时也要避免把SDK存放在系统目录下,否则,当普通用户(不是root)使用IDE时就需要获取sudo权限。

编译系统

编译系统首选Gradle。相比于Gradle,Ant更加的局限并且更加繁琐。使用Gradle编译系统可以很简单的做到:

  • 将应用编译成不同的版本
  • 完成简单的类似脚本的任务
  • 管理和下载依赖
  • 自定义秘钥仓库
  • 其他…

Google正积极的开发安卓Gradle插件,作为新的标准编译系统。

项目结构

主要有两个主流的项目结构:旧的Ant项目结构和Eclipse ADT项目结构,较新的Gradle和Android Studio项目结构。当然选择新的项目结构。如果你的项目正在用旧的项目结构,考虑放弃旧的结构,转移到新的项目结构下吧。

旧项目结构:

 

新的项目结构:

新旧项目结构最大的不同点是新项目结构更加合理的分开了代码集(main, androidTest)。例如,你可以在代码集src文件夹下添加’paid’和’free’文件夹,分别用于存放付费版应用代码和免费版应用的代码。

顶层app文件夹用于将你的应用和其他库(例如:library-foobar)区分开来。Settings.gradle中保存了app/build.gradle需要用到的库的引用。

Gradle配置

普通项目结构。遵循Google安卓Gradle规范
简单任务。可以用Gradle完成一些简单任务,而不用特地去写(shell, Python, Perl等)脚本。具体参考Gradle文档
密码。你需要在build.gradle中配置应用发行版本的签名配置。以下这些情况是需要避免的:

不要这样做。也许你会在版本控制系统中这样做。

换一种方式,新建一个gradle.properties文件,文件内容如下。注意,不要把Gradle.properties添加到版本控制系统中。

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

Gradle会自动导入gradle.properties文件,所以你可以在build.gradle中这样写:

使用Maven管理项目依赖,而不是直接导入jar文件。如果你显式的导入jar文件到项目中,那这些依赖的jar文件只会是某个固定的版本,例如2.1.1。下载jar文件并管理更新这种方式笨拙不堪,而Maven完全解决了这个问题,并且,Maven可以集成在安卓Gradle编译系统中。你可以指定版本的范围,例如2.2.+,然后Maven就会自动更新到版本范围内的最新版本。例如:

IDE和文本编辑器

不管用什么编辑器,它都必须要能够很好的显示项目结构。编译器的选择看个人喜好,但是编辑器必须要能够显示项目结构和编译。

现在最为推荐的IDE时Android Studio,因为Android Studio由Google开发,最为接近Gradle,默认使用新的项目结构,也终于发布了beta版,可以说是为Android开发量身定做的IDE。

当然你也可以使用Eclipse ADT,但是需要重新配置,因为Ecplise ADT默认使用旧的项目结构和使用Ant编译。甚至,可以使用纯文本编辑器,比如Vim, Sublime Text, 或者Emacs。如果使用纯文本编辑器,就需要在命令行中使用Gradle和adb。如果Eclipse集成Gradle后仍旧不能工作,你可以选择在命令行中编译,或者迁移至Android Studio。

不管使用什么IDE和文本编辑器,确保使用Gradle和新的项目结构来编译应用程序,同时避免把编译器的配置文件添加到版本控制系统当中。例如,避免添加Ant的配置文件build.xml。还有需要强调的一点,如果你在Ant中更改了编译配置,不要忘记更新build.gradle,使其能够完成编译。另外,对其他的开发者友好一点,不要强迫他们去改变他们的工具的偏好设置。

Jackson是一个用于将对象转换成JSON或者将JSON转换成对象的Java库。为了解决JSON和对象相互转换的问题,Gson是一个受欢迎的选择。但是我们发现,自从Jackson支持多种JSON处理方式:流,内存中的树模型和传统的JSON-POJO数据绑定,Jackson更加高效。请记住,Jackson是一个比GSON大的库,所以请根据你自己的实际情况做出选择。考虑到65K的方法空间限制,你可能会偏向于选择GSON。其他选择:Json-smartBoon JSON

网络,缓存和图片。现在已经有许多经过实践证明的向后端服务器请求数据的解决方案。你应该考虑使用这些解决方案来实现自己的客户端。使用Volley或者Retrofit。Volley也提供了加载和缓存图片的帮助类。如果你选择Retrofit,考虑使用Picasso来加载和缓存图片,使用OkHttp来实现高效的HTTP请求。Retrofit,Picasso和OkHttp都由同一个公司实现,所以这三者契合的特别好。OkHttp也可以和Volley配套使用。

RxJava是一个用于响应式编程的库,也即是,处理异步事件的库。RxJava这个范例非常强大,而且前途光明。RxJava非常与众不同,因此使用RxJava时可能会令人迷惑。我们推荐在把RxJava部署到整个应用前先花一些时间了解RxJava。现在已经有一些项目是利用RxJava来完成的,如果你需要帮助,请向这些人询问:Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen。另外,我们也写了一些博客:[1], [2], [3], [4].

如果你没有使用Rx的经验,请从应用Rx的响应API开始。或者,从应用Rx的UI事件处理开始,比如点击事件或者在搜索框中的键盘事件。如果你对使用Rx很有信心,想要把Rx应用到整个应用程序当中,请在比较难处理、容易令人迷惑的部分写明Javadocs。记住,其他不熟悉RxJava的程序员维护项目时可能会非常困难。请尽力去帮助他去理解你的代码和Rx。

Retrolambda是一个在Android平台或者其他低于JDK8的平台上处理Lambda表达式语法的Java库。利用这个库,可以保持你的代码的整洁严谨并且具有可读性,特别是当你使用了函数式编程风格(functional style),例如使用了RxJava。使用前,先安装JDK8,在Android Studio项目结构对话框中将它设置为你的SDK路径,设置JAVA8_HOME和JAVA7_HOME环境变量,然后在项目根目录下build.gradle中增加以下内容:

然后在每一个模块下的build.gradle中,增加以下内容:

Android Studio支持对Java8的lambda智能提示。如果你是第一次使用lambda,从以下两条规则开始:

  • 所有只有一个方法的接口都是「lambda友好」的,能够被转换成更加整洁严谨的语法。
  • 如果你不确定参数或者其他信息,写一个普通的匿名内部类,然后让Android Studio将它转换成一个lamdba表达式。

请注意dex方法限制,避免使用过多的库。被打包成dex文件的安卓应用,都有一个硬性的限制:最多能有65536个方法引用[1] [2] [3]。如果你超出了这个限制,在编译的时候你就会看到一个严重的编译错误。因此,使用尽量少的库,并使用dex-method-counts工具来决定在保证不超出限制的前提下,有哪些库可以使用。特别要避免使用Guava库,因为它包含了超过13k个方法。

在Android应用开发中,首选Fragment来显示UI。Fragment是可重用的用户交互界面,并且可以将Fragment组合在一起。我们推荐使用Fragment来显示用户交互界面,而不是使用Activity。以下是一些理由:

  •  实现多视图布局。将手机应用扩展至平板的主要方法便是利用Fragment。利用Fragment,可以让视图A和B都显示在一个平板屏幕上,而在手机屏幕上,视图A和B都占一整块屏幕。如果你的应用从一开始就用Frament来实现,那么你很容易就能将你的应用适配到屏幕大小不同的设备上。
  • 屏与屏之间的通信。 安卓API并没有提供一个恰当的方法将复杂的数据(例如,一些Java对象)从一个Activity发送到另外一个Activity中。但是利用Fragment时,以activity实例为通信管道,可以实现该activity下的子fragment之间的通信。即使这种方法优于Activity之间的通信,你可能仍旧需要一个事件总线的架构,考虑使用Otto或者greenrobot EventBus
  • Fragment有更好的普适性,而不仅仅只是实现UI。你可以实现一个没有UI的fragment,作为activity后台运行的「工人」。你也可以将这个点子发挥的更淋漓尽致一点,比如创建一个fragment专门用于实现fragment的改变逻辑,而不是将这些逻辑写在activity中。
  • 甚至ActionBar也可以在fragment中管理。你可以创建一个没有UI的fragment,只用于管理ActionBar,或者在每一个当前可见的fragment中把自己需要的action项添加到父activity的ActionBar上。阅读更多内容

虽然我们建议使用fragment,但是我们不建议大量使用嵌套的fragment,因为可能会引起“套娃式bug”(matryoshka bugs)。只在合理的情况下(例如,水平滑动的ViewPager中的fragment嵌套在一个模拟屏幕的fragment中)或者经过深思熟虑时,才使用嵌套的fragment。

从架构层面来讲,你的应用应该有一个顶层的activity,其中activity中包含了大部分的业务相关的fragment。你也可以有其他的辅助activity,只要这些activity和主activity的通信足够简单,能够通过Intent.setData()或者Intent.setAction()或者其他简单的方式实现即可。

Java包结构

Android应用程序的Java包结构可以用基本上近似于模型-视图-控制器结构。对于Android,Fragment和Activity实际上就是控制类。同时,这两者也是用户交互界面的一部分,因此,这两者也是视图。

由于上述原因,将fragment(或者activity)严格的归类为控制器或者是视图是非常困难,不合理的。所以,更合理的做法是把fragment存放在专有的fragment包内。如果你遵循了前一部分的建议,那么可以将activity存放在最顶层的包下。如果你计划创建多于2个或3个activity,那么创建一个activities包。

否则(译者注:如果没有fragment和activity),包结构看起来就是一个典型的MVC结构。有一个models包,存放主要用于JSON解析时API返回值的POJO对象;一个views包,存放你自定义的视图,通知,action bar视图和小部件等。Adapter的归类比较模糊,是处于数据和视图之间的位置。但是,一般情况下,adapter需要在getView()函数中引入一些视图,所以可以在views包下建一个adapters包来存放adpater。

一些控制类是整个应用程序都需要使用到的,也更加接近安卓系统底层。这些控制类存放在managers包下。各种数据处理类,例如「DateUtils」,存放在utils包下。负责与后端服务器进行交互的类存放在network包下。

总之,按靠近后端服务器到靠近用户的顺序排列,包结构如下:

 资源

命名。遵循以类型作为前缀的习惯,像type_foo_bar.xml。例如:fragment_contact_details.xml,view_primary_button.xml,activity_main.xml。

管理好布局XML代码。如果你不确定如何按照一定的格式来管理XML,可以参考以下几个习惯:

  • 一个属性占单独的一行,缩进4个空格
  • android:id总是第一个属性
  • android:layout_****属性放在顶部
  • style属性放在底部
  • 标签关闭/>独占一行,便于调整属性的顺序和增加属性
  • 不要在android:text中硬编码字符串,考虑使用Android Studio中提供的Designtime attributes功能

最重要的规则是,在布局XML中定义android:layout_****属性,而其他的android:****属性则在样式XML中定义。这个规则有例外的情况,但是大部分情况下是适用的。这个规则保证只有layout属性(positioning, margin, sizing)和内容属性在布局文件中,其他的外观属性(colors, padding, font)则定义在样式文件中。

例外的情况有:

  • android:id显然应该在布局文件中定义
  • LinearLayout的android:orientation属性在布局文件中定义更为合理
  • android:text应该在布局文件中定义,因为它定义了特定的内容(译者注:属于内容属性)
  • 有时候创建通用的样式文件来定义android:layout_width和android:layout_height更加合理,但是一般情况下这两个属性应该在布局文件中定义。

使用样式。在项目中,重复的view的外观(译者注:重复的view属性)是很常见的,因此,基本上每个项目都需要恰当的使用样式。在一个应用程序中,至少应该有一个通用的文本内容的样式。例如:

应用到TextView当中如下:

你也可能需要给button按钮写一个通用的样式, 不过不要只停留在给文本内容和按钮写通用样式上。继续的深入应用这个思想,把View的相关的重复的属性写成通用的样式。

把大的样式文件分成多个小样式文件。你不一定非得只有一个styles.xml文件。Android SDK支持以非传统方式命名的样式文件。文件名styles并没有特别的作用,起作用的只是文件中的XML标签<style>。因此,一个项目中可以同时有这些样式文件styles.xml, styles_home.xml, styles_item_details.xml, styles_forms.xml。不像资源目录名那样在编译时有特殊意义,在res/values下的文件名是任意的。

colors.xml是颜色调色板。colors.xml中应该只包含一些颜色名字到RGBA颜色值的映射。不要在colors.xml中为不同的按钮定义不同的颜色。

不要像下面这样做:

如果你像上面这种形式来定义颜色,你很快便开始定义重复的RGBA颜色值。这种情况下,需要改变基础色值时,工作将会变得非常复杂。并且,这些颜色定义跟上下文有关,像”button”和”comment”这些,应该在按钮的样式文件中定义,而不是在colors.xml中定义。

你可以这样做:

像应用程序的设计者要这份颜色调色板。名字不一定非得是颜色的名字,例如”green”, “blue”等。像”brand_primary”, “brand_secondary”, “brand_negative”这中类型的名字也是完全可以接受的。以这种格式来管理颜色,在改变颜色值的时候会很方便,同时也可以很直观的看到使用了多少个不同的颜色。如果要展现一个漂亮的UI界面,减少颜色种类的使用是很重要的一点。

像管理colors.xml那样来管理dimens.xml。同样,你可以定义间隔,字体大小等属性的”调色板”,理由和管理颜色的理由一样。下面是dimens文件的一个好样例:

你应该使用(译者注:dimens文件中定义的)spacing_****尺寸来实现视图布局的margin和padding属性,而不是在布局文件中硬编码属性值,这一点很像字符串的一般处理方式。这会使应用保持一致的观感,同时在管理和更改样式和布局时也更加方便。

避免深层级视图。有时候,你想要在原有的视图xml中添加一个新的LinearLayout,以此来实现一个新的视图。那么,很有可能发生下面的情况:

即使你没有直接在一个布局文件中看到上述的情况,当你在把一个视图填充(在Java代码中)到另一个视图中时,上述的情况也有可能发生。

这可能引发一系列的问题。可能会有性能问题,因为在这种情况下,处理器需要处理非常复杂的UI树。另外一个更严重的错误是栈溢出错误

因此,尽可能的减少视图的层级:学习如何使用RelativeLayout,如何优化布局和如如何使用<merge>标签

谨慎处理与WebView相关的问题。当你必须显示一个网页时,例如一篇新闻,不要在客户端中处理HTML,更好的做法是向后端程序员请求”纯净”的HTML代码。当你把WebView绑定到activity上,而不是绑定到ApplicationContext上时,WebView也可能会泄露内存。不要使用WebView来展现简单文字或者按钮,用TextView和Button来实现。

 测试框架

Android SDK提供的测试框架仍旧不够完善,特别是UI测试。Android Gradle现在利用一个为安卓定制的JUnit帮助工具插件,实现了一个测试框架connectedAndroidTest来执行你创建的JUnit测试。也就是说,在进行测试时,你需要连接设备或者模拟器。请根据官方的测试指南[1] [2]来操作。

只用Robolectric来单元测试,不用于视图UI测试。为了保证开发速度,Robolectric这个测试框架致力于提供不连接设备时的测试,也即是适合于对模型和视图模型的单元测试。但是,在Robolectric的框架下测试UI是不准确,不完全的。在测试和动画,对话框相关的UI元素时,你可能会遇到一些问题。由此,你”坠入了深渊”(测试过程中看不到控制屏幕),这使测试变得非常复杂。

Robotium让写UI测试变得非常容易。在Robotium测试框架下测试UI,你不需要进行连接设备的测试,但是利用Robotium提供的大量的帮助工具,你可以非常方便的分析视图UI和控制屏幕。测试用例也非常简单,以下是一个例子:

模拟器

如果你以开发安卓应用为职业,那么买一个正版的Genymotion模拟器吧。相比于AVD模拟器,Genymotion模拟器具有更高的帧率。它提供了一些工具来演示你的应用,模拟网络连接质量,GPS定位等。当然,Genymotion也适合于进行连接设备的测试。(译者注:为了全面的测试)你需要买很多(但不是全部)不同的设备,因此花钱买一个正版的Genymotion模拟器会比买很多物理设备便宜很多。

注意:Genymotion模拟器不会实现所有的谷歌服务,例如Google Play商店和地图。如果你需要测试三星独有的API,那还是有必要买一个三星的设备。

Proguard配置

一般情况下,ProGuard用于缩减和混淆安卓项目的打包代码。

是否使用Proguard取决于你的项目配置。大部分情况下,当你编译一个发行版本的apk时,你需要配置gradle来运行ProGuard。

为了判断要保留哪些代码,忽略或者混淆哪些代码,你必须明确的指出一个或者多个代码入口。 这些代码入口一般为包含有main函数的类,Java小程序(applet),移动信息设备小程序(Midlet),activity等。你在SDK_HOME/tools/proguard/proguard-android.txt可以找到安卓框架提供的默认配置。每个项目在my-project/app/proguard-rules.pro中自定义的proguard规则,(译者注:执行proguard时)会被附加到默认配置上。

有一个跟ProGuard相关的常见问题,在应用程序启动时因为ClassNotFoundException或者NoSuchFieldException或者类似的异常而崩溃,即使你在编译命令行(例如,assmbleRelease)中成功的完成编译并且没有warning提示。不外乎以下两种情况:

  1. ProGuard认为一些类,枚举,方法,变量或者注解不需要,将其移除了。
  2. ProGuard混淆了类,枚举,或者变量,但是这些类可能被通过它原来的名字间接地调用了,例如,通过Java的反射机制调用。

查看app/build/outputs/proguard/release/usage.txt,看造成崩溃问题的对象是否被移除了。查看app/build/outputs/proguard/release/mapping.txt,看造成崩溃问题的对象是否被混淆了。

为了防止ProGuard剔除需要用到的类或者类成员,在你的proguard配置中添加一个keep项:

为了防止ProGuard混淆一些类或者类成员,添加一个keepnames项:

在这份ProGuard配置模板中有一些例子。在ProGuard文档中有更多的例子。

提示:把每一个发行版本的mapping.txt文件都保存下来。这样,当用户遇到一个bug,提交了一个混淆的调用栈时,便可以根据保存的mapping.txt来调试,找到问题所在。

DexGuard。如果你需要一个不错的工具来优化代码,特别是经过混淆的发行版代码,考虑使用DexGuard。DexGuard是有ProGuard团队做的一个商业软件。利用DexGuard,可以很容易的分割Dex文件,解决了65k方法空间限制的问题。

感谢

感谢Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton和其他Futurice开发者分享关于安卓开发的知识。

许可

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

3 收藏 3 评论

关于作者:Jeffery

简介还没来得及写 :) 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部