黑科技!提升进程优先级的一种新姿势

640

大家好我是张拭心,今天这篇文章是我认识的一个优秀少年 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 让他去死的时候假装没听见呢?显然是可以的,基本的流程就是:

  1. 先拿到 ActivityThread 的实例

  2. 拿到 ActivityThread$H 的实例 mH

  3. 向 mH 设置一个 Handler.Callback

  4. 在 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 吧!




推荐阅读

我的安卓开发半年工作经验总结

安卓开发必备知识体系:Java篇

跳槽需要内推?这里有一大波机会



640?

点击“阅读原文”去作者博客阅读更多精彩文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值