本文是Futurice公司的Android开发人员总结的最佳实践,遵循这些准则能够避免重复制造轮子。若是你对iOS或者Windows Phone开发感兴趣,那么也请看看iOS最佳实践和Windows客户端开发最佳实践。java
将你的Android SDK放在你的home目录或其余应用程序无关的位置。 当安装有些包含SDK的IDE的时候,可能会将SDK放在IDE同一目录下,当你须要升级(或从新安装)IDE或更换的IDE时,会很是麻烦。 此外,若果你的IDE是在普通用户,不是在root下运行,还要避免吧SDK放到一下须要sudo权限的系统级别目录下。android
你的默认编译环境应该是Gradle. Ant 有不少限制,也很冗余。使用Gradle,完成如下工做很方便:web
同时,Android Gradle插件做为新标准的构建系统正在被Google积极的开发。shell
有两种流行的结构:老的Ant & Eclipse ADT 工程结构,和新的Gradle & Android Studio 工程结构, 你应该选择新的工程结构,若是你的工程还在使用老的结构,考虑放弃吧,将工程移植到新的结构。编程
老的结构后端
新的结构缓存
主要的区别在于:服务器
经常使用结构 参考网络
Google's guide on Gradle for Android架构
小任务 除了(shell, Python, Perl, etc)这些脚本语言,你也可使用Gradle 制做任务。 更多信息请参考Gradle's documentation。
密码 在作版本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.+'}
Jackson 是一个将java对象转换成JSON与JSON转化java类的类库。Gson 是解决这个问题的流行方案,然而咱们发现Jackson更高效,由于它支持替代的方法处理JSON:流、内存树模型,和传统JSON-POJO数据绑定。不过,请记住, Jsonkson库比起GSON更大,因此根据你的状况选择,你可能选择GSON来避免APP 65k个方法限制。其它选择: Json-smart and Boon JSON
网络请求,缓存,图片 执行请求后端服务器,有几种交互的解决方案,你应该考虑实现你本身的网络客户端。使用Volley 或Retrofit。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 Studio工程结构对话框中把它设置成为SDK路径,同时设置JAVA8_HOME和JAVA7_HOME环境变量, 而后在工程根目录下配置 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不熟悉,只需参照如下开始学习吧:
Fragments应该做为你实现UI界面默认选择。你能够重复使用Fragments用户接口来 组合成你的应用。咱们强烈推荐使用Fragments而不是activity来呈现UI界面,理由以下:
Fragments 的引入主要将手机应用延伸到平板电脑,因此在平板电脑上你可能有A、B两个窗格,可是在手机应用上A、B可能分别充满 整个屏幕。若是你的应用在最初就使用了fragments,那么之后将你的应用适配到其余不一样尺寸屏幕就会很是简单。
从一个Activity发送复杂数据(例如Java对象)到另一个Activity,Android的API并无提供合适的方法。不过使用Fragment,你可使用 一个activity实例做为这个activity子fragments的通讯通道。即便这样比Activity与Activity间的通讯好,你也想考虑使用Event Bus架构,使用如 Otto 或者 greenrobot EventBus做为更简洁的实现。 若是你但愿避免添加另一个类库,RxJava一样能够实现一个Event Bus。
你能够有一个没有界面的fragment做为Activity提供后台工做。 进一步你可使用这个特性来建立一个fragment 包含改变其它fragment的逻辑 而不是把这个逻辑放在activity中。
你能够选择使用一个没有UI界面的fragment来专门管理ActionBar,或者你能够选择使用在每一个Fragment中 添加它本身的action 来做为父Activity的ActionBar.参考.
很不幸,咱们不建议普遍的使用嵌套的fragments,由于 有时会引发matryoshka bugs。咱们只有当它有意义(例如,在水平滑动的ViewPager在 像屏幕同样fragment中)或者他的确是一个明智的选择的时候才普遍的使用fragment。在一个架构级别,你的APP应该有一个顶级的activity来包含绝大部分业务相关的fragment。你也可能还有一些辅助的activity ,这些辅助的activity与主activity 通讯很简单限制在这两种方法 Intent.setData() 或 Intent.setAction()或相似的方法。
com.futurice.project ├─ network ├─ models ├─ managers ├─ utils ├─ fragments └─ views ├─ adapters ├─ actionbar ├─ widgets └─ notifications
Designtime attributes 设计时布局属性,Android Studio已经提供支持,而不是硬编码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文件中。
例外有如下这些:
使用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" />
你或许须要为按钮控件作一样的事情,不要中止在那里。将一组相关的和重复android:**的属性放到一个通用的style中。
将一个大的style文件分割成多个文件 你能够有多个styles.xml 文件。Android SDK支持其它文件,styles这个文件名称并无做用,起做用的是在文件 里xml的标签。所以你能够有多个style文件styles.xml,style_home.xml,style_item_details.xml,styles_forms.xml。 不用于资源文件路径须要为系统构建起的有意义,在res/values
目录下的文件能够任意命名。
colors.xml是一个调色板 在你的colors.xml文件中应该只是映射颜色的名称一个RGBA值,而没有其它的。不要使用它为不一样的按钮来定义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, 应该放到一个按钮风格中,而不是在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" 这样的名字也是彻底能够接受的。 像这样规范的颜色很容易修改或重构,会使应用一共使用了多少种不一样的颜色变得很是清晰。 一般一个具备审美价值的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字符串同样直接写值。 这样写会很是有感受,会使组织和改变风格或布局是很是容易。
避免深层次的视图结构 有时候为了摆放一个视图,你可能尝试添加另外一个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 你的布局 和如何使用 `` 标签.
当心关于WebViews的问题. 若是你必须显示一个web视图, 好比说对于一个新闻文章,避免作客户端处理HTML的工做, 最好让后端工程师协助,让他返回一个 "纯" HTML。 WebViews 也能致使内存泄露 当保持引他们的Activity,而不是被绑定到ApplicationContext中的时候。 当使用简单的文字或按钮时,避免使用WebView,这时使用TextView或Buttons更好。
Android SDK的测试框架还处于初级阶段,特别是关于UI测试方面。Android Gradle 目前实现了一个叫connectedAndroidTest的测试, 它使用一个JUnit 为Android提供的扩展插件 extension of JUnit with helpers for Android.能够跑你生成的JUnit测试,
只当作单元测试时使用 Robolectric ,views 不用 它是一个最求提供"不链接设备的"为了加速开发的测试, 很是时候作 models 和 view models 的单元测试。 然而,使用Robolectric测试时不精确的,也不彻底对UI测试。 当你对有关动画的UI元素、对话框等,测试时会有问题, 这主要是由于你是在 “在黑暗中工做”(在没有可控的界面状况下测试)
Robotium 使写UI测试很是简单。 对于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-specific 混淆规则,如在my-project/app/proguard-rules.pro中定义, 会被添加到默认的配置中。
关于 ProGuard 一个广泛的问题,是看应用程序是否崩溃并报ClassNotFoundException 或者 NoSuchFieldException 或相似的异常, 即便编译是没有警告并运行成功。 这意味着如下两种可能:
ProGuard 已经移除了类,枚举,方法,成员变量或注解,考虑是不是必要的。
ProGuard 混淆了类,枚举,成员变量的名称,可是这些名字又被拿原始名称使用了,好比经过Java的反射。
检查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 也是有他们团队开发的。 它会很容易将Dex文件分割,来解决65K个方法限制问题。
文章不易,若是你们喜欢这篇文章,或者对你有帮助但愿你们多多点赞转发关注哦。文章会持续更新的。绝对干货!!!