Android 四大组件全解读,kotlin 极简教程
为了达到这一点,我们将一个 APP 的 main 方法分解成几种系统可以与之交互的形式。这几种形式就是Activity
,BroadcastReceiver
,Service
和ContentProvider
APIs,广大的 Android 开发者都很熟悉它们。
这些类好像在告诉你,你的APP内部应当怎样工作,但这是一种误解!事实上,这些类只是定义你的APP需要怎样与系统交互(以及系统怎样协调你的APP与其他APP进行交互)。这种与系统的交互一旦开始,系统就不再关心你的APP内部是怎样运行了。
为了更好地说明这一点,让我们简要地看看这些 APIs 对于 Android 系统来说到底意味着什么。
这是一个 APP 与用户交互的入口。从系统的角度看,系统为 Activity 提供的关键交互动作是:
持续跟踪用户当前正在关心的(也就是显示在屏幕上的东西),以确保当前进程保持运行。
个人理解:这里,作者实际上的含义是,当你的应用被系统从 Activity 启动时,在 Activity 的 start 与 stop 状态之间,系统会确保这个 Activity 始终占据着设备的屏幕,并且确保你的应用绝不会被系统杀死。这是你从 Activity 启动自己的 APP 时,系统给予你的 APP 的一种承诺(just a promise)。
知道那些之前使用过的进程,这些进程包含着用户可能会返回获取的东西(stopped activities),并因此给予这些进程更高的优先级。
帮助应用处理进程被杀死的情况,以便用户能够返回到之前的 activities,并且这些 activities 能够加载自己之前的状态
个人理解:很显然,系统所承诺的这种状态恢复能力,是依靠
Activity
的onSaveInstanceState
和onRestoreInstanceState
方法,也就是说,你在 Save 方法中保存好你想在进程被杀死时想要保存的 Activity 状态,然后你就可以在 Restore 方法中获取这些状态以恢复 Activity。当你把这些做完后,剩下的就是系统的事情了,系统会承诺,如果由于内存压力杀死了你的 Activity 所在的进程,那么当你返回时,系统会重建你的应用进程,并帮助你恢复之前 Activity 的状态。
提供一种在不同应用之间的用户流(user flow)的方式,当然这要靠系统来协调。最经典的例子就是分享功能的实现。
对于 Activity 来说,系统并不关心的是:
一旦系统从 Activity 入口进入到你的 APP UI 之中,系统将不再关心 Activity 内部逻辑的组织。你可以将所有的应用逻辑全放入这一个 Activity 中,比如你可以手动地改变它的 views,使用 fragments 或者其他框架,你也可以把你的应用逻辑分拆成额外的内部 activities。你也可以三者同时使用(指的是改变 views,使用 fragemnts,分拆成额外的 activities)。这些事情系统是毫不关心的,只要你遵循 Activity 与系统之间的约定(在适当的状态下启动它,正确地保存/恢复它的状态)。
这是一种让系统在正常的用户流(user flow)之外,传递事件给 APP 的机制。最重要的是,因为这是另一个被精心定义的 APP 的入口,即使 APP 当前并不在运行,系统也可以将 broadcasts 传递给 APP。所以,举例来说,一个 APP 可以提前调度一个 alarm,以便通知用户一个马上到来的事件,通过将这个 alarm 传递给该 APP 的一个 BroadcastReceiver,在 alarm 发生之前,APP
都没必要运行。
对于 BroadcastReceiver 来说,系统并不关心的是:
在APP内部分发事件
是一个与 BroadcastReceiver 接收事件完全不同的事,不管你是使用一些 eventbus 框架,实现你自己的回调系统,还是任何其他方法…你都没有理由使用系统的广播机制,因为你并不是在App之间分发事件
。
事实上,不使用系统的广播机制还有一个很好的原因,这会带来许多不必要的负担,而且使用全局广播机制来实现 APP 内部的事件分发会引发许多安全问题 。
当然,我们也提供了一个 LocalBroadcastManager 便利类,它实现了一个纯粹的进程内的 intent 分发系统,而且它的 API 与系统 BroadcastReceiver API 很相似,如果你喜欢当然也可以使用。但再次强调,你没有理由在仅仅发生在APP中的事情上使用BroadcastReceiver机制
。
当由于各种各样的原因需要 APP 在后台运行时,Service 就是一个这样的入口。有两种语义上截然不同的 Services(一种是 Started Service,一种是 Bound Service)来告诉系统怎样管理一个 APP。
Started Service 就相当于因为某种原因你的 APP 告诉系统:“系统大哥,我有事要干,请让我一直运行,直到我告诉你我干完了。”(这里的我相当于 APP,因为此时的 Service 就代表了 APP,而系统是只跟 APP 对话的)这里的“事”可能是在后台同步数据或者在用户离开 APP 后播放音乐。
同时,Started Service 又有两种,一种是用户可感知的,一种是用户无法感知的。这两种不同的 Started Service 会让系统对它们采取不同的管理方式。
播放音乐的 Service 是用户可以直接感知的,所以 Service 会对系统说:“我想成为前台(foreground),并且在通知栏挂一个通知,让用户能够感到我的存在。”这种情况下,系统知道,必须使出吃奶的劲保证这个 service 进程的运行,因为如果这个进程宕掉,用户会不高兴。
另一种后台 Service 是用户无法直接感知的,所以系统可以更加灵活地处理这个 Service 的进程。在系统急需 RAM 以保证用户眼前的事情正常运转时,系统可能会允许该进程被杀死(然后可以在之后有能力时再启动该 Service)。
Bound Service 之所以会运行,是因为其他 APP 或者系统要使用它。通常情况该 Service 都会给其他进程提供一个 API。在这种情况下,系统知道这两个进程之间存在一个依赖关系。所以,如果进程 A 绑定了进程 B 中的一个 Service,系统就会知道,它要为进程 A 保证进程 B 和它里面的 Service 正常运行。进一步讲,如果进程 A 是用户当前正在关心的进程,系统将知道把进程 B 也当作用户正在关心的进程。
由于 Service 的灵活性(有好也有坏),Service 已经成为各种类型的系统中一个非常有用的构建块(building block)。实时墙纸,通知监听器和许多其他的系统核心特性都被构建为 Service,当它们需要运行时,系统再绑定它们。
对于 Service,系统不关心的是:
Android 不关心你的 APP 中那些不影响它怎样对待你的进程的事,所以这些情况下,是没有理由使用 Service 的。举例来说,如果你想在后台为你的 UI 下载数据,你不应该使用 Service 来做这件事----做这些事时,不告诉系统保持你的进程运行真的是很重要的,因为确实没有必要!!这样做也让系统有更多的自由去管理你的进程,以便与用户正在做的事情相协调(注:可以让系统在内存紧急的情况下,杀死你的进程,优先保证用户正在做的事情,这里忍不住吐槽一句:每个APP肯定都会觉得自己是最重要的哈,Google开发Android的人也是典型的理想主义!
)
如果你只是简单地开启了一个后台线程来做数据下载(或者其他不是 Service 的办法),你将会得到你想要的结果:如果用户在你的 UI 里,系统将会确保你的进程运行,所以下载绝不会被中断。当用户离开了你的 UI,你的进程仍将被保持(缓存)因而可以继续下载数据,只要 RAM 不告急就行。
同样,为了将你的 APP 的不同部分连接起来,你也没有理由去绑定同一个进程中的 Service。这样做倒是没有什么明显的害处,因为系统会看到,该进程对自己有一个依赖,它将会忽略这个依赖,仍将你的 APP 当作普通进程看待。但是这样做对你和系统实际上都是没有必要的。作为替代,你可以使用单例或者其他进程内的模式来将你的 APP 的各部分连接到一起。
最后,ContentProvider 是一个专用的办法,用来将你的 APP 的数据公开到其他地方。人们通常会将它们当作对数据库的抽象,因为有许多的 API 和支持库就是这样使用 ContentProvider 的。但是从系统设计的角度,这并不是 ContentProvider 的初衷。
对于系统来说,ContentProvider 实际上是一个入口,用于获取一个 APP 内部的公开的被命名的数据项(data items),每个数据项都被一个 URI scheme 所标识。这样,APP 就可以决定怎样将自己的数据项映射到一个 URI scheme,怎样将这个 URI scheme 公开给其他 APP 或者系统,好让 APP 或者系统使用这个 URI scheme 来获取自己内部的数据。这将让系统能够用一些很独特的方式来管理你的 APP:
将 URI scheme 公开出去并不要求你的 APP 一直保持运行,所以即使你的 APP 没有运行,这些 URI scheme 也可以公开给任何 APP 和系统。只有当某个人告诉系统:“请把这个 URI 代表的数据拿给我。”时,系统将会让你的 APP 运行起来向你索要对应于 URI 的数据项并返回给请求者。
这些 URIs 也提供了一个很重要的细粒度的安全模型。比如,你的 APP 可以将代表一张你的 APP 内的图片的 URI 放在剪贴板上,但是让它的 ContendProvider 保持在锁定状态,所以没有人能够自由地获取它。当其他 APP 从剪贴板上获取了这个 URI,并向系统请求获取对应的图片时,系统可以给它一个临时的“URI 许可”,以便让它仅能获取该 URI 所对应的图片,你的 APP 的其他内容都是安全的。
评论