一、AIDL 的回调1、基本介绍AIDL 的回调指服务端主动通知客户端例如位置变化这需要跨进程回调2、演示1CallbackIPlayerCallback.aidl这里是位于src/main/java/com/my/common包下packagecom.my.common;interfaceIPlayerCallback{voidonSongChanged(StringsongName);voidonPlayStateChanged(booleanisPlaying);}2AIDLIMyAidlInterface.aidl这里是位于src/main/java/com/my/common包下packagecom.my.common;importcom.my.common.IPlayerCallback;interfaceIMyAidlInterface{voidplay();voidpause();voidregisterCallback(IPlayerCallbackcallback);voidunregisterCallback();}3ServerprivateIPlayerCallbackcallback;privateStringsongName;privatebooleanisPlayingfalse;privatefinalIMyAidlInterface.StubbindernewIMyAidlInterface.Stub(){Overridepublicvoidplay()throwsRemoteException{if(isPlaying)return;isPlayingtrue;if(callbacknull)return;callback.onPlayStateChanged(isPlaying);if(songNamenull){songNameServer Song;callback.onSongChanged(songName);}}Overridepublicvoidpause()throwsRemoteException{if(!isPlaying)return;isPlayingfalse;if(callbacknull)return;callback.onPlayStateChanged(isPlaying);}OverridepublicvoidregisterCallback(IPlayerCallbackcallback)throwsRemoteException{ServerService.this.callbackcallback;}OverridepublicvoidunregisterCallback()throwsRemoteException{ServerService.this.callbacknull;}};4Clienttry{myAidlInterface.play();Log.i(TAG,play method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,play method error: e.getMessage());}try{myAidlInterface.pause();Log.i(TAG,pause method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,pause method error: e.getMessage());}try{myAidlInterface.registerCallback(newIPlayerCallback.Stub(){OverridepublicvoidonSongChanged(StringsongName)throwsRemoteException{Log.i(TAG,now thread: Thread.currentThread().getName());Log.i(TAG,onSongChanged: songName);}OverridepublicvoidonPlayStateChanged(booleanisPlaying)throwsRemoteException{Log.i(TAG,now thread: Thread.currentThread().getName());Log.i(TAG,onPlayStateChanged: isPlaying);}});Log.i(TAG,registerCallback method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,registerCallback method error: e.getMessage());}try{myAidlInterface.unregisterCallback();Log.i(TAG,unregisterCallback method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,unregisterCallback method error: e.getMessage());}5Test先调用 registerCallback 方法输出结果如下registerCallback method success再调用 play 方法输出结果如下now thread: main onPlayStateChanged: true now thread: main onSongChanged: Server Song play method success再调用 pause 方法输出结果如下now thread: main onPlayStateChanged: false pause method success最后调用 unregisterCallback 方法输出结果如下unregisterCallback method success二、RemoteCallbackList1、基本介绍RemoteCallbackList 是 Android 专门为跨进程通信设计的一个工具类它用于安全、自动地管理回调beginBroadcast 是 RemoteCallbackList 中用于安全遍历列表的一个方法它配合 finishBroadcast 方法一起使用beginBroadcast 方法会固定当前要遍历的快照记录当前所有存活回调的数量在调用finishBroadcast 方法之前列表不会变化2、演示1CallbackIPlayerCallback.aidl这里是位于src/main/java/com/my/common包下packagecom.my.common;interfaceIPlayerCallback{voidonSongChanged(StringsongName);voidonPlayStateChanged(booleanisPlaying);}2AIDLIMyAidlInterface.aidl这里是位于src/main/java/com/my/common包下packagecom.my.common;importcom.my.common.IPlayerCallback;interfaceIMyAidlInterface{voidplay();voidpause();voidregisterCallback(IPlayerCallbackcallback);voidunregisterCallback(IPlayerCallbackcallback);}3ServerprivateRemoteCallbackListIPlayerCallbackcallbackListnewRemoteCallbackList();privateStringsongName;privatebooleanisPlayingfalse;privatefinalIMyAidlInterface.StubbindernewIMyAidlInterface.Stub(){Overridepublicvoidplay()throwsRemoteException{if(isPlaying)return;isPlayingtrue;booleanisSongNameChangedfalse;if(songNamenull){songNameServer Song;isSongNameChangedtrue;}intncallbackList.beginBroadcast();for(inti0;in;i){IPlayerCallbackcallbackcallbackList.getBroadcastItem(i);callback.onPlayStateChanged(isPlaying);if(isSongNameChanged)callback.onSongChanged(songName);}callbackList.finishBroadcast();}Overridepublicvoidpause()throwsRemoteException{if(!isPlaying)return;isPlayingfalse;intncallbackList.beginBroadcast();for(inti0;in;i){IPlayerCallbackcallbackcallbackList.getBroadcastItem(i);callback.onPlayStateChanged(isPlaying);}callbackList.finishBroadcast();}OverridepublicvoidregisterCallback(IPlayerCallbackcallback)throwsRemoteException{callbackList.register(callback);}OverridepublicvoidunregisterCallback(IPlayerCallbackcallback)throwsRemoteException{callbackList.unregister(callback);}};4Clienttry{myAidlInterface.play();Log.i(TAG,play method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,play method error: e.getMessage());}try{myAidlInterface.pause();Log.i(TAG,pause method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,pause method error: e.getMessage());}try{myAidlInterface.registerCallback(newIPlayerCallback.Stub(){OverridepublicvoidonSongChanged(StringsongName)throwsRemoteException{Log.i(TAG,now thread: Thread.currentThread().getName());Log.i(TAG,onSongChanged: songName);}OverridepublicvoidonPlayStateChanged(booleanisPlaying)throwsRemoteException{Log.i(TAG,now thread: Thread.currentThread().getName());Log.i(TAG,onPlayStateChanged: isPlaying);}});Log.i(TAG,registerCallback method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,registerCallback method error: e.getMessage());}try{myAidlInterface.unregisterCallback();Log.i(TAG,unregisterCallback method success);}catch(RemoteExceptione){e.printStackTrace();Log.e(TAG,unregisterCallback method error: e.getMessage());}5Test先调用 registerCallback 方法输出结果如下registerCallback method success再调用 play 方法输出结果如下now thread: main onPlayStateChanged: true now thread: main onSongChanged: Server Song play method success再调用 pause 方法输出结果如下now thread: main onPlayStateChanged: false pause method success最后调用 unregisterCallback 方法输出结果如下unregisterCallback method success三、AIDL 安全校验需求引入默认情况下任何知道 AIDL 接口的应用都能绑定服务并调用方法这可能导致数据泄露和恶意调用1、权限校验1ServerAndroidManifest.xml定义权限permissionandroid:namecom.my.ACCESS_TEST_SERVICEandroid:protectionLevelnormal/在 Binder 方法中校验权限intresultcheckCallingPermission(com.my.ACCESS_TEST_SERVICE);Log.i(TAG,权限检查结果: result);if(resultPackageManager.PERMISSION_DENIED){Log.i(TAG,权限拒绝);thrownewSecurityException(权限拒绝);}Log.i(TAG,权限通过);// 执行其他业务逻辑2ClientAndroidManifest.xml声明权限uses-permissionandroid:namecom.my.ACCESS_TEST_SERVICE/2、UID / PID 校验Server在 Binder 方法中校验 UID / PIDintcallingUidBinder.getCallingUid();if(callingUid!1000callingUid!1001){thrownewSecurityException(无权调用);}// 执行其他业务逻辑3、包名白名单校验Server在 Binder 方法中校验包名白名单privatestaticfinalListStringALLOWED_PACKAGESArrays.asList(com.my.server,com.my.client);privatebooleanallowedPackagesCheck(){intcallingUidBinder.getCallingUid();String[]packagesgetPackageManager().getPackagesForUid(callingUid);if(packages!null){for(Stringpkg:packages){Log.i(TAG,package: pkg);if(ALLOWED_PACKAGES.contains(pkg)){returntrue;}}}returnfalse;}if(!allowedPackagesCheck()){Log.i(TAG,调用者未被授权);thrownewSecurityException(调用者未被授权);}Log.i(TAG,调用者已被授权);// 执行其他业务逻辑四、protectionLevel补充学习protectionLevel 是 Android 权限系统中定义权限风险等级的属性normal普通级别安装时自动授予无需用户确认dangerous危险级别运行时需用户明确授权signature签名级别只有相同签名的应用才能获得权限安装时自动授予signatureOrSystem签名或系统级别只有相同签名的应用才能获得权限或者位于 Android 系统映像的专用文件夹中的应用才能获得权限在 API 级别 23 中已废弃是signature|privileged的旧同义词建议使用 signature 代替参考文档 1https://developer.android.google.cn/guide/topics/manifest/permission-element?hlzh-cn参考文档 2https://developer.android.google.cn/reference/android/R.attr#protectionLevel