Android 黑科技保活实现原理揭秘(3)

1.保持进程不被系统杀死。

2.进程被系统杀死之后,可以重新复活。

随着 Android 系统变得越来越完善,单单通过自己拉活自己逐渐变得不可能了。因此后面的所谓「保活」基本上是两条路:

1.提升自己进程的优先级,让系统不要轻易弄死自己;

2.App 之间互相结盟,一个兄弟死了其他兄弟把他拉起来。

当然,还有一种终极方法,那就是跟各大系统厂商建立 PY 关系,把自己加入系统内存清理的白名单。比如说国民应用微信。当然这条路一般人是没有资格走的。

大约一年以前,大神 @Gityuan 在其博客上公布了 TIM 使用的一种可以称之为「终极永生术」的保活方法。这种方法在当前 Android 内核的实现上可以大大提升进程的存活率。笔者研究了这种保活思路的实现原理,并且提供了一个参考实现 Leoric。接下来就给大家分享一下这个终极保活黑科技的实现原理。

保活的底层技术原理


知己知彼,百战不殆。既然我们想要保活,那么首先得知道我们是怎么死的。一般来说,系统杀进程有两种方法,这两个方法都通过 ActivityManagerService 提供:

1.killBackgroundProcesses

2.forceStopPackage

在原生系统上,很多时候杀进程是通过第一种方式,除非用户主动在 App 的设置界面点击「强制停止」。不过国内各厂商以及一加、三星等 ROM 现在一般使用第二种方法。第一种方法太过温柔,根本治不住想要搞事情的应用。第二种方法就比较强力了,一般来说被 force-stop 之后,App 就只能乖乖等死了。

因此,要实现保活,我们就得知道 force-stop 到底是如何运作的。既然如此,我们就跟踪一下系统的 forceStopPackage 这个方法的执行流程:

首先是 ActivityManagerService里面的 forceStopPackage 这方法:

public void forceStopPackage(final String packageName, int userId) {

// … 权限检查,省略

long callingId = Binder.clearCallingIdentity();

try {

IPackageManager pm = AppGlobals.getPackageManager();

synchronized(this) {

int[] users = userId == UserHandle.USER_ALL

? mUserController.getUsers() : new int[] { userId };

for (int user : users) {

// 状态判断,省略…

int pkgUid = -1;

try {

pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,

user);

} catch (RemoteException e) {

}

if (pkgUid == -1) {

Slog.w(TAG, "Invalid packageName: " + packageName);

continue;

}

try {

pm.setPackageStoppedState(packageName, true, user);

} catch (RemoteException e) {

} catch (IllegalArgumentException e) {

Slog.w(TAG, "Failed trying to unstop package "

  • packageName + ": " + e);

}

if (mUserController.isUserRunning(user, 0)) {

// 根据 UID 和包名杀进程

forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);

finishForceStopPackageLocked(packageName, pkgUid);

}

}

}

} finally {

Binder.restoreCallingIdentity(callingId);

}

}

在这里我们可以知道,系统是通过 uid为单位 force-stop 进程的,因此不论你是 Native 进程还是 Java 进程,force-stop 都会将你统统杀死。我们继续跟踪 forceStopPackageLocked这个方法:

final boolean forceStopPackageLocked(String packageName, int appId,

boolean callerWillRestart, boolean purgeCache, boolean doit,

boolean evenPersistent, boolean uninstalling, int userId, String reason) {

int i;

// … 状态判断,省略

boolean didSomething = mProcessList.killPackageProcessesLocked(packageName, appId, userId,

ProcessList.INVALID_ADJ, callerWillRestart, true /* allowRestart */, doit,

evenPersistent, true /* setRemoved */,

packageName == null ? ("stop user " + userId) : ("stop " + packageName));

didSomething |=

mAtmInternal.onForceStopPackage(packageName, doit, evenPersistent, userId);

// 清理 service

// 清理 broadcastreceiver

// 清理 providers

// 清理其他

return didSomething;

}

这个方法实现很清晰:先杀死这个 App 内部的所有进程,然后清理残留在 system_server 内的四大组件信息。我们关心进程是如何被杀死的,因此继续跟踪 killPackageProcessesLocked,这个方法最终会调用到 ProcessList 内部的 removeProcessLocked 方法, removeProcessLocked 会调用 ProcessRecord 的 kill 方法,我们看看这个 kill:

void kill(String reason, boolean noisy) {

if (!killedByAm) {

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “kill”);

if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {

mService.reportUidInfoMessageLocked(TAG,

"Killing " + toShortString() + " (adj " + setAdj + "): " + reason,

info.uid);

}

if (pid > 0) {

EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);

Process.killProcessQuiet(pid);

ProcessList.killProcessGroup(uid, pid);

} else {

pendingStart = false;

}

if (!mPersistent) {

killed = true;

killedByAm = true;

}

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

}

}

这里我们可以看到,首先杀掉了目标进程,然后会以uid为单位杀掉目标进程组。如果只杀掉目标进程,那么我们可以通过双进程守护的方式实现保活;关键就在于这个 killProcessGroup,继续跟踪之后发现这是一个 Native 方法,它的最终实现在libprocessgroup中,代码如下:

int killProcessGroup(uid_t uid, int initialPid, int signal) {

return KillProcessGroup(uid, initialPid, signal, 40 /retries/);

}

注意这里有个奇怪的数字:40。

我们继续跟踪:

static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {

// 省略

int retry = retries;

int processes;

while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {

LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;

if (retry > 0) {

std::this_thread::sleep_for(5ms);

–retry;

} else {

break;

}

}

// 省略

}

瞧瞧我们的系统做了什么骚操作?循环 40 遍不停滴杀进程,每次杀完之后等 5ms,循环完毕之后就算过去了。

看到这段代码,我想任何人都会蹦出一个疑问:假设经历连续 40 次的杀进程之后,如果 App 还有进程存在,那不就侥幸逃脱了吗?

实现方法


那么,如何实现这个目的呢?我们看这个关键的 5ms。假设,App 进程在被杀掉之后,能够以足够快的速度(5ms 内)启动一堆新的进程,那么系统在一次循环杀掉老的所有进程之后,sleep 5ms 之后又会遇到一堆新的进程;如此循环 40 次,只要我们每次都能够拉起新的进程,那我们的 App 就能逃过系统的追杀,实现永生。是的,炼狱般的 200ms,只要我们熬过 200ms 就能渡劫成功,得道飞升。不知道大家有没有玩过打地鼠这个游戏,整个过程非常类似,按下去一个又冒出一个,只要每次都能足够快地冒出来,我们就赢了。

最后

为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。

加油,共勉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值