如果你不知道AMS代表的是ActivityManagerService的话,请按ctrl(command)+W,或按鼠标回退键。因为你接着往下看可能会觉得反胃,恶心,眩晕,虽然我好像写的挺易懂的样子,HOHOHOHOHO~
通常android中谈论到GC的时候,我们更喜欢往Java的GC上面去靠,然后套路性的谈论GC的方法,GC的发起时间,如果可以靠到JVM,甚至是Dalvik,ART的GC的策略应该已经可以把*装的蛮好了。
今天我们就来谈论在AMS在内存回收中起到的作用。
我们通常的理解中AMS做的工作更多的是管理Activity,去做Activity的线程启动,分配Activity的资源,慢点!你说什么!分配Activity的资源!分配完了你不负责回收么,既然是你分配的,你不回收??
打开你的AMS类,不要怕,即使有300+L的import,20000+L的代码量也不用担心,我们就看几个东西,不疼的,就像被蚊子扎了一下。。
从闲置Idle开始
首先我们定位到这样一个方法,名字叫activityIdle
。字面意思,activity的闲置方法,你想既然activity闲置了,是不是就是说明我们回收内存的机会来了。
我们先不急着看,这个方法做了什么,我们希望追根溯源的去找一下,这个方法在什么地方被调用的,所谓知其然,知其所以然。
如果你读过我的binder分析大法,那么当你对着这个方法Find Usage发现没有什么反映了之后你应该不会慌乱的想到要从ActivityManagerNative出发了。
如果你这边有所疑问,你可以先去看一下
突然悟出在源码中分析Binder的玩法
最终我们找到在如下的地方调用到了这个activityIdle方法
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
...
if (a != null) {
mNewActivities = null;
IActivityManager am = ActivityManagerNative.getDefault();
ActivityClientRecord prev;
do {
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null && a.activity.mFinished));
if (a.activity != null && !a.activity.mFinished) {
try {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
...
}
}
这是Message队列的内部类闲置handler,这个queueIdle回调在消息队列中没有消息可以处理的空闲时期被调起,此处不做深入,知道即可。那我们看一下,这个Idler在哪里被调用了。
找到在handleResumeActivity中,
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
...
//如果是其他activity可以跳转过来的
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
...
} else {
...
}
}
也就是说,在activity执行resume方法之后,系统会在当前的线程中添加一个空闲任务。
这边需要科普一下,一般我们在调用了finish方法,或者是启动了一个新的应用或者是activity方法之后,当前的activity会处于后台,并且处于空闲,因此就会触发queueIdle的方法,从而触发AMS的activityIdle的方法。
然后我们正式的打开activityIdle方法
public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
final long origId = Binder.clearCallingIdentity();
synchronized (this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
ActivityRecord r =
mStackSupervisor.activityIdleInternalLocked(token, false, config);
if (stopProfiling) {
if ((mProfileProc == r.app) && (mProfileFd != null)) {
try {
mProfileFd.close();
} catch (IOException e) {
}
clearProfilerLocked();
}
}
}
}
Binder.restoreCallingIdentity(origId);
}
主要就是检测当前activity栈是否为空,如果栈中有activity,那么就调用ActivityStactSupervisor.activityIdleInternalLocked
方法
方法比较长,其实出于本文的主旨考虑,可以只留一个trimApplications
方法,但是为了大家可以有一些其他的收获,这边把涉及到activity生命周期的部分方法做了保存并注解,有心的读者可以发散开来,以对onStop,onDestory有更深入的了解。
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
ArrayList<ActivityRecord> finishes = null;
ArrayList<UserState> startingUsers = null;
int NS = 0;
int NF = 0;
boolean booting = false;
boolean activityRemoved = false;
...
if (allResumedActivitiesIdle()) {
if (r != null) {
mService.scheduleAppGcsLocked();
}
...
}
// 获取已经暂停的activity列表
final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(true);
NS = stops != null ? stops.size() : 0;
if ((NF = mFinishingActivities.size()) > 0) {
// 获取已经触发了finish方法的列表
finishes = new ArrayList<>(mFinishingActivities);
mFinishingActivities.clear();
}
if (mStartingUsers.size() > 0) {
startingUsers = new ArrayList<>(mStartingUsers);
mStartingUsers.clear();
}
for (int i = 0; i < NS; i++) {
r = stops.get(i);
final ActivityStack stack = r.task.stack;
if (stack != null) {
//如果该被暂停的activity已经调用了finish方法,那么就调用栈的finish当前的activity的方法
if (r.finishing) {
stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
} else {
// 否则调用栈的stopActivity方法
stack.stopActivityLocked(r);
}
}
}
// 遍历finish列表中的每一个activity,如果当前栈不为空,就去触发栈的destroyActivityLocked方法
for (int i = 0; i < NF; i++) {
r = finishes.get(i);
final ActivityStack stack = r.task.stack;
if (stack != null) {
activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
}
}
...
mService.trimApplications();
//dump();
//mWindowManager.dump();
if (activityRemoved) {
resumeFocusedStackTopActivityLocked();
}
return r;
}
主要的回收方法是trimApplications方法,这个方法倒是真的一点都无法删减了
final void trimApplications() {
synchronized (this) {
int i;
//遍历mRemovedProcesses,杀死所有的进程
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
//如果此进程没有activity,且没有广播,且没有服务方可被kill掉
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.toShortString() + " ("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
app.kill("empty", false);
} else {
try {
app.thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
mRemovedProcesses.remove(i);
if (app.persistent) {
addAppLocked(app.info, false, null /* ABI override */);
}
}
}
updateOomAdjLocked();
}
}
直接把mRemovedProcesses的所有进程统统杀死,mRemovedProcesses中保存的是crash的进程,ANR的进程,以及调用killbackprocess等的进程。
略为复杂的updateOomAdjLocked方法
调用updateOomAdjLocked方法,更新所有进程的oom adj,以及在必要的时候kill掉一些进程。并且根据内存等级回调触发onTrimMemory的方法,具体onTrimMemory具体做什么是需要我们自己去实现的。所以真正起到回收内存作用的还是更新oom adj和必要时候kill进程。
方法代码极长,如果大家感兴趣,可以去看这位大哥写的方法分析
Android 7.0 ActivityManagerService(8) 进程管理相关流程分析(2)updateOomAdjLocked
最终就是这个方法让AMS在内存回收中起到了重要的作用。简要的来讲,总共做了如下一些工作:
- 根据当前进程列表情况,将后台进程中adj数值较高的和空进程分配到待回收槽中,槽的大小有限,一旦超过了槽的大小,那么就会对槽中的进程根据LRU情况,kill掉最久没使用的进程
- 不断的计算进程列表中各进程的adj值,并进行更新。
- 计算进程的回收等级,根据回收等级触发onTrimMemory的回调
Linux内核搭配所做的内存回收工作
如果你熟悉Linux内核的LMK机制,那你应该能知道,LowMemoryKiller会在适当的时候对进程进行杀死处理。这里就是利用了adj进行评判每个进程的重要等级,然后在内存吃紧的时候杀死优先级低的进程并发送Kill Signal。
内核还有一个oom killer,在LMK依然解决不了问题的时候,系统为新的应用程序分配内存的时候会出现OOM的错误,这个时候就轮到oom killer出场了,它会对计算进程的badness值(也是一种判断进程回收等级的数值),badness值大的会被收掉。