您的位置:首页 > 其它

【安卓开发】最佳实践之一:安卓开发篇

2015-04-03 14:41 316 查看
这篇文章主要为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提示。不外乎以下两种情况:

ProGuard认为一些类,枚举,方法,变量或者注解不需要,将其移除了。
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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: