谁生谁死?Android进程优先级

 

前言

让我们面对现实:移动设备没有无限的内存,无限的电池容量或无限的其他任何东西。这对您的应用程序意味着,您应该将进程死亡视为应用程序生命周期的自然部分。重要的部分是,确保与杀死进程相关的内存回收不会对用户产生负面影响。实际上,Android中的大部分进程架构都是专门设计的,它确保设计顺序不是任意的,而是通过一个重要的层次结构来遵循一组设计模式。

Android进程层次结构

您将发现最重要的进程称为前台进程,后面是任何可见进程,服务进程,后台进程,最后是“空”进程,详见文档,我们将在此处进行扩展。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

以下不在原文,摘自文档:

进程生命周期

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程):

  1. 前台进程

    用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:

    通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

  2. 可见进程

    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:

    • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
    • 托管绑定到可见(或前台)Activity 的 Service

    可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

  3. 服务进程

    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

  4. 后台进程

    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。

  5. 空进程

    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

请注意,虽然我们将讨论特定组件(service,activity),但Android只会杀死进程,而不会杀死组件当然,这并不排除通常的垃圾收集过程(它将回收未引用对象的内存),但这是另一篇文章的主题。

前台进程

您认为将用户正在与之交互的内容保持存活,是最重要的事情,您是对的。但是“用户正在与之交互的内容”对定义来说有点模糊。属于这个定义的最明显的事情是当前的前台Activity  - 一个Activity,其中onResume()已被调用,但尚未接到对onPause()的调用。

虽然有些activities 独立存在,但它们也可以依赖绑定服务承载绑定到前台activity 的服务的任何进程,都被赋予相同的前台优先级。这当然具有直观意义 - 如果前台activities 认为保持与服务的持续连接非常重要,那么对于activities 和Android都很重要,以保持该服务的活跃性。这同样适用于正在与前台服务交互的ContentProvider。

但谁说活动是用户注意到的唯一消失的东西?如果我的音乐突然停止播放或我的导航方向突然丢失,我肯定会生气。值得庆幸的是,Android允许服务通过startForeground()方法成为高优先级前台服务,让services 对系统显而易见。这绝对是正确播放媒体的最佳做法,但这里要问的重要问题是“如果我的服务被停止,用户会立即注意到吗?” 前台服务应仅用于关键的,立即引起注意的用例。

注意:作为前台服务要求您的服务包含通知,以确保用户完全了解您的服务正在运行。如果您觉得您的用例不需要通知,那么前台服务可能不适合您(没关系:前台服务不是在后台运行的要求 - 见下文)。

还有一些其他情况,其中进程暂时被提升为接收关键生命周期方法的前台进程,包括任何服务的生命周期方法(onCreate()onStartCommand()onDestroy())以及任何广播接收器的onReceive()。这可以保护这些组件,以确保这些操作是有效的原子操作,并且每个组件都能够完成它们而不会被杀死。

可见进程

等等,我以为我已经涵盖了当前的活动?通过Android的乐趣,您会发现有些情况下您的Activities可见但不在前台一个简单的例子是以Dialog为主题的前台活动或半透明活动开始新活动。另一个示例可能是您调用运行时权限对话框(实际上是一个Activity!)。

从收到onStart()到收到onStop()时,你会知道你是一个可见的活动。在这些调用之间,您应该做一切可见活动(实时更新屏幕等)。

与前台活动类似,相同的可见过程状态也应用于可见活动的绑定服务和内容提供者。这再次确保了这些依赖过程不会过早地被杀死,而它们正在被活动中使用它们。

请记住,仅仅因为你是可见的并不意味着你不能被杀死。如果前台进程有足够的内存压力,您的可见进程仍有可能被终止。从用户的角度来看,这意味着当前活动背后的可见活动被替换为黑屏。当然,如果您正确地重新创建了您的活动,那么只要关闭前台活动而不丢失任何数据,您的进程和活动就会恢复。

注意:即使可见也可能导致您的活动和进程被终止这一事实是startActivityForResult() + onActivityResult()requestPermissions() + onRequestPermissionsResult()流的原因之一,它不会采用回调类实例 - 如果您的整个过程每个回调类实例都会死掉。如果您看到使用回调方法的库,请意识到它对低内存压力情况不具备弹性。

服务进程

如果您的进程不属于上述任何一种类别,但您已启动服务,那么您将被视为服务进程。对于许多正在进行后台处理(例如,加载数据)的应用而言,这是典型的,而没有作为前台服务所带来的即时性。

这不是一个糟糕的地方!对于绝大多数情况,这是后台处理的理想场所,因为它确保只有在上述可见和前台进程类别中发生了大量事情时才会终止您的进程。

请特别注意从onStartCommand()返回的常量,因为这可以控制如果由于内存压力而导致服务被杀死时会发生什么:

  • START_STICKY意味着您希望系统尽可能自动重启您的服务,但您不关心是否再次返回最后一个 Intent(即,您可以重新创建自己的状态或控制自己的开始/停止生命周期)。
  • START_REDELIVER_INTENT非常适合那些想重新启动,如果杀了与在收到的意向服务 onStartCommand()直到你调用 stopSelf() startId这是传递 onStartCommand()  -在这里你正在使用的传入意图和他们的startId小号作为完成工作的队列。
  • START_NOT_STICKY适用于可以安静地进入夜晚的服务。这适用于管理定期任务的服务,等待直到下一个时间帧不是世界末日。

后台进程

假设您的Activity是前台Activity,但是用户只需点击主页按钮就可以调用onStop()。假设所有这些都是保持较高优先级的类别,那么您的进程将属于后台进程类别。这里是(在正常操作情况下)设备的大部分内存专用,以防您决定稍后返回之前的某个开放活动。

Android不会仅仅为了杀戮而杀死东西(记住:从头开始事情并不是免费的!),所以这些过程可能会在被回收之前保留一段时间,因为内存需求来自更高层次的东西类别,按最近的使用顺序杀死(最旧的是先收回)。但是,与杀死时的可见活动相同,您应该能够在不丢失用户状态的情况下随时重新创建活动

空进程

与任何层次结构一样,最低级别。如果它还没有被覆盖,它可能就在这里。在这里,没有活跃的组件,就Android而言,这些可以在任何时候被杀死,虽然进程可以纯粹用于缓存目的(这里我们去,有效地使用我们的内存而不是让它完全免费)。

注意事项和注意事项

虽然我们在诸如您拥有的活动和服务等组件方面讨论了进程优先级,但请记住,这些优先级是在进程级别而不是组件级别完成的。只需一个组件(比如前台服务)就可以将整个过程推向前台。虽然绝大多数应用程序都是单个进程,但如果您的应用程序的生命周期或极重量级部分与长时间运行的轻量级部分无关,那么强烈考虑将它们设置为单独的进程,以便尽快收集重量级进程比以后。

同样重要的是,您所属的进程类别是基于组件级别发生的事情。这意味着当您突然成为后台进程时,在您的Activity中的单独线程中启动超级重要的长时间运行操作可能会遇到灾难。使用可用的工具(基于优先级的服务或前台服务)确保系统知道您正在做什么。

与他人合作愉快并牢记用户

使整个系统工作的原因是对用户的强烈关注。成为一个好公民并构建您的应用程序,以便您始终以适当的优先级工作。

然而,虽然我仍然建议购买极低端的Android进行测试,但您仍然可以测试您的应用如何响应您的高端设备被杀。要在程序包级别终止您的应用程序,请使用:

adb shell am force-stop com.example.packagename

并且,如果您有多个进程,您可以通过查看第二列(即第一个数字)来找到您的进程ID(PID)

adb shell ps | grep com.example.packagename

然后杀了它

adb shell kill PID

测试应用程序如何响应被杀是第一步确保您的应用程序在尽可能多的设备上运行良好,无论有多少内存压力。

 

谷歌官方进程与线程

转自Who lives and who dies? Process priorities on Android

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值