今天在NotificationManagerService
中去调用PendingIntent.getIntent()
时发现了下面这个报错,其中pid=4901, uid=10097
分别是来push平台
的进程id和应用id,而PendingIntent.getIntent()
这个API是@hide
的,因此普通应用是没法调用的,反射也调用不到,因此后面给出的解法是针对具备系统权限的开发者的。java
W System.err: java.lang.SecurityException: Permission Denial: getIntentForIntentSender() from pid=4901, uid=10097 requires android.permission.GET_INTENT_SENDER_INTENT
W System.err: at com.android.server.am.ActivityManagerService.enforceCallingPermission(ActivityManagerService.java:6291)
W System.err: at com.android.server.am.ActivityManagerService.getIntentForIntentSender(ActivityManagerService.java:5855)
W System.err: at android.app.PendingIntent.getIntent(PendingIntent.java:1135)
W System.err: at com.android.server.notification.xxx.callReplyIntent(xxx.java:64)
W System.err: at com.android.server.notification.NotificationManagerService.checkDisqualifyingFeatures(NotificationManagerService.java:5569)
W System.err: at com.android.server.notification.NotificationManagerService.enqueueNotificationInternal(NotificationManagerService.java:5193)
W System.err: at com.android.server.notification.NotificationManagerService$10.enqueueNotificationWithTag(NotificationManagerService.java:2609)
W System.err: at android.app.INotificationManager$Stub.onTransact(INotificationManager.java:1149)
W System.err: at android.os.Binder.execTransactInternal(Binder.java:1027)
W System.err: at android.os.Binder.execTransact(Binder.java:1000)
复制代码
看报错的调用栈能发现是在AMS中作权限检查(ActivityManagerService.enforceCallingPermission()
)的时候抛的异常:android
// 抛异常的代码
void enforceCallingPermission(String permission, String func) {
if (checkCallingPermission(permission)
== PackageManager.PERMISSION_GRANTED) {
return;
}
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ " requires " + permission;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
// checkCallingPermission(),往下走就是去检查该用户是否具备android.permission.GET_INTENT_SENDER_INTENT 这个权限了
int checkCallingPermission(String permission) {
return checkPermission(permission,
Binder.getCallingPid(),
Binder.getCallingUid());
}
复制代码
能够看到抛异常是由于checkCallingPermission
函数返回false了,也就是调用方push平台没有权限去调这个接口,那为何咱们明明是在NotificationManagerService
(这是一个系统服务,管理通知的发送和删除等事务)中去调用这个接口的,这里却判断出来是来自push平台的呢,这里咱们须要了解两个Binder相关的概念。git
每一个线程都有一个惟一的IPCThreadState
对象记录着当前线程的pid和uid,而这两个id的获取就是经过Binder.getCallingPid() 与 Binder.getCallingUid()
这两个方法来获取的。bash
当线程A经过Binder调用线程B的接口时,B线程的IPCThreadState
中保存的uid和pid
是线程A的uid和pid
,通常咱们调用这两个接口来获取线程A的id值来作权限对比。app
而此时在线程B中,B是当前进程中的调用方了,若此时线程B经过Binder去调用其余方法,而其余方法又经过这两个方法去获取对应的uid和pid
去作权限对比时,获取到的值就仍是线程A的,因此咱们须要在B经过Binder去调用其余方法前,将线程B中保存的uid和pid
作一遍刷新,对于这个Binder提供了另外两个接口:ide
其中Binder.clearCallingIdentity()
会帮咱们去清除当前线程中的id状态,并将结果返回给咱们;而当咱们执行完Binder操做后,咱们须要恢复线程B中的id值,此时就须要调用Binder.restoreCallingIdentity(id)
去执行这个操做。函数
了解完这两个概念,咱们再来看看原来的问题,前面咱们说过,这个接口是@hide
的,只有具备系统权限的应用才能调用,那么问题就转化为,咱们在这里怎么以系统的身份去调这个接口呢?这里就须要用到咱们上面说的几个接口了,直接看代码吧:gitlab
final long token = Binder.clearCallingIdentity();
Intent intent;
try {
intent = pendingIntent.getIntent();
} finally {
Binder.restoreCallingIdentity(token);
}
复制代码
代码很简单,也就是在调用前利用Binder.clearCallingIdentity()
将线程状态清除掉,在调用结束时利用Binder.restoreCallingIdentity(token)
将线程状态恢复,这样就实现了将原来来自push平台的调用转化为NotificationManagerService
的调用,而NotificationManagerService
是系统服务,就能够顺利调用到了。ui
其实这个问题是Android在7.0以上的系统中新增了相关的权限判断后才出现的(感兴趣的能够看下这笔提交),在这以后普通应用就没法调用该接口了,想要调用PendingIntent.getIntent()
就必须拥有系统签名,或者像上面这个例子同样,经过Binder提供的接口,巧妙的将原来权限不足的应用的权限升级为系统权限。spa
本文主要想分享下上文出现的几个Binder相关函数的意义和使用场景,That'all