- 文档
- 教程
- 移动开发
【免费下载链接】android-best-practices
Do's and Don'ts for Android development, by Futurice developers
本指南以 Futurice 团队多年 Android 实战沉淀的《android-best-practices》为骨架(对应仓库根目录 README.md 及其多语言译本,如 西语版),系统梳理从构建系统、依赖管理、代码与资源组织,到测试、调试、内存泄漏检测与发布加固的完整最佳实践链路。读完本文,你将掌握一套可直接落地的 Android 工程规范:如何用 Gradle 安全管理签名密钥、如何用规范的 XML 资源组织避免布局与样式的失控、如何规避 65k 方法数限制、如何正确配置 ProGuard 并排查混淆引发的崩溃,以及如何用 Stetho 与 LeakCanary 提升调试与内存治理效率。
一、总览:这些建议从何而来
这些实践并非纸上谈兵,而是来自 Futurice 公司一线 Android 开发者的真实经验教训,其目的在于"避免重复发明轮子"(Avoid reinventing the wheel)。文档同时面向开发流程的各个环节,其核心主张可以归纳为:
- 构建:默认使用 Gradle 及其推荐的项目结构,构建过程由 Gradle 文件定义而非 IDE 配置。
- 安全:密码与敏感数据一律放入不纳入版本控制的
gradle.properties。 - 依赖:优先使用成熟库(Jackson/OkHttp/Retrofit/Picasso/Glide),不自己造 HTTP 客户端轮子;同时警惕 65k 方法数限制,谨慎控制库的数量。
- 代码组织:精心规划 Activities 与 Fragments 的职责边界、Java 包结构与资源文件(layouts、styles、colors、dimens、strings)。
- 质量:用合适的测试框架、模拟器、ProGuard、Stetho、LeakCanary 保证发布质量与可维护性。
仓库以单一 Markdown 文档承载全部内容(README.md),并提供了包含 中文、法语、日语、韩语、葡萄牙语、俄语、西语、土耳其语、越南语 在内的多语言版本,且全文基于 Creative Commons Attribution 4.0 International (CC BY 4.0) 协议开放(见 LICENSE),方便团队将其内化为自己的工程规范。
二、开发环境:Android SDK 的安放位置
核心建议:把 Android SDK 放在home目录或其他与应用程序无关的独立位置。
有些 IDE 发行版在安装时会顺带把 SDK 放到 IDE 自身目录之下,这会在你升级或重装 IDE 时带来隐患——很可能连 SDK 一起被覆盖或删除,迫使你重新经历漫长而繁琐的下载过程。
同时,要避免把 SDK 放到需要sudo权限的系统级目录:如果你的 IDE 以普通用户身份运行而非 root,后续 SDK 组件的安装与更新会频繁遭遇权限问题。
三、构建系统:Gradle 与推荐的项目结构
3.1 为什么默认选择 Gradle
文档明确建议将 Gradle 作为默认构建系统,理由是它让以下工作变得简单:
- 创建应用的不同flavor(变体)
- 用脚本完成简单任务
- 管理与下载依赖
- 自定义 keystore
- 以及更多……
Ant 作为旧构建系统已于 2015 年废弃,Android 的 Gradle 插件改由 Google 亲自开发与维护。
关键原则:应用的构建过程必须由 Gradle 文件定义,而不是依赖 IDE 特定的配置。这样才能保证在不同工具链之间构建结果一致,并为持续集成(CI)系统提供良好支持。
3.2 采用 Gradle 的默认项目结构
尽管 Gradle 在项目结构上提供了很大灵活性,除非你有令人信服的理由,否则应当采用它的默认结构(基于 source set 的main、androidTest等目录划分),这会大幅简化构建脚本。仓库中 中文译本 对这种新旧结构的差异做了更直观的对照:旧式 Ant & Eclipse ADT 结构把assets、libs、res、src、AndroidManifest.xml平铺在根目录;而新式 Gradle 结构则引入顶层app模块(内部再分src/main、src/androidTest),并将第三方库项目(如library-foobar)独立为同级模块,通过根目录的settings.gradle引用。
3.3 Gradle 配置细节
通用结构:遵循 Google 官方的 Gradle for Android 指南。
小任务:与其写 shell、Python、Perl 等外部脚本,不如直接在 Gradle 中定义任务(Task),遵循 Gradle 官方文档即可。
密码与敏感数据(重点):在应用模块的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中可以直接引用,并配合try/catch在缺失时给出清晰提示:
signingConfigs { release { try { storeFile file("myapp.keystore") storePassword KEYSTORE_PASSWORD keyAlias "thekey" keyPassword KEY_PASSWORD } catch (ex) { throw new InvalidUserDataException("Deberías definir tu KEYSTORE_PASSWORD y KEY_PASSWORD en gradle.properties.") } } }提示:
gradle.properties位于项目根目录时对模块级build.gradle全局可见;务必把该文件加入.gitignore,并可为团队成员提供一份不含真实密码的模板。
优先使用 Maven 依赖解析而非导入 jar 文件:显式将 jar 放进项目意味着版本被"冻结"在特定版本上,下载与管理更新非常繁琐,而 Maven 依赖解析恰好解决了这个问题,这也是 Gradle 推荐的做法。例如:
dependencies { implementation 'com.squareup.okhttp:okhttp:2.2.0' implementation 'com.squareup.okhttp:okhttp-urlconnection:2.2.0' }避免动态依赖版本:不要使用2.1.+这类动态解析,它会导致构建不稳定、不同构建之间出现难以追踪的行为差异;使用2.1.1这样的静态版本有助于打造更稳定、可预测、可复现的开发环境。
为非 release 构建使用不同的包名:借助applicationIdSuffix让debug与release两个 APK 可以同时安装在同一台设备上(自定义构建类型若有需要也可照做),这在应用上架后尤其有价值:
android { buildTypes { debug { applicationIdSuffix '.debug' versionNameSuffix '-DEBUG' } release { // ... } } }配套实践:用不同图标区分设备上的构建版本(例如不同颜色或叠加 "debug" 字样)。借助默认项目结构,只需把 debug 图标放进app/src/debug/res、release 图标放进app/src/release/res,Gradle 会自动按构建类型选择资源。此外还可以按构建类型修改应用名与versionName。
3.4 IDE 与文本编辑器
原则:编辑器是个人选择,但必须让它适配项目结构与构建系统。
推荐 Android Studio:由 Google 开发并高频更新,对 Gradle 支持良好,内置大量实用的监测与分析工具,专为 Android 开发定制。
也可以使用 Vim、Sublime Text、Emacs 等纯文本编辑器,但此时必须自己用命令行 Gradle 与adb完成构建与调试。
Eclipse ADT 已不再是好的选择:Google 于 2015 年停止对 ADT 的支持,并建议用户尽快迁移到 Android Studio。
无论选择哪种工具,都不要把编辑器专属配置文件(如 Android Studio 的.iml文件)提交到版本控制——它们通常包含你本机特有的配置,无法在同事的机器上正常工作。最后,请善待团队其他开发者,不要强迫他们更换自己更顺手的工具。
四、依赖选型:库的取舍与 65k 方法数限制
4.1 JSON 解析:Jackson 为主,Gson 为备
Jackson 是 Java 领域的 JSON 序列化/反序列化库,支持三种 JSON 处理方式:流式(streaming)、内存树模型(in-memory tree model)与传统的JSON-POJO 数据绑定,API 覆盖面广且灵活。
Gson 同样是热门选择,但它比 Jackson 更小——在需要规避 65k 方法数限制的场景下可以考虑以 Gson 替代。其他备选还有 Json-smart 与 Boon JSON。
4.2 网络、缓存与图片:不要自己写 HTTP 客户端
请求后端服务器有几套久经实战考验的方案,应直接采用而非自行实现客户端:
- Volley 或 Retrofit:发起网络请求。Volley 还附带图片加载与缓存能力。
- 若选 Retrofit,建议配 Picasso 做图片加载与缓存、OkHttp 做更高效的 HTTP 请求。
- Retrofit、Picasso、OkHttp 出自同一家公司(Square),彼此配合良好、兼容性问题少见;OkHttp 也可以与 Volley 配合使用。
- Glide 是另一款图片加载/缓存方案:支持 GIF 动图与圆形图片,宣称性能优于 Picasso,但方法数也更多。
4.3 RxJava:强大的异步范式,谨慎落地
RxJava 是响应式编程库,用于处理异步事件。它是一个强大且前景广阔的范式,但也与传统思维方式差异巨大、容易让人困惑,文档明确建议:在使用它架构整个应用之前保持谨慎。
渐进式采用路径:
- 如果对 Rx 没有经验,先只把它用于后端 API 响应的处理;
- 随后再逐步应用到 UI 事件处理(如点击事件、搜索框输入事件);
- 若确信自己的 Rx 水平并想将其用于整个架构,务必为所有棘手部分编写 Javadocs——其他不熟悉 RxJava 的开发者可能很难维护这样的项目,请尽力帮助他们理解你的代码与 Rx。
补充:仓库英文版 README.md 还提到用 RxAndroid 提供 Android 线程支持、用 RxBinding 从既有 Android 组件轻松创建 Observable,并指出从 Android Studio 3.0 起 Java 8 lambda 支持已内建、不再需要 Retrolambda。
4.4 Retrolambda:在旧平台使用 Lambda 语法
Retrolambda 允许在 Android 及其他 JDK8 之前的平台上使用 Lambda 表达式语法,能让代码更紧凑易读,尤其适合函数式风格(如与 RxJava 搭配)。Android Studio 对 Java 8 lambda 提供代码辅助,新手可以这样入手:
- 任何只有一个方法的接口都是"lambda 友好"的,可以折叠成更紧凑的语法;
- 对参数等细节拿不准时,先写普通匿名内部类,再让 Android Studio 帮你折叠成 lambda。
4.5 警惕 dex 方法数限制,慎用库
Android 应用打包为 dex 文件后,存在65536 个引用方法的硬性上限,一旦超过就会编译失败。因此:
- 尽量使用最少数量的库;
- 用 dex-method-counts 工具统计各库的方法数,确定哪些库组合能让你保持在上限之下;
- 尤其避免使用 Guava,它包含约 13k 个方法,是方法数大户。
说明:英文版补充提到从 API 21 起 multidex 支持库不再是必需(README.md 的 Gradle 配置一节),但 65k 单 dex 限制的告诫依旧适用于老设备与旧 API 场景。
五、Activities 与 Fragments:谨慎驶过架构选择的暗礁
社区与 Futurice 内部对"如何用 Fragments/Activities 组织架构"并无统一共识。Square 甚至推出过主要基于 View 构建架构的 mortar 库来绕开 Fragments,但这并未被社区广泛认可为推荐实践。
受 Android API 历史影响,可以粗略地把Fragments 视为屏幕上的 UI 片段(通常与界面相关),把Activities 视为控制器(尤其对生命周期与状态管理重要)。不过实际中角色常有交叉:Activities 可能承担 UI 职责(如屏幕间转场),Fragments 也可能只作为控制器使用。文档的建议是:小心驶得万年船,在充分了解信息的前提下做决策,因为纯 Fragments、纯 Activities 或纯 Views 的架构各有弊端。需要警惕的要点:
- 尽量避免嵌套 Fragments:嵌套容易引发"套娃"式(matryoshka)bug,仅在有明确理由时使用(例如横向滑动的 ViewPager 内的 Fragment,位于类似屏幕的 Fragment 内部),或确属深思熟虑的决策。
- 避免在 Activities 中堆砌过多代码:尽量让 Activity 保持轻量容器角色,主要用于生命周期与 Android 系统 API 交互。优先使用"单 Fragment 的 Activity"而非裸 Activity——把 UI 代码放进 Activity 对应的 Fragment,这样将来要改为标签页布局或多 Fragment 平板界面时可以直接复用;除非有充分的决策理由,避免出现没有对应 Fragment 的 Activity。
- 不要滥用 Android API level:如果应用内部大量依赖 Intent 进行通信,可能影响 Android 系统或其他应用,造成错误或延迟。例如已知的问题是:若应用包间用 Intent 做内部通信,恰在系统刚启动后打开应用时,可能带来数秒的用户体验延迟。
六、Java 包结构:面向功能的组织方式
文档给出的思路是把 Android 的 Java 架构放到 MVC 框架下审视:Fragments/Activities 既算控制器、又因显式参与 UI 而算视图,因此难以严格归类。建议的做法是:
- Fragments 放进独立的
fragments包;Activities 数量不超过 2~3 个时留在顶层包,更多时则建activities包。 models包:存放供 JSON 解析器与 API 响应使用的 POJO。views包:存放所有 Views、通知、Action Bar Views、widgets 等;adapters作为数据与视图的中介结构,通常需要通过getView()导出 View,因此可放在views之下。managers包:全局性的、贴近 Android 系统的控制器类。utils包:数据处理类(如 "DateUtils")。network包:负责与后端交互的类。
整体上按"从离后端最近到离用户最近"排列:
com.futurice.project ├─ network ├─ models ├─ managers ├─ utils ├─ fragments └─ views ├─ adapters ├─ actionbar ├─ widgets └─ notifications说明:这是该文档(西语版)所呈现的按层分包方案;仓库英文版 README.md 已演进为更推荐的面向功能(feature based)分包,并列举了依赖边界清晰、利于封装、便于按功能移除代码、易于过渡到模块化构建等收益。团队可根据项目阶段选择适合的方案。
七、资源组织:布局、样式、颜色、尺寸与字符串
7.1 资源命名规范
遵循type_foo_bar.xml的命名模式(类型前缀 + 功能 + 具体对象),例如:
fragment_contact_details.xmlview_primary_button.xmlactivity_main.xml
7.2 布局 XML 的编排规范
如果不确定布局文件如何排版,可遵循以下约定:
- 每行一个属性,缩进 4 个空格;
- 始终把
android:id放在第一个属性; android:layout_****类属性置于顶部;style属性放在最后;- 自闭合标签
/>独占一行,便于排序与追加属性; - 不要硬编码
android:text,考虑使用 Android Studio 提供的 Designtime 属性(设计期预览专用)。
示例:
<?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_****属性(定位、外边距、尺寸等布局属性)应留在布局 XML 中,而其余android:****外观属性(颜色、内边距、字体等)应放进样式文件。存在以下例外:
android:id显然应留在布局文件;LinearLayout的android:orientation通常放在布局文件中更合理;android:text定义内容,应放在布局文件;- 有时把通用的
android:layout_width/android:layout_height抽成样式是合理的,但默认仍应写在布局文件里。
7.3 使用样式(styles)消除重复属性
几乎所有项目都需要恰当使用样式,因为 View 的外观复用极为常见。至少应为应用中的大部分文本定义一个通用样式,例如:
<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:****属性迁移到公共样式。
7.4 拆分大样式文件
没有必要只用一个styles.xml。Android SDK 原生支持任意文件名,styles这个名字并无特殊魔法,真正起作用的是文件内部的<style>XML 标签。因此你可以拥有styles.xml、styles_home.xml、styles_item_details.xml、styles_forms.xml等多个文件。与资源目录名(对构建系统有意义)不同,res/values下的文件名可以是任意的。
7.5colors.xml只做一件事:定义调色板
colors.xml中除了"颜色名 → RGBA 值"的映射外,不应再有任何别的内容。这样既能避免 RGBA 值被反复书写,也让"应用到底用了多少种颜色"一目了然,方便日后变更与重构;对美观的 UI 而言,收敛颜色种类本身就很重要。
不要这样定义(把业务语义揉进颜色名):
<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"这类带上下文的定义本应放进按钮/注释的样式文件,而不是colors.xml。
应该这样定义(纯调色板):
<resources> <!-- Escala de grises(灰度) --> <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> <!-- Colores básicos(基础色) --> <color name="green">#27D34D</color> <color name="blue">#2A91BD</color> <color name="orange">#FF9D2F</color> <color name="red">#FF432F</color> </resources>可以请应用的设计师来定这套调色板;命名不必是"green""blue"这类直白颜色名,color_primario(主色)、color_secundario(次色)之类的语义名同样很好。英文版 README.md 还进一步示范了三层职责划分:colors.xml只定义调色板,styles.xml中通过引用调色板来表达颜色用途(如按钮前景为白色),activity_main.xml引用样式给按钮上色;若需要更彻底的解耦,还可以再建一个引用调色板的中间颜色资源文件:
<color name="button_foreground">@color/white</color> <color name="button_background">@color/blue</color><style name="AcceptButton"> <item name="android:foreground">@color/button_foreground</item> <item name="android:background">@color/button_background</item> </style>这种做法的收益是更强的颜色重构能力与更稳定的样式定义,代价是需要额外维护一组颜色映射。
7.6dimens.xml:同样做成"尺寸调色板"
与colors.xml同理,应对典型字号与间距定义一套"调色板"。一份好的 dimens 文件示例:
<resources> <!-- Tamaño de letra(字号) --> <dimen name="font_larger">22sp</dimen> <dimen name="font_large">18sp</dimen> <dimen name="font_normal">15sp</dimen> <dimen name="font_small">12sp</dimen> <!-- Espacio entre dos 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> <!-- Tamaño de una View(视图典型尺寸) --> <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_****尺寸而非硬编码数值——就像字符串通常的处理方式一样。这能让应用获得一致的视觉体验,也让样式与布局的调整更加容易。
7.7strings.xml命名与大小写
字符串的 key 应体现上下文(namespace),不必害怕两个或多个 key 共用同一个值;语言是复杂的,命名空间式的 key 能带来语境、消解歧义。
不好(key 语义模糊):
<string name="network_error">Network error</string> <string name="call_failed">Call failed</string> <string name="map_failed">Map loading failed</string>好(key 携带上下文前缀):
<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>不要用全大写书写字符串值,遵循常规文本习惯(例如仅首字母大写)。如果确实需要全大写展示,请在 TextView 上使用textAllCaps属性来实现:
不好:
<string name="error_message_call">CALL FAILED</string>好:
<string name="error_message_call">Call failed</string>八、避免过深的 ViewGroup 层级
为了完成某种视图排布,你可能忍不住再套一层 LinearLayout,于是出现类似下面这种逐层嵌套:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout ... > <LinearLayout ... > <LinearLayout ... > <LinearLayout ... > </LinearLayout> </LinearLayout> </LinearLayout> </RelativeLayout> </LinearLayout>即便单个布局文件中没有显式出现这种结构,通过 Java 代码向其他 View 中 inflate 视图也可能在运行时形成同样的深树。
深层级会带来两类问题:一是性能问题——处理器需要维护一棵复杂 UI 树;二是更严重的隐患——视图过多时可能触发StackOverflowError。
因此应尽量让视图层级保持扁平:学习使用 RelativeLayout、学会优化布局、善用<merge>标签。(英文版 README.md 在此基础上进一步推荐了 ConstraintLayout,它是构建扁平层级的有力工具。)
九、WebViews 的注意事项
需要展示网页(例如新闻正文)时,避免在客户端做 HTML 清洗处理——应要求后端返回"纯净"的 HTML。
WebView 还有一个典型的内存泄漏点:当它持有 Activity 的引用、而不是绑定到 ApplicationContext 时,就会泄漏内存。此外,简单文本或按钮不要用 WebView,优先使用平台自带的 TextView 或 Button。
十、测试框架、模拟器与调试工具
10.1 测试框架
该文档写作时的观点(西语版)如下:
Android SDK 自带的测试工具尚显稚嫩,尤其 UI 测试方面。Android Gradle 提供了
connectedAndroidTest任务,借助 JUnit 的 Android 扩展运行单元测试,这意味着需要连接真机或模拟器才能执行测试。Robolectric 只适合做单元测试,不适合测视图:它以"无需连接设备"换取开发速度,适合 Models 与 View Models 的单元测试;但它对 Android 平台的模拟实现并不精确也不完整,动画、对话框等 UI 元素难以测试,且测试是在"盲测"状态下进行的。
Robotium 让 UI 测试编写更简单:虽然不强制需要,但它提供的视图查找/分析辅助与屏幕控制能力很有价值。测试用例可以简洁到这种程度:
solo.sendKey(Solo.MENU); solo.clickOnText("More"); // 找到第一处 "More" 并点击 solo.clickOnText("Preferences"); solo.clickOnText("Edit File Extensions"); Assert.assertTrue(solo.searchText("rtf"));版本演进提示:仓库英文版 README.md 的测试主张已更新为——用 JUnit 做纯 JVM 上的无 Android 依赖单元测试、避免 Robolectric(因其平台模拟不保证正确性,建议改为 JVM 单元测试 + 真机集成测试的组合)、用 Espresso 编写 UI 测试、用 AssertJ-Android 简化 Android 组件(Support、Google Play Services、AppCompat 等)的断言。断言示例:
// 使用 AssertJ-Android 的断言示例 assertThat(layout).isVisible() .isVertical() .hasChildCount(5);10.2 模拟器
该文档当时的建议(西语版)是:若以 Android 开发为职业,购买 Genymotion 模拟器许可证——其帧率远高于典型 AVD 模拟器,提供演示、网络质量模拟、GPS 定位等工具,非常适合测试,覆盖众多(但非全部)设备,比购买多台真机划算得多。注意事项:Genymotion 不含全部 Google 服务(如 Google Play Store、Google Maps),且三星特定 API 仍需真机验证。
版本演进提示:英文版 README.md 指出,Android SDK 模拟器(尤其 x86 变体)性能近年已大幅改善、足以应付日常开发,同时强调真机验证的价值,并建议把测试精力聚焦于市场份额大、与产品最相关的设备。
10.3 Stetho:把 Chrome DevTools 变成 Android 调试台
Stetho 是 Facebook 出品的 Android 调试桥,与 Chrome 桌面浏览器的 Developer Tools 深度集成。通过它你可以轻松检查应用,尤其是网络流量;还能直接查看与编辑 SQLite 数据库和 SharedPreferences。务必确保 Stetho只在 debug 构建中启用,绝不能进入 release 构建。(英文版还提到备选方案 Chuck:功能更简化,但日志直接显示在设备上,便于测试人员使用,无需连接 Chrome。)
10.4 LeakCanary:把内存泄漏检测变成日常
LeakCanary 让内存泄漏的运行时检测与定位成为开发流程的常规一环。配置与用法详见其官方 wiki。关键提醒:release 构建中只配置 "no-op" 依赖(即空实现),避免把检测逻辑带进生产包。
十一、ProGuard 配置与混淆问题排查
ProGuard 常用于 Android 项目,对打包代码进行缩减(shrink)与混淆(obfuscate)。是否启用取决于项目配置,常见做法是让 Gradle 只在构建 release APK 时启用:
buildTypes { debug { minifyEnabled false } release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }要判断哪些代码必须保留、哪些可以丢弃或混淆,需要为代码指定一个或多个入口点——通常是带有 main 方法的类、applet、midlet、Activity 等。
Android 框架自带一份默认配置,位于SDK_HOME/tools/proguard/proguard-android.txt;上述配置中项目自定义的规则文件my-project/app/proguard-rules.pro会被追加到默认配置之后。
11.1 经典问题:构建成功却启动崩溃
常见现象:assembleRelease等构建命令无警告地成功了,但应用启动即崩,抛出ClassNotFoundException、NoSuchFieldException等异常。原因不外乎两种:
- ProGuard 判定某类、枚举、方法、字段或注解不再需要,将其移除;
- ProGuard 对类、枚举或字段名做了混淆(重命名),但代码仍通过原名间接使用它(例如 Java 反射)。
排查步骤:
- 检查
app/build/outputs/proguard/release/usage.txt,确认目标对象是否被移除; - 检查
app/build/outputs/proguard/release/mapping.txt,确认目标对象是否被混淆。
11.2 用 keep / keepnames 保护代码
防止 ProGuard 移除所需类或成员,在 ProGuard 配置中加入keep:
-keep class com.futurice.project.MyClass { *; }防止 ProGuard 混淆类或成员,使用keepnames:
-keepnames class com.futurice.project.MyClass { *; }更多示例可参考 ProGuard 官方手册的 examples 章节。
11.3 实战纪律
- 项目早期就做 release 构建:验证 ProGuard 规则是否正确保留了依赖。每引入新库或更新依赖后,再做一次 release 构建并在真机上测试 APK。不要等到应用做到 "1.0" 版才首次做 release 构建——届时可能遭遇一堆意外与极短的修复时间。
- 保留每次发布的
mapping.txt:这样当用户遇到 bug 并提交混淆后的堆栈轨迹时,你才能据其定位与反解问题。 - DexGuard:若需要更硬核的优化尤其是混淆能力,可考虑 DexGuard——由开发 ProGuard 的同一团队出品的商业软件,它还能轻松拆分 dex 文件以解决 65k 方法数限制。
十二、数据存储:SharedPreferences、ContentProviders 与 ORM
12.1 SharedPreferences
如果只需要持久化简单值、且应用运行在单一进程中,SharedPreferences 是很好的默认选择。以下场景它并不适用:
- 性能:数据复杂或数据量很大;
- 多进程访问:有 widget 或远程服务运行在各自进程中、需要同步数据。
(英文版 README.md 还补充了第三种场景——关系型数据:数据各部分之间存在关联、需要强制维护这些关系。)
也可以把复杂对象序列化为 JSON 存储、读取时再反序列化,但要注意这种做法的性能与可维护性权衡。
12.2 ContentProviders
当 SharedPreferences 不够用时,应使用平台标准的 ContentProviders——更快且进程安全。它唯一的缺点是搭建所需样板代码较多、且高质量教程稀缺。不过可以通过 Schematic 之类的库自动生成 ContentProvider,显著降低工作量。
之后仍需自己编写少量解析代码来读写 SQLite 列与数据对象之间的映射。也可以借助 Gson 等把数据对象序列化,只持久化结果字符串——代价是性能损失,好处是不必为类的每个字段声明一列。
12.3 关于 ORM
除非数据极端复杂且有迫切需求,一般不推荐使用对象关系映射(ORM)库:它们往往复杂、学习成本高。如果决定使用 ORM,务必确认其是否进程安全(如果应用有此需求的话)——很多现有 ORM 方案在这方面出人意料地并不可靠。
十三、持续集成(CI)
英文版 README.md 进一步补充了持续集成主张:CI 系统能在每次向版本控制推送更新时自动构建与测试项目,还能运行静态代码分析、生成并分发 APK。Lint 与 Checkstyle 保障代码质量,FindBugs 则在代码中寻找 bug。CI 软件种类繁多、特性各异,开源项目通常可免费使用:Jenkins 适合自建本地服务器,Travis CI 则是云托管 CI 的推荐选择。这与本文开头"构建过程由 Gradle 文件定义、而非 IDE 配置"的原则一脉相承——只有可脚本化、可复现的构建,才能被 CI 顺利接管。
十四、结语与许可
本文所依据的规范文档源自 Futurice 的实践沉淀,仓库内提供 README.md 及各语言译本(中文、西语、法语、日语、韩语、葡萄牙语、俄语、土耳其语、越南语),全文遵循 Creative Commons Attribution 4.0 International (CC BY 4.0) 协议(LICENSE),可在注明出处的前提下自由使用与演绎。文中大部分代码示例、目录结构与命令均可直接对照仓库原文复现,建议团队将其裁剪为本项目的工程规范并纳入代码评审清单。
- 文档
- 教程
- 移动开发
【免费下载链接】android-best-practices
Do's and Don'ts for Android development, by Futurice developers
相关推荐
oh-my-hermes技能体检:omh-skill-health如何守护124个技能的质量
oh my hermes技能体检:omh skill health如何守护124个技能的质量 oh my hermes(ohm) 是 Hermes Agent
文档教程移动开发iOS开发最佳实践指南:Futurice的完整入门指南
iOS开发最佳实践指南:Futurice的完整入门指南 本文详细介绍了iOS开发环境的搭建与配置、项目初始化与架构选择策略、依赖管理工具对比以及Git版本控制的
文档教程RedwoodJS最佳实践指南:如何构建可扩展的全栈项目结构
RedwoodJS最佳实践指南:如何构建可扩展的全栈项目结构 RedwoodJS是一个强大的全栈Web框架,专为帮助开发者从 原型项目快速成长到创业级应用 而设
后端前端Web框架开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考