1. Android内存回收机制

[toc]

Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:

  • IMPORTANCE_FOREGROUND:前台进程

  • IMPORTANCE_VISIBLE:可见进程

  • IMPORTANCE_SERVICE:服务进程

  • IMPORTANCE_BACKGROUND:后台进程

  • IMPORTANCE_EMPTY:空进程 这几种优先级的回收顺序是 Empty process、Background process、Service process、Visible process、Foreground process。

1.1. 1. 前台进程

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

  • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
  • 托管某个 Service,该 Service 绑定到用户正在交互的 Activity
  • 托管正在“前台”运行的 Service(服务已调用 startForeground())
  • 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
  • 托管正执行其 onReceive() 方法的 BroadcastReceiver

通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

1.2. 2. 可见进程

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

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

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

1.3. 3. 服务进程

正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。

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

1.4. 4. 后台进程

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。

这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。

1.5. 5. 空进程

不含任何活动应用组件的进程。

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

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

1.6. 6. 默认回收过程

Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收。回收动作入口:activityIdleInternal()。

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

1.6.1. 清单 1. IDLE_NOW_MSG 的处理方式

case IDLE_NOW_MSG:{ 
    IBinder token = (Ibinder)msg.obj; 
    activityIdle(token, null); 
} 
break;

1.6.2. 清单 2. IDLE_TIMEOUT_MSG 的处理方式

case IDLE_TIMEOUT_MSG: {
   if (mDidDexOpt) { 
      mDidDexOpt = false; 
      Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); 
      nmsg.obj = msg.obj; 
      mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); 
      return; 
  } 
  IBinder token = (IBinder)msg.obj; 
  Slog.w(TAG, "Activity idle timeout for " + token); 
  activityIdleInternal(token, true, null); 
} 
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 和 finish 的 activity 对象。+ 对于 stop 列表,如果其中的 activity 的 finish 状态为 true,判断是不是要立即停止,如果要立即停止则调用 destroyActivityLocked() 通知目标进程调用 onDestroy() 方法,否则,先调用 resumeTopActivity() 运行下一个 Activity。如果 finish 状态为 false,则调用 stopActivityLocked() 通知客户进程停止该 Activity,这种情况一般发生在调用 startActivity() 后。对于 finish 列表,直接调用 destroyActivityLocked() 通知客户进程销毁目标 Activity。

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

1.7. 7. 回收过程函数 trimApplications()

trimApplications() 函数的结构如下 :

1.7.1. 清单 3. trimApplications 函数

private final void trimApplications() { 
  synchronized (this) { 

    // First remove any unused application processes whose package 
    // has been removed. 

    for (i=mRemovedProcesses.size()-1; i>=0; i--) {
       (1)//kill process;
     } 

    if (!updateOomAdjLocked()) {
       (2)//do something default 
    } 

    // Finally, if there are too many activities now running, try to 
    // finish as many as we can to get back down to the limit. (3)do something 
  } 
}

清单 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 的进程等,见如下代码。

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

if (app.persistent || app.services.size() != 0 || app.curReceiver != null || app.persistentActivities > 0) {
   // Don't count processes holding services against our
   // maximum process count. numServiceProcs++; 
}

步骤二, 设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs(即默认最大进程数与运行 Service 的进程数之和),如果当前进程的数量 mRemovedProcesses.size() 大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。清理过程见清单 5(部分代码省略)。从清单 5 的代码中可以看出,进程被杀死的条件是: 必须是非 persistent 进程,即非系统进程; 必须是空进程,即进程中没有任何 activity 存在。如果杀死存在 Activity 的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验; 必须无 broadcast receiver。运行 broadcast receiver 一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭; 进程中 service 的数量必须为 0。存在 service 的进程很有可能在为一个或者多个程序提供某种服务,如 GPS 定位服务。杀死此类进程将使其他进程无法正常服务。

以上条件缺一不可。

1.7.3. 清单 5. 清理过程

if (!app.persistent && app.activities.size() == 0 && app.curReceiver == null && app.services.size() == 0) { 
  if (app.pid > 0 && app.pid != MY_PID) { 
    Process.killProcess(app.pid); 
  } else { 
    try { 
      app.thread.scheduleExit(); 
    } catch (Exception e) { 
      // Ignore exceptions. 
    } 
  } 
  // todo: For now we assume the application is not buggy 
  // or evil, and will quit as a result of our request. 
  // Eventually we need to drive this off of the death 
  // notification, and kill the process if it takes too long.

   cleanUpApplicationRecordLocked(app, false, i);
   i--; 

  }

步骤三,再次检查当前运行的进程,如果 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() 销毁,最后才杀死进程。

1.7.4. 清单 6. 执行 destroyActivityLocked() 销毁

boolean canQuit = !app.persistent && app.curReceiver == null 
&& app.services.size() == 0 && app.persistentActivities == 0;

int NUMA = app.activities.size(); 
for (j=0; j<NUMA && canQuit; j++) { 
  HistoryRecord r = (HistoryRecord)app.activities.get(j); 
  canQuit = (r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped; 
} 

if (canQuit) { 

  // Finish all of the activities, and then the app itself. 
  for (j=0; j<NUMA; j++) { 
    HistoryRecord r = (HistoryRecord)app.activities.get(j);
    if (!r.finishing) { 
      destroyActivityLocked(r, false); 
    } 
    r.resultTo = null; 
  }

   if (app.pid > 0 && app.pid != MY_PID) {
     Process.killProcess(app.pid);
   }
   cleanUpApplicationRecordLocked(app, false, i);
   i--;
   //dump();
 }

步骤四,上面 3 个过程都是针对整个 process 进行的资源回收。在以上过程执行完毕之后,将在更小的粒度上对 Activity 的资源进行回收。与上面所述类似,列表 mLRUActivities 存储了当前所有运行中的 Activity,排序规则同样为最少访问原则。mLRUActivities.size() 返回系统中运行的 Activity 的数量,当其大于 MAX_ACTIVITIES(MAX_ACTIVITIES 是一个常量,一般值为 20,代表系统中最大允许同时存在的 Activity)时。将回收部分满足条件的 Activity 以减少内存的使用。回收条件代码清单 7 所示:

1.7.5. 清单 7. 回收条件代码

//Finally, if there are too many activities now running, try to 
// finish as many as we can to get back down to the limit. 
for ( i=0; i<mLRUActivities.size() && mLRUActivities.size() > curMaxActivities; i++) { 
  final HistoryRecord r = (HistoryRecord)mLRUActivities.get(i); 
  // We can finish this one if we have its icicle saved and 
  // it is not persistent. 

  if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { 

    final int origSize = mLRUActivities.size(); 
    destroyActivityLocked(r, true); 
    if (origSize > mLRUActivities.size()) { 
      i--; 
    } 

  }

 }

这里回收的只是 Activity 的内存资源,并不会杀死进程,也不会影响进程的运行。当进程需要调用被杀掉的 Activity 时,可以从保存的状态中回复,当然可能需要相对长一点的时延。

1.8. 参考

Copyright © tracyliu-FE 2021 all right reserved,powered by Gitbook文件修订时间: 2022-03-06 12:52:33

results matching ""

    No results matching ""