Chinaunix首页 | 论坛 | 博客
  • 博客访问: 260674
  • 博文数量: 63
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1860
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-07 14:41
文章分类

全部博文(63)

文章存档

2015年(2)

2014年(61)

我的朋友

分类: Android平台

2014-10-07 15:48:07

    简介:Android 是一款基于 Linux 内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理。本文在 Application Framework 与 Linux 内核两个层次上,以进程为粒度,对 Android 操作系统的进程资源回收机制进行了剖析。读者可以从本文获得对 Android 应用程序的生存周期的进一步理解,从而更加合理、高效地构建应用程序。

Android APP 的运行环境

    Android 是一款基于 Linux 内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别。主要包含下面几个层次:

1.Application Framework

    Application Framework 将整个操作系统分隔成两个部分。对应用开发者而言,所有 APP 都是运行在 Application Framework 之上,而并不需要关心系统底层的情况。Application Framework 层为应用开发者提供了丰富的应用编程接口,如 Activity Manager,Content Provider,Notification Manager,以及各种窗口 Widget 资源等。在 Application Framework 层,Activity 是一个 APP 最基本的组成部分。一般每个 Activity 对应于屏幕上的一个视图(或者说一屏),一个 APP 可以有一个或者多个 Activity。应用程序被打包成 .apk 格式的文件,由 Dalvik VM 解释执行。

2.Dalvik VM

    Dalvik 虚拟机采用寄存器架构,而不是 JVM 的栈结构。Java 程序编译后的 .class 文件并不能在 Dalvik 中解释执行。因此 Google 提供了一个 dx 工具,用于将 .class 文件转换成 Dalivk 能够识别的 .dex 格式。具体 Dalvik VM 的细节不是本文重点,以下不再讨论。

3.Linux kernel

    由上所述,所有的 APP 都是由 Java 代码编写并在 Dalvik VM 中得到解释执行。在 Android 操作系统中,每个 Dalvik VM 的每个 Instance 都对应于 Linux 内核中的一个进程。可以使用 adb shell 工具查看系统中的当前进程。如下图所示,Android2.3.3 启动后内核中的进程列表

图 1. Android 2.3 中的进程列表(部分)

image001.png


Android 内存回收原则

    下面将从 Application Framework 和 Linux kernel 两个层次分析 Android 操作系统的资源管理机制。

Android 之所以采用特殊的资源管理机制,原因在于其设计之初就是面向移动终端,所有可用的内存仅限于系统 RAM,必须针对这种限制设计相应的优化方案。当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。当系统内存不足时,系统将激活内存回收过程。为了不因内存回收影响用户体验(如杀死当前的活动进程),Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:

IMPORTANCE_FOREGROUND:

IMPORTANCE_VISIBLE:

IMPORTANCE_SERVICE:

IMPORTANCE_BACKGROUND:

IMPORTANCE_EMPTY:

    ActivityManagerService 集中管理所有进程的内存资源分配。所有进程需要申请或释放内存之前必须调用 ActivityManagerService 对象,获得其“许可”之后才能进行下一步操作,或者 ActivityManagerService 将直接“代劳”。类 ActivityManagerService 中涉及到内存回收的几个重要的成员方法如下:trimApplications(),updateOomAdjLocked(),activityIdleInternal() 。这几个成员方法主要负责 Android 默认的内存回收机制,若 Linux 内核中的内存回收机制没有被禁用,则跳过默认回收。

默认回收过程

    Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制将在下一张介绍。 本章所有代码可参见 ActivityManagerService.java。

回收动作入口:activityIdleInternal()

    Android 系统中内存回收的触发点大致可分为三种情况。第一,用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖;第二,用户按 back 键,退出当前应用程序;第三,启动一个新的应用程序。这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 时,activityIdleInternal() 将会被调用。代码如下:

清单 1. IDLE_NOW_MSG 的处理方式

  1. case IDLE_NOW_MSG:{
  2. IBinder token = (Ibinder)msg.obj;
  3.     activityIdle(token, null);
  4. }
  5. break;
复制代码
清单 2. IDLE_TIMEOUT_MSG 的处理方式
  1. case IDLE_TIMEOUT_MSG: {
  2. if (mDidDexOpt) {
  3.         mDidDexOpt = false;
  4.         Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);
  5.         nmsg.obj = msg.obj;
  6.         mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT);
  7.         return;
  8.     }
  9.     IBinder token = (IBinder)msg.obj;
  10.     Slog.w(TAG, "Activity idle timeout for " + token);
  11.     activityIdleInternal(token, true, null);
  12. }
  13. break;
复制代码

    IDLE_NOW_MSG 由 Activity 的切换以及 Activiy 焦点的改变等事件引发,IDLE_TIMEOUT_MSG 在 Activity 启动超时的情况下引发,一般这个超时时间设为 10s,如果 10s 之内一个 Activity 依然没有成功启动,那么将发送异步消息 IDLE_TIMEOUT_MSG 进行资源回收。activityIdleInternal() 的主要任务是改变系统中 Activity 的状态信息,并将其添加到不同状态列表中。其主要工作如下:

    首先,调用 scheduleAppGcsLocked() 方法通知所有进行中的任务进行垃圾回收。scheduleAppGcsLocked() 将进行调度 JVM 的 garbage collect,回收一部分内存空间,这里仅仅是通知每个进程自行进程垃圾检查并调度回收时间,而非同步回收。然后,取出 mStoppingActivities 和 mFinishigActivities 列表中的所有内容,暂存在临时变量中。这两个列表分别存储了当前状态为 stop 和 finishi 的 activity 对象。对于 stop 列表,如果其中的 activity 的 finish 状态为 true,判断是不是要立即停止,如果要立即停止则调用 destroyActivityLocked() 通知目标进程调用 onDestroy() 方法,否则,先调用 resumeTopActivity() 运行下一个 Activity。如果 finish 状态为 false,则调用 stopActivityLocked() 通知客户进程停止该 Activity,这种情况一般发生在调用 startActivity() 后。对于 finish 列表,直接调用 destroyActivityLocked() 通知客户进程销毁目标 Activity。

    这里的 destroyActivityLocked 等函数并没有真正意义上改变内存的使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的 trimApplications() 函数中。

回收过程函数 trimApplications()

trimApplications() 函数的结构如下 :

清单 3. trimApplications 函数

  1. private final void trimApplications() {
  2. synchronized (this) {
  3.         // First remove any unused application processes whose package
  4.         // has been removed.
  5.         for (i=mRemovedProcesses.size()-1; i>=0; i--) {
  6.            (1)//kill process;
  7.         }
  8.           if (!updateOomAdjLocked()) {
  9.            (2)//do something default
  10.         }
  11.         // Finally, if there are too many activities now running, try to
  12.         // finish as many as we can to get back down to the limit.
  13.            (3)do something
  14.     }
  15. }
复制代码

清单 3 中的三个标序号的位置分别负责如下工作:

(1)当程序执行到 trimApplications() 之后,首先检查 mRemovedProcesses 列表中的进程。mRemovedProcesses 列表中主要包含了 crash 的进程、5 秒内没有响应并被用户选在强制关闭的进程、以及应用开发这调用 killBackgroundProcess 想要杀死的进程。调用 Process.killProcess 将所有此类进程全部杀死。

(2)调用 updateOomAdjLocked() 函数,若成功返回,说明 Linux 内核支持 setOomAdj() 接口,updateOomAdjLocked 将修改 adj 的值并通知 linux 内核,内核根据 adj 值以及内存使用情况动态管理进程资源(lowmemorykiller 和 oom_killer)。若 updateOomAdjLocked() 返回为假,则表示当前系统不支持 setOomAdj() 接口,将在本地进行默认的资源回收。

(3)最后,如果当前依然运行了过多的 Activity,对多余的 Activity 进行回收。 trimApplications() 的大多数的代码都在处理 Oom_killer 不存在情况下的默认资源回收,下面对其默认回收过程(即代码清单中标记(2)的位置)进行进一步分析。其回收过程可大致描述如下。

步骤一,获取当前所有运行的进程 mLruProcesses,mLruProcesses 中的排序规则是按最近使用时间。对 mLruProcesses 中不能被关闭的进程进行计数,这些不能被关闭的进程包括运行 service 的进程,运行 broadcast receiver 的进程等,见如下代码。

清单 4. 计数不能被关闭的进程

  1. if (app.persistent || app.services.size() != 0
  2.             || app.curReceiver != null
  3.             || app.persistentActivities > 0) {
  4. // Don't count processes holding services against our
  5.     // maximum process count.
  6.         numServiceProcs++;
  7. }
复制代码

步骤二, 设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs(即默认最大进程数与运行 Service 的进程数之和),如果当前进程的数量 mRemovedProcesses.size() 大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。清理过程见清单 5(部分代码省略)。从清单 5 的代码中可以看出,进程被杀死的条件是:

● 必须是非 persistent 进程,即非系统进程;

● 必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;

● 必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;

● 进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。

以上条件缺一不可。

清单 5. 清理过程

  1. if (!app.persistent && app.activities.size() == 0
  2.             && app.curReceiver == null && app.services.size() == 0) {
  3.         if (app.pid > 0 && app.pid != MY_PID) {
  4.             Process.killProcess(app.pid);
  5.         } else {
  6.             try {
  7.                 app.thread.scheduleExit();
  8.             } catch (Exception e) {
  9.                 // Ignore exceptions.
  10.             }
  11.         }
  12.         // todo: For now we assume the application is not buggy
  13.         // or evil, and will quit as a result of our request.
  14.         // Eventually we need to drive this off of the death
  15.         // notification, and kill the process if it takes too long.
  16.         cleanUpApplicationRecordLocked(app, false, i);
  17.         i--;
  18. }
复制代码

步骤三,再次检查当前运行的进程,如果 mRemovedProcesses.size() 仍然大于 curMaxProcs,则放宽条件再次进行回收。判断条件见代码清单 6(部分代码省略)。下面代码中,布尔变量 canQuit 的值为真时,那么这个进程可以被回收。canQuit 的取值分两个步骤,首先是根据进程的属性赋值。 1. 必须是非 persistent 进程,即非系统进程;2. 必须无 broadcast receiver;3. 进程中 service 的数量必须为 0;4. persistent 类型的 activity 数量为 0。与步骤二唯一的不同在第 4 条,这里不要求进程是空进程,只要进程中没有 persistent 类型的 Activity 就可以(Activity 是否是 persistent 类型在开发阶段指定)。这些条件都满足时,再检查进程中每个 Activity 的属性,当该进程中所有的 Activity 都还必须满足三个条件:Activity 的状态已经保存,当前处在不可见状态并且 Activity 已经 Stop。这时杀掉进程只会降低下次调用程序时的加载速度,下次启动时将恢复到关闭之前的状态,并不会在用户体验上造成致命的影响,所以,canQuit 置位为真。这种情况与步骤二的回收方式也有所不同,由于进程中 Activity 的数量不是 0,下一步需要对每个 activity 执行 destroyActivityLocked() 销毁,最后才杀死进程。

阅读(1318) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~