news 2026/10/1 18:11:01

Android 开发最佳实践(Futurice 版)完整指南:Gradle 构建、资源组织与 ProGuard 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android 开发最佳实践(Futurice 版)完整指南:Gradle 构建、资源组织与 ProGuard 实战
  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-best-practices

Do's and Don'ts for Android development, by Futurice developers

项目地址:https://gitcode.com/gh_mirrors/an/android-best-practices
点击查看免费下载

本指南以 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.xml
  • view_primary_button.xml
  • activity_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等异常。原因不外乎两种:

  1. ProGuard 判定某类、枚举、方法、字段或注解不再需要,将其移除;
  2. 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

项目地址:https://gitcode.com/gh_mirrors/an/android-best-practices
点击查看免费下载
上一篇:Home Assistant IMAP 集成 `imap.fetch_part` 动作详解:提取邮件附件与 MIME 部件
下一篇:DataHub Prefect 集成实战指南:用 prefect-datahub Block 捕获 Flow/Task 血缘与运行元数据

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 18:10:01

Kubernetes CrashLoopBackOff 排障实战:日志、退出码与根因定位

开年在群里帮一个朋友排查容器反复重启的问题&#xff0c;从下午两点一直弄到晚上八点&#xff0c;最后发现根因居然是镜像里的一个环境变量写错了。这种场景在 Kubernetes 排障里太常见了——Pod 状态卡在 CrashLoopBackOff&#xff0c;日志却往往被截断或者压根没输出&#x…

作者头像 李华
网站建设 2026/10/1 18:08:09

YOLOv8轻量化改进:坐标注意力与EfficientNet结合的车辆检测方案

一、为什么YOLOv8在边缘端“跑不动”? 车辆检测是智能交通系统的核心任务,但把检测模型塞进边缘设备时,开发者往往会遇到一个尴尬的局面:YOLOv8精度够用,但计算开销压不下去。 根据Ultralytics官方文档的基准数据,YOLOv8n的参数量约为3.0M,GFLOPs约为8.1,在桌面GPU上…

作者头像 李华
网站建设 2026/10/1 18:07:51

AI Engineering from Scratch:构建可追溯、可验证的AI系统流水线

1. 这不是“搭积木”&#xff0c;而是亲手锻造AI系统的完整流水线“AI Engineering from Scratch”——这个标题乍看像一句技术宣言&#xff0c;实则是一份沉甸甸的工程契约。它不指代调用几个API、微调一个LoRA权重&#xff0c;更不是在Colab里跑通一段Hugging Face示例代码就…

作者头像 李华
网站建设 2026/10/1 18:07:48

uv:用Rust重写的下一代Python包管理器,从入门到实战

上个月帮朋友清理一台 Windows 上的 Python 环境&#xff0c;他是 ComfyUI 重度用户&#xff0c;扩展管理器怎么都装不上&#xff0c;pip 又抛出一连串externally-managed-environment、pip 无法识别、版本过老的警告。我帮他做的事很简单&#xff1a;把包管理器从 pip 换成 uv…

作者头像 李华