[关闭]
@flyouting 2014-06-29T16:07:23.000000Z 字数 3984 阅读 6028

Android应用程序终止的影响

Effects of Android Application Termination

android


透过ActivityManagerService来查看程序是如何被关闭的

在一般的系统进程管理和用户操作之间,还有少数一些方法能够使我们的程序终止。作为一个开发者,你的代码可能会产生一些附加效果,我们来看看一下终止应用的方法产生的影响:

Foreground vs. Background

在我们进一步讨论之前,我们需要先简单说下Android是如何看待前台和后台状态的。一般来说,当前对用户可见的,可交互的应用被称之为前台应用。系统中其他的应用被认为处于后台状态。低内存清理模块可能无法平等的看待后台状态,但是鉴于讨论这个问题,姑且认为是同等看待的。

服务对象可以被标记为“前台服务”,其唯一活跃的用户界面是由一个在系统窗口一直存在的notification组成。在这种特殊的情况下,应用进程被给予了很高的等级(特别是PERCEPTIBLE_APP_ADJ,值为2,值越低优先级越高),即使它没有任何可以跟用户交互的界面,虽然不能完全等同与一个可见的程序,但是这里我们认为它使得所有进程都处于前台状态。

技术上而言,我所指的是进程所在的“调度组”,这不同于出于内存压力需要而去关闭进程时用于排序的数值,系统服务使用这种区分来管理涉及到清除进程的行为。这很重要,需要记住。

比较对照

行为 低内存清理 清除最近任务 强制关闭
进程终止 Y Y1 Y
PendingIntent可触发 Y Y N
接收广播可以启动程序 Y Y N
Activity栈保存 Y N N

Y1:这个终止不能保证,详见下文

低内存清理

这种情况在Android系统中会阶段性发生,且用户无察觉,当系统内存不足时,它会关闭一些进程以回收资源。这么做是为了尽量少的对用户产生影响。所以,当程序进程被终止,外部事件(比如一个广播)会再次启动这个进程。程序甚至会保存在最近使用页面以方便用户再次打开。此外,进程被终止时存在的所有的Activity的栈都会被保留,当用户把应用重新切到前台的时候,他们会被重现。理论上,在第一时间终止应用进程对用户是而言是隐性的。

清除最近任务

此处输入图片的描述

当用户在最近任务页面清除掉一个应用时,产生的行为有些不同。在这种情况下,系统会认为用户不希望此应用状态被保留。此程序进程分配的Activity栈也会被清理。下一次打开应用会开启一个全新的任务。跟上边一样,从其他应用和服务发来的行为可以唤醒并重启此应用进程。

此外,如果进程当时处于一个后台状态的话,那只会终止进程。这通常被认为是正确的,因为一个前台的Activity栈在最近任务页面显示且活跃时也会被暂时切入后台。然而,如果是一个活跃的前台服务的情况下,终止进程的行为会被阻止。在后边会描述为什么这样。

然而这确实意味着,后台运行的服务不能阻止这个级别的进程终止。为了帮助过渡,服务类通过onTaskRemoved()方法提供了一个回调,可以在服务被强制关闭之前记录或处理一些关键的东西。注意,这个回调特定于这种用例,低内存清理时是没有这个回调的。

强制关闭

此处输入图片的描述
最后这种情况是指用户进入系统设置页面,强制要求关闭某个应用。很多人认为这跟之前的方式是一样的。但这里有些需要特别注意,首先就是不管进程是不是后台状态,用户强制关闭时,进程一定会被终止。

除此之外,系统服务中关于此进程的记录都会被标记成"关闭状态",这意味着没有外部触发可以唤醒整个应用进程。在用户再次主动的开发它之前,这个进程是被孤立的。就跟用户刚安装了应用且一次都没打开过一样的状态。

一个有趣的边界情况

不可避免的是,随着Android进程管理的不断进步,复杂性的提升会导致一些未知的状态。其中一个例子就是在一个bug报告的评论里,这里描述的行为是当应用正在运行一个前台服务,任务被滑动取消了,进程依然保持运行状态,但是它很容易被外部触发给搞死。

这么做的原因在于当前ActivityManagerService是如何管理这些事件的。当收到一个请求,需要清理某一个app的任务,如果进程被认为是后台的,服务会直接终止进程。如果是在前台,那服务会延迟一会才会被终止。推测这是为了让把工作做完后才终止,或者从前台状态转变成其他。

ActivityManagerService.computeOomAdjLocked()方法中,进程的前后台状态的重新评定都会检查标记值。这就跟系统事件一样,不断的在发生,这个小例子的关键问题看起来像是在ActivityManagerService中的一段代码中,它用于评估不同的事件条件。

  1. if (app == TOP_APP) {
  2. // The last app on the list is the foreground app.
  3. adj = ProcessList.FOREGROUND_APP_ADJ;
  4. schedGroup = Process.THREAD_GROUP_DEFAULT;
  5. app.adjType = "top-activity";
  6. foregroundActivities = true;
  7. interesting = true;
  8. procState = ActivityManager.PROCESS_STATE_TOP;
  9. } else if (app.instrumentationClass != null) {
  10. // Don't want to kill running instrumentation.
  11. adj = ProcessList.FOREGROUND_APP_ADJ;
  12. schedGroup = Process.THREAD_GROUP_DEFAULT;
  13. app.adjType = "instrumentation";
  14. interesting = true;
  15. procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
  16. } else if ((queue = isReceivingBroadcast(app)) != null) {
  17. // An app that is currently receiving a broadcast also
  18. // counts as being in the foreground for OOM killer purposes.
  19. // It's placed in a sched group based on the nature of the
  20. // broadcast as reflected by which queue it's active in.
  21. adj = ProcessList.FOREGROUND_APP_ADJ;
  22. schedGroup = (queue == mFgBroadcastQueue)
  23. ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE;
  24. app.adjType = "broadcast";
  25. procState = ActivityManager.PROCESS_STATE_RECEIVER;
  26. } else if (app.executingServices.size() > 0) {
  27. // An app that is currently executing a service callback also
  28. // counts as being in the foreground.
  29. adj = ProcessList.FOREGROUND_APP_ADJ;
  30. schedGroup = app.execServicesFg ?
  31. Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE;
  32. app.adjType = "exec-service";
  33. procState = ActivityManager.PROCESS_STATE_SERVICE;
  34. //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
  35. } else {
  36. // As far as we know the process is empty. We may change our mind later.
  37. schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
  38. // At this point we don't actually know the adjustment. Use the cached adj
  39. // value that the caller wants us to.
  40. adj = cachedAdj;
  41. procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
  42. app.cached = true;
  43. app.empty = true;
  44. app.adjType = "cch-empty";
  45. }

通过代码可以看到,一个接收广播的活动在检查是否有前台服务之前就被评定了。如果应用程序在接收标准广播的过程(即一个没有flag_receiver_foreground标志),该过程将被设置为后台状态,代码不会继续执行验证是否有前台服务正在运行。

总结,导致意外的情况:

  1. 程序启动了一个前台服务
  2. 用户将程序切到后台(程序仍在前台集合里,因为服务)
  3. 用户滑动清除了任务,当状态改变时,进程会被杀死
  4. 接收到广播事件,ActivityManagerService更新程序进程的状态来处理广播
  5. 在这么做时, ActivityManagerService把进程移到后台,因为广播事件中不包含前台标示。
  6. 在这种情况下,ActivityManagerService跳过了前台服务的检测。
  7. 在同一地方,如果进程被标记为死亡或者后台组,检测返回为TRUE。

原文地址

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注