“我该如何设计我的 Android 应用?我该使用哪种 MVC 模式?我该用 event bus 做些什么?”
我们经常会看到 Android 平台的工程师询问该如何在他们 app 中使用设计模式和架构设计。答案也许是令人惊讶的,我们往往并没有强烈的意愿或者真知灼见。
(这里的我们是指 Android Platform 开发小组,并不是指 Goolge 或 Android 开发者。在 Google 内部或外部有很多关于如何编写 app 的优秀的意见和建议,我不打算去反驳这些。)
你是否该使用 MVC ? 或 MVP ? 或 MVVM ?
这个我不知道。我在学校里了解过 MVC,其他的(MVP/MVVM)是 Google 搜索后才写到前面的。这可能有点让人惊讶,因为 Android 感觉像是有强烈的意见指引该如何去写 app —— 通过其 Java API 以及一些高级的概念,它可能看起来像一个典型的应用框架,指导 app 应该如何工作,但绝大多数情况下并不是这样。
将核心 Android API 称为“系统框架”可能会更好。在大多数情况下,我们提供的平台 API 用于定义 app 和操作系统的交互方式;但对于存在于 app 中的任何内容,这些 API 通常与之无关。也就是说,Android API 通常看起来与期望的操作系统 API 不同(或更高级),从而导致该如何使用它们显得比较迷惑。
举例来说,来看看操作系统定义“如何启动一个 app”。在一个经典的系统中,app 像这样启动:
int main(...) {
// My app goes here!
}
操作系统启动 app,调用它的 main() 方法,app 运行并执行所需的操作,直到它决定完成。这里没有任何关于 app 需要怎么做以及该如何设计 main() 方法里的操作——这是个纯净的白板。
然而在 Android 中,我们明确的决定不会有一个 main() 方法,因为我们需要让平台更好地控制 app 的运行方式。特别是,我们需要设计一个用户无需考虑开始以及停止 app 的系统,由系统为他们完成这部分工作...所以系统需要有更多的关于每个 app 内部发生的情况的信息,并且能够在需要时以各种有效的方式启动 app,即使它们当前没有运行。
为了做到这一点,我们将 app 典型的主要入口分解成系统可以使用的几种不同的交互。它们是 Activity, BroadcastReceiver,Service 和 ContentProvider API,Android 开发者可以迅速熟悉它们。
为了说明,让我们简单的看看这些不同的 API以及他们对 Android 系统的意义。
Activity
这是 app 与用户交互的入口。从系统的角度,它与 app 提供的关键交互是:
- 追踪用户当前关心的内容(屏幕上显示的内容),以确保托管进程持续运行。
- 获取之前使用的进程包含的可能返回的 Activity(已停止的 Activities),从而高优先级的保持这些进程。
- 帮助 app 处理其进程被 kill 的情况,以便于用户可以返回先前状态的 Activity。
- 为 app 之间提供一种实现用户流的方式,由系统协调。(经典的比如分享场景)。
我们没有关注的地方:
当我们进入你的 UI 入口后,我们不需要关注如何组织内部的流程。使用单一的 Activity 更改它的 views,或者使用 fragment 和其他框架,或者将其分解为额外的 activities。或者根据需求以上三条都做,只要你保持和 Activity 的上层做连接(它在适当的状态下启动,保存/恢复当前的状态),这和系统没有关系。
BroadcastReceiver
这是系统将事件传递到普通用户流之外的应用程序的机制。最重要的是,因为这是另一个明确定义的 app 入口,系统可以传递 broadcast 到 app 即使它当前不在运行。举例来说,app 可以安排闹钟来发布通知,告诉用户待办事件...通过将该闹钟的 broadcast 传递到 app 内的 BroadcastReceiver,无需该应用保持运行直到闹钟关闭。
我们没有关注的地方:
App 内的 event 调度是一件完全不同的事,是否你使用一些 event bus 框架,实现你自己的回调系统,以及其他...并没有理由使用系统的广播机制,因为你没有跨app 调度 event(实际上有很多理由不这样做——在 app 的内部实现使用全局的广播机制会有很多不必要的开销和许多潜在的安全问题。)我们确实提供了 LocalBroadcastManager 类,它实现了纯进程内 intent 调度系统,与系统API具有相似的 API,如果你碰巧喜欢它们:)。但是,没有其他理由在你的 app 中使用系统的广播机制。
Service
一个由于各种原因需要保持 app 在后台运行的通用的入口。实际上有两条非常独特语义的 service 告诉系统如何管理应用程序:
Start service 告诉系统,由于某些原因,“让我保持运行,直到我说完成了。”这可以用来同步一些数据,或者在用户离开 app 后播放音乐。这也代表了两种不用类型的 start service,描绘系统该如何处理它们:
- 音乐播放栏是用户可以直接意识到的,因此 app 告诉系统它想通过成为前台 notification 告诉用户音乐正在播放,这时系统明白应该尽量保持该服务进程运行,因为假如它消失了用户会不开心。
- 常规的后台服务不是用户直接意识到运行的,所以系统管理它的进程的自由度更高。如果用户需要立即关注的内容需要内存,这个服务可以被立即 kill(然后在未来某个时刻重启服务)。
Bound service 正在运行,因为其他 app (或系统)表示要使用该服务。这基本上是为另一个进程提供 API 的服务。系统知道这些进程之间存在依赖关系,因此如果进程 A 与进程B中的服务绑定,系统需要保持进程 B(以及它的 service)以运行 A。此外,如果进程 A 是用户所关注的,系统同样会关注进程 B。
由于其灵活性(好的或坏的),service 已被证明是各种上层系统概念的非常有用的构建基础。动态壁纸,通知监听器,屏保程序,输入程序,服务功能服务,以及许多其他核心系统功能都以 service 构建应用实现,当它们需要运行时与系统的 service 绑定。
我们没有关注的地方:
Android 不会关心你的 app 内管理使用流程的操作,因此这种场景没有必要使用 service。例如,你想为 UI 开启后台下载任务,为此你不应该使用 service —— 实际上不必告诉系统你的进程需要持续运行,因为它真的不需要。
如果你创建一个简单的后台线程(或者任意非 service 机制)下载东西,你将获得潜在的场景:当用户处在 app 的 UI 界面,系统将保持你的 app 进程,下载任务不会被打断。当用户离开 UI 界面,只要其他地方不需要 RAM,你的进程将被保留(缓存)并将能够持续下载。
同样,对于连接同一 app 内的不同模块,没有理由在同一进程中使用 bind service,这样做并不十分有害——系统发现进程依赖于自身,因此不必做比寻常更多的事情,但对于 app 和系统来说却做了许多无用功。此外,你应该使用单例或者其他的进程内模式连接 app 内的不同模块。
ContentProvider
ContentProvider 是一个专业的 app 数据 publish 工具。人们通常认为它是数据库层的抽象,因为它有很多与数据库相关的支持和 API ...然而从系统设计的角度,并不像那样。
ContentProvider 是 app 发布数据条目的入口,通过 URI scheme 标识。因此,app 可以决定如何将其包含的数据映射到 URI 命名空间,将这些 URI 发给可以依此访问数据的其他 entities。系统在管理 app 时可以使用一些特殊的操作:
- 发出 URI 并不需要 app 保持运行,所以这些可以发布到任何 app dead 的地方。只有在有人告诉系统,“嘿,给我这个 URI 的数据”,它需要确保持有该数据的 app 正在运行,并且按照要求检索和返回数据。
- 这些 URI 还提供了一个重要的细粒度安全模型。例如,app 可以将剪贴板上的图片映射到 URI,但将其内容提供者锁住,以免任何人可以自由访问它。当另一个 app 将该 URI 从剪贴板取出时,系统可以给它提供一个临时的“URI 权限授权”以便允许它访问 URI 对应的数据,但在提供数据的 app 中什么也没有发生。
我们没有关注的地方:
如何实现 content provider 背后的数据管理并不重要;如果你不需要SQLite 数据库中的结构化数据,不必使用 SQLite。例如,FileProvider helper class 是通过 content provider 使你 app 内 raw 文件可用的简单方法。
同样,如果你没有从 app 发布数据供其它人使用,则完全没必要使用 content provider。因为围绕 content provider 构建的各种 helper class 可以便捷地将数据放入 SQLite 并将其用于填充 UI 组件(如 ListView)。但假如这些工具使你开发更加困难,说明你不需要使用它,而应该为你的 app 训责