黑科技!提升进程优先级的一种新姿势
大家好我是张拭心,今天这篇文章是我认识的一个优秀少年 easoll 的分享,他在阅读 framework 源码后发现了一种新的提升进程优先级的方法,绝对是你之前没有用过的方法,值得学习!
作者:easoll
原文地址:https://easoll.github.io/
想了解更多的朋友欢迎复制链接或者点击“阅读原文”去作者博客
大家都知道在安卓中调用 Service
的 startForeground()
方法可以将Service
所在进程的优先级提高,减小进程被回收的概率。
调用 startForeground()
方法的时候系统会在通知栏显示一个通知,这对于传统的多媒体应用来说是没有问题的。但是对于那些只想提升优先级而不想让用户感知的应用来说,强行显示个通知栏看起来太怪异了。
查看 startForeground()
的源码之后,发现有很重要的两步,如下所示:
ActiveServices.java
private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
Notification notification, int flags) {
if (id != 0) {
......
r.postNotification(); //step1: 在通知栏显示通知
if (r.app != null) {
updateServiceForegroundLocked(r.app, true); //step2: 更新进程优先级
}
getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
} else {
......
}
}
那么我们有没有办法能让 updateServiceForegroundLocked()
执行成功,而让 postNotification()
执行失败呢?
我们进一步查看 postNotification()
方法:
ServiceRecord.java
public void postNotification() {
if (foregroundId != 0 && foregroundNoti != null) {
// Do asynchronous communication with notification manager to
// avoid deadlocks.
final String localPackageName = packageName;
final int localForegroundId = foregroundId;
final Notification _foregroundNoti = foregroundNoti;
ams.mHandler.post(new Runnable() {
public void run() {
NotificationManagerInternal nm = LocalServices.getService(
NotificationManagerInternal.class);
if (nm == null) {
return;
}
Notification localForegroundNoti = _foregroundNoti;
try {
......
//step1: 向NotificationManagerServervice发送通知
nm.enqueueNotification(localPackageName, localPackageName,
appUid, appPid, null, localForegroundId, localForegroundNoti,
userId);
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.setServiceForeground(name, ServiceRecord.this,
0, null, 0);
//step2:如果出现异常,则将应用进程crash掉
ams.crashApplication(appUid, appPid, localPackageName, -1,
"Bad notification for startForeground: " + e);
}
}
});
}
}
由上可知,只要 enqueueNotification()
执行出现异常,通知栏则不会显示通知了,但是此时却会导致应用进程 crash
调用。
那么如何使得 crashApplication()
这个方法失效呢,我们进一步查看
crashApplication()
方法的代码:
ActivityManagerService.java
public void crashApplication(int uid, int initialPid, String packageName, int userId,
String message) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: crashApplication() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
synchronized(this) {
//最终会通过binder,调用应用进程中的scheduleCrash方法
mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
}
}
ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
public void scheduleCrash(String msg) {
//通过handler发送一个类型为H.SCHEDULE_CRASH的消息
sendMessage(H.SCHEDULE_CRASH, msg);
}
}
private class H extends Handler {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
......
case SCHEDULE_CRASH:
//当收到类型为SCHEDULE_CRASH的消息的时候则抛出一个异常,导致进程crash
throw new RemoteServiceException((String)msg.obj);
......
}
}
我们现在已经知道了 ams
是如何让我们的进程 crash
的了,基本就是 ams
跟我们应用进程说,你准备准备该去死了,然后应用进程就去死了。
但是做为一个有个性的进程,能不能在 ams
让他去死的时候假装没听见呢?显然是可以的,基本的流程就是:
先拿到
ActivityThread
的实例拿到
ActivityThread$H
的实例mH
向
mH
设置一个Handler.Callback
在
Handler.Callback
中检测到SCHEDULE_CRASH
消息时则消费该消息
具体实现代码如下所示:
private static void hookH(){
if(mHasHookH){
return;
}
mHasHookH = true;
try {
try {
Class hClass = Class.forName("android.app.ActivityThread$H");
Field scheduleCrashField = hClass.getDeclaredField("SCHEDULE_CRASH");
mScheduleCrashMsgWhat = (int)scheduleCrashField.get(null);
Log.i(TAG, "get mScheduleCrashMsgWhat success");
}catch (Exception e){
Log.i(TAG, "get mScheduleCrashMsgWhat failed");
e.printStackTrace();
}
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, msg.toString());
if(msg.what == mScheduleCrashMsgWhat){
return true;
}
return false;
}
};
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object activityThreadInstance = currentActivityThread.invoke(null);
Handler hInstance = (Handler) mH.get(activityThreadInstance);
Class handlerClass = Handler.class;
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(hInstance, callback);
}catch (Exception e){
e.printStackTrace();
}
}
这样就可以实现优先级提升而又不弹出通知栏了。
作者写了一个工具类方便大家使用:
https://github.com/easoll/RaisePriorityHack,觉得有帮助就给个 star 吧!
推荐阅读
点击“阅读原文”去作者博客阅读更多精彩文章
推荐阅读