今天在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