Android最佳实践指南
Updated on 2016/1/6 修正了一些翻译段落
欢迎转载,但请保留译者链接: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 andWindows App Development Best Practices documents.
Summary 概要
使用Gradle和它推荐的项目结构
将密码和敏感数据放在gradle.properties中
不要自己写Http客户端,使用Volley或OkHttp
使用Jackson来解析JSON
由于65k方法数限制,避免使用Guava并维持数量较少的库引用
注:尽管如此,在开发复杂应用时基本都会面临这一问题,成熟解决方案则为dex分包
使用Fragment呈现UI
Activity仅用于管理Fragment
注:以上两条其实就是AUF(Always Use Fragment)原则,初期更多一点的代码量将在未来换取诸多好处
Layout XML同样也是代码,好好组织它们
使用style来避免Layout XML中的重复属性
使用多个style文件避免生成一个庞然大物
保持colors.xml简短并谨记DRY,只在其中定义基础色彩
注:DRY,Don't repeat yourself;
同样保持dimens.xml DRY,仅定义一般常量
不要制造过深的ViewGroup层级
注:Android采用的是深度优先遍历
避免WebView的客户端侧处理,并知晓它可能导致内存泄漏
使用Robolectric做单元测试,Robotium做UI测试
模拟器使用Genymotion
注:似乎随着新模拟器的进步,这一点变得不是太重要
总是使用 ProGuard 或 DexGuard
简单的数据持久化使用SharedPreferences,其他的使用ContentProvider
注:译者写了一个简化SharedPreferences使用、支持文件配置的工具LitePreferences
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
会创建更稳定的、可预期的和可重复的开发环境。
IDEs and text editors IDE和文本编辑器
无论使用什么编辑器,它必须能针对项目结构让人愉快地使用
文本编辑器是一个很个人的选择,依据项目结构和构建系统来让编辑器起到作用同时也是你的责任。
目前最推荐的IDE是 Android Studio,因为它由Google开发,与Gradle关系最紧密,默认使用新式项目结构,针对Android开发量身定做。
如果你愿意你能使用 Eclipse ADT ,但是需要一番配置,因为它采用旧式项目结构与Ant构建。如果 Eclipse 的 Gradle 集成令你使用得不愉快,你的选择只有使用命令行来构建。最好的决定还是向 Android Studio 迁徙,因为 ADT plugin 已遭废弃。
你也能只使用一个单纯的文本编辑器像是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_HOME
和 JAVA7_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支持.
<?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 (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_width
和android:layout_height
常量会很有用,但默认情况下它们应该出现在 layout files 中
使用 styles. 几乎每一个项目都需要适当地使用 style,因为对于 view 来说有着重复的外观是非常常见的事,看下面这个例子:
<style name="ContentText">
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:textColor">@color/basic_black</item>
</style>
该 style 被用于 TextViews:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/price"
style="@style/ContentText"
/>
你很可能需要为buttons做一些相同的事,不要在这里停下。从宏观角度上提炼出一组相关联的、重复的android:****
属性到一个公共的 style 中去。
把一个大的 style 文件分割成多个
你无须拘泥于单个 styles.xml
文件。 Android SDK 支持其他不符合这一命名规则的文件,关于文件名 styles
什么魔法也没有,起效果的是文件中的 XML tags <style>
。因此你能拥有这样命名的style文件 styles.xml
, styles_home.xml
, styles_item_details.xml
, styles_forms.xml
。 不像 resource 目录那样命名对构建系统具有意义, res/values
目录下的文件命名完全可以随意。
注:是的,你可以在strings.xml中放color资源,ResourceManager通过映射可以找到它。
colors.xml
是一个颜色调色板 你的colors.xml
中不要放其他事物,只需要映射颜色名到一个RGBA值。不要为不同类型的buttons定义RGBA值。
Don't do this:
<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" or "comment",它们应该放置于一个button style 中,而非colors.xml
。
Instead, do this:
<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", etc.这样的命名也是完全可以接受的:"brand_primary", "brand_secondary", "brand_negative"。像这样格式化颜色会让改变和重定义颜色变得容易,还能让人看出一共有多少种不同的颜色被使用。通常对漂亮的UI设计来说,减少所使用颜色的多样性是一件重要的事。
好好对待 dimens.xml ,正如对待 colors.xml.你也应该定义典型的间距和字号大小的“调色板”,像对于色彩的基本意图那样。一个好的 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>
像通常对待strings那样,你应该使用spacing_****
dimensions 来设置 layouting, margins 和 paddings,而不是使用硬编码值。这会带来一致的观感,同时让组织和改变styles及layouts变得简单。
strings.xml
使用类似的命名空间来命名你的strings的keys,不要害怕在两个或多个keys中重复某一个值。语言是很复杂的,所以命名空间是有必要的,它能用于提供上下文信息还有打破模糊。
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值。遵循一般的文本惯例(e.g., capitalize first character)。如果你需要将整句string大写显示,那么针对实例使用TextView中的这个属性textAllCaps
。
Bad
<string name="error.message.call">CALL FAILED</string>
Good
<string name="error.message.call">Call failed</string>
避免深层级的 views. 有时候你只是想要再加一个LinearLayout,用于完成一些views的布置。但这种情况却可能会发生:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<RelativeLayout
...
>
<LinearLayout
...
>
<LinearLayout
...
>
<LinearLayout
...
>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
尽管你没有在layout文件中直接目击到这样的景象,但这最终有可能发生,如果你填充(in Java) views到其他views中。
一些问题可能会发生。你也许遇见过性能问题,因为这样会生成一棵复杂的UI树来让处理器解析。另一个更严重的问题则是可能带来栈溢出错误: StackOverflowError.
因此,试着让你的views层级尽可能的扁平:学习如何使用RelativeLayout, 如何优化你的布局 optimize your layouts 还有如何使用 <merge>
tag.
清楚与 WebView 相关的问题 当你必须要显示一个web页面的时候,比如说一篇文章,避免客户端侧的对于HTML的清理处理,更好的方式是从后端程序中直接获取一段 "纯粹的" HTML 。当持有Activity的引用而非ApplicationContext时,WebView还可能导致内存泄漏WebViews can also leak memory when they keep a reference to their Activity, instead of being bound to the ApplicationContext。避免用 WebView 来做一些简单的文本或按钮, 更好的选择是 TextViews 或 Buttons。
Test frameworks 测试框架
Android SDK's testing framework尚不完善,特别是有关于UI 测试。Android Gradle实现了一个命名为connectedAndroidTest
的测试任务,它能运行你创造的JUnit test,参考extension of JUnit with helpers for Android.这意味着你需要连接实机或模拟器来运行测试,参考官方测试的指南[1] [2]
使用 Robolectric 作为 unit tests, 而不要做 views tests 这是一个致力于提高开发速度的无须连接设备的测试框架,特别适用于针对models 和 view models的单元测试。然而,Robolectric对于UI tests是不完全并且错误的。在测试以下相关UI元素时会有问题:animations, dialogs, etc,而且当你“行走于黑暗中”(没法看到屏幕正被控制着操作)时,实际情况究竟怎样也是非常难以理解的。
Robotium 让写 UI tests 变得简单 你可能不需要用Robotium来连接实机跑UI case,但你仍会通过它获取好处,因为它提供了许多helpers用于获取及分析views以及控制屏幕。Test cases 将看起来很简单像是这样:
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"));
Emulators 模拟器
如果你是专业的Android apps开发者,买一个专业版 Genymotion emulator吧。Genymotion比原生模拟器运行起来有着更高的帧速。它拥有一些工具用于调试你的应用,像是模拟网络连接质量,GPS位置等。用于连接着进行UI test它也很理想。你还能获取许多(不是全部)不同的虚拟设备,与购买许多实机相比Genymotion 专业版的花费实在是十分便宜。
警告:Genymotion emulators不支持所有的Google服务像是Google Play Store and Maps.要是你想要测试三星特征的APIs,仍旧有必要拥有一台三星实机。
Proguard configuration Proguard配置
ProGuard 常作为Android项目中缩减体积、混淆代码的工具。
是否使用ProGuard取决于你的项目配置。通常你可以在gradle中像这样配置以在构建正式apk时使用ProGuard。
buildTypes {
debug {
minifyEnabled false
}
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
为了决定某些代码是需要保持原样还是丢弃(注:即未实际使用,不打包,这也就是为什么使用ProGuard会减少应用体积)还是混淆,你需要在代码中指出一个或多个关键点。这些关键点区分出这些典型的类:with main methods, applets, midlets, activities, etc.
Android framework使用的默认混淆配置能在这里找到:SDK_HOME/tools/proguard/proguard-android.txt
,使用这个配置,再加上你自己在这里定义的项目限定的配置:my-project/app/proguard-rules.pro
,将会共同构成最终的ProGuard混淆规则。
使用ProGuard经常遇见的一个问题是应用启动时闪退,错误信息则为ClassNotFoundException
or NoSuchFieldException
or similar,尽管运行构建命令成功 (i.e. assembleRelease
) 且无警告。
这意味着以下一到两件事:
- ProGuard移除了类、枚举、方法、域或注解,检查一下哪些是不需要移除的部分。
- ProGuard混淆(重命名)了类、枚举、域,但这些都在代码中被直接使用了它们原本的命名, i.e. through Java reflection.
检查app/build/outputs/proguard/release/usage.txt
看是否有存疑对象被移除掉了。
检查 app/build/outputs/proguard/release/mapping.txt
看是否有存疑对象被混淆了。
防止ProGuard丢弃一些需要的类或类成员,在你的ProGuard配置中加入keep
options:
-keep class com.futurice.project.MyClass { *; }
防止ProGuard混淆一些需要的类或类成员,添加keepnames
:
-keepnames class com.futurice.project.MyClass { *; }
Check this template's ProGuard config for some examples.
Read more at Proguard for examples.
尽早地在你的项目中提供一个正式版本构建 用来检查ProGuard是否执行正确符合预期是十分重要的事。当你引用了新库的时候,记得构建一个正式 版本在实机上测试一下。不要等到你的应用要发布"1.0"版本了再来构建正式版本,你可能会遭遇一些令人不愉快的惊喜,而你没有剩下的时间去修正它们。
建议 保存好每一个你发布给你用户的正式版本的mapping.txt
file 。通过持有这些文件,你才能够debug一些问题,当你的用户遇见bug并提交了一份带有混淆的stack trace.
DexGuard. 如要你需要一个 hard-core tools 来优化并混淆正式版本代码, 可以考虑DexGuard, 制作 ProGuard 的团队推出的商用软件. 它还能轻松地分割 Dex files 以解决65k方法数限制.
Data storage 数据存储
SharedPreferences
如果你只需持久化简单的标志位并且你的应用只在单进程环境下运行。SharedPreferences对你来说很可能已经足够了。它是不错的默认选项。
这儿有两个原因会让你不想要使用SharedPreferences:
- 性能: 你拥有大量数据或者数据本身非常复杂
- 多进程获取数据: 你拥有运行在各自进程中的组件或是远程服务,它们需要同步数据
ContentProviders
在SharedPreferences不够满足你的需求的情况下,你应该使用作为平台标准的ContentProvider,它快速并且进程安全。
关于ContentProvider的问题则在于你在使用它之前需要写大量的样板似的代码,还有就是低质量的学习指南。如果可能的话,使用自动库来生成ContentProvider,这样会显著减少劳力。such as Schematic.
你仍然需要靠你自己来写一些解析代码用于从Sqlite列中读取出Object数据,反之亦然。你可以序列化数据对象,像是使用Gson,并且只持有结果字串。通过这种方式你会损失一些性能,但另一方面你将不需要为数据类中的每一个域都声明列。
Using an ORM
我们通常不推荐使用对象关系映射库Object-Relation Mapping library,除非你有着不寻常的复杂数据和迫切的需要。它们趋向复杂并需要时间去学习。如果你决定使用ORM了,要是你的应用对 进程安全 process safe 有需求的话就要注意所使用的库是否支持 这一特性,许多现存的ORM解决方案令人惊讶地不支持。
Thanks to
Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton and other Futurice developers for sharing their knowledge on Android development.
License
Futurice Oy
Creative Commons Attribution 4.0 International (CC BY 4.0)