Android 多进程开发 - AIDL 回调、RemoteCallbackList、AIDL 安全校验

一、AIDL 的回调

1、基本介绍
  • AIDL 的回调指服务端主动通知客户端,例如,位置变化,这需要跨进程回调
2、演示
(1)Callback
  • IPlayerCallback.aidl,这里是位于 src/main/java/com/my/common 包下
java 复制代码
package com.my.common;

interface IPlayerCallback {
    void onSongChanged(String songName);
    void onPlayStateChanged(boolean isPlaying);
}
(2)AIDL
  • IMyAidlInterface.aidl,这里是位于 src/main/java/com/my/common 包下
java 复制代码
package com.my.common;

import com.my.common.IPlayerCallback;

interface IMyAidlInterface {
    void play();
    void pause();
    void registerCallback(IPlayerCallback callback);
    void unregisterCallback();
}
(3)Server
java 复制代码
private IPlayerCallback callback;
private String songName;
private boolean isPlaying = false;

private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {

    @Override
    public void play() throws RemoteException {
        if (isPlaying) return;
        isPlaying = true;

        if (callback == null) return;

        callback.onPlayStateChanged(isPlaying);

        if (songName == null) {
            songName = "Server Song";
            callback.onSongChanged(songName);
        }
    }

    @Override
    public void pause() throws RemoteException {
        if (!isPlaying) return;
        isPlaying = false;

        if (callback == null) return;

        callback.onPlayStateChanged(isPlaying);
    }

    @Override
    public void registerCallback(IPlayerCallback callback) throws RemoteException {
        ServerService.this.callback = callback;
    }

    @Override
    public void unregisterCallback() throws RemoteException {
        ServerService.this.callback = null;
    }
};
(4)Client
java 复制代码
try {
    myAidlInterface.play();
    Log.i(TAG, "play method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "play method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.pause();
    Log.i(TAG, "pause method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "pause method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.registerCallback(new IPlayerCallback.Stub() {
        @Override
        public void onSongChanged(String songName) throws RemoteException {
            Log.i(TAG, "now thread: " + Thread.currentThread().getName());
            Log.i(TAG, "onSongChanged: " + songName);
        }

        @Override
        public void onPlayStateChanged(boolean isPlaying) throws RemoteException {
            Log.i(TAG, "now thread: " + Thread.currentThread().getName());
            Log.i(TAG, "onPlayStateChanged: " + isPlaying);
        }
    });
    Log.i(TAG, "registerCallback method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "registerCallback method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.unregisterCallback();
    Log.i(TAG, "unregisterCallback method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "unregisterCallback method error: " + e.getMessage());
}
(5)Test
  1. 先调用 registerCallback 方法,输出结果如下

    registerCallback method success

  2. 再调用 play 方法,输出结果如下

    now thread: main
    onPlayStateChanged: true
    now thread: main
    onSongChanged: Server Song
    play method success

  3. 再调用 pause 方法,输出结果如下

    now thread: main
    onPlayStateChanged: false
    pause method success

  4. 最后调用 unregisterCallback 方法,输出结果如下

    unregisterCallback method success


二、RemoteCallbackList

1、基本介绍
  1. RemoteCallbackList 是 Android 专门为跨进程通信设计的一个工具类,它用于安全、自动地管理回调

  2. beginBroadcast 是 RemoteCallbackList 中用于安全遍历列表的一个方法,它配合 finishBroadcast 方法一起使用

  3. beginBroadcast 方法会固定当前要遍历的快照(记录当前所有存活回调的数量),在调用finishBroadcast 方法之前,列表不会变化

2、演示
(1)Callback
  • IPlayerCallback.aidl,这里是位于 src/main/java/com/my/common 包下
java 复制代码
package com.my.common;

interface IPlayerCallback {
    void onSongChanged(String songName);
    void onPlayStateChanged(boolean isPlaying);
}
(2)AIDL
  • IMyAidlInterface.aidl,这里是位于 src/main/java/com/my/common 包下
java 复制代码
package com.my.common;

import com.my.common.IPlayerCallback;

interface IMyAidlInterface {
    void play();
    void pause();
    void registerCallback(IPlayerCallback callback);
    void unregisterCallback(IPlayerCallback callback);
}
(3)Server
java 复制代码
private RemoteCallbackList<IPlayerCallback> callbackList = new RemoteCallbackList<>();
private String songName;
private boolean isPlaying = false;

private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {

    @Override
    public void play() throws RemoteException {
        if (isPlaying) return;
        isPlaying = true;

        boolean isSongNameChanged = false;
        if (songName == null) {
            songName = "Server Song";
            isSongNameChanged = true;
        }

        int n = callbackList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            IPlayerCallback callback = callbackList.getBroadcastItem(i);
            callback.onPlayStateChanged(isPlaying);
            if (isSongNameChanged) callback.onSongChanged(songName);
        }
        callbackList.finishBroadcast();
    }

    @Override
    public void pause() throws RemoteException {
        if (!isPlaying) return;
        isPlaying = false;

        int n = callbackList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            IPlayerCallback callback = callbackList.getBroadcastItem(i);
            callback.onPlayStateChanged(isPlaying);
        }
        callbackList.finishBroadcast();
    }

    @Override
    public void registerCallback(IPlayerCallback callback) throws RemoteException {
        callbackList.register(callback);
    }

    @Override
    public void unregisterCallback(IPlayerCallback callback) throws RemoteException {
        callbackList.unregister(callback);
    }
};
(4)Client
java 复制代码
try {
    myAidlInterface.play();
    Log.i(TAG, "play method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "play method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.pause();
    Log.i(TAG, "pause method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "pause method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.registerCallback(new IPlayerCallback.Stub() {
        @Override
        public void onSongChanged(String songName) throws RemoteException {
            Log.i(TAG, "now thread: " + Thread.currentThread().getName());
            Log.i(TAG, "onSongChanged: " + songName);
        }

        @Override
        public void onPlayStateChanged(boolean isPlaying) throws RemoteException {
            Log.i(TAG, "now thread: " + Thread.currentThread().getName());
            Log.i(TAG, "onPlayStateChanged: " + isPlaying);
        }
    });
    Log.i(TAG, "registerCallback method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "registerCallback method error: " + e.getMessage());
}
java 复制代码
try {
    myAidlInterface.unregisterCallback();
    Log.i(TAG, "unregisterCallback method success");
} catch (RemoteException e) {
    e.printStackTrace();
    Log.e(TAG, "unregisterCallback method error: " + e.getMessage());
}
(5)Test
  1. 先调用 registerCallback 方法,输出结果如下

    registerCallback method success

  2. 再调用 play 方法,输出结果如下

    now thread: main
    onPlayStateChanged: true
    now thread: main
    onSongChanged: Server Song
    play method success

  3. 再调用 pause 方法,输出结果如下

    now thread: main
    onPlayStateChanged: false
    pause method success

  4. 最后调用 unregisterCallback 方法,输出结果如下

    unregisterCallback method success


三、AIDL 安全校验

需求引入
  • 默认情况下,任何知道 AIDL 接口的应用都能绑定服务并调用方法,这可能导致数据泄露和恶意调用
1、权限校验
(1)Server
  1. AndroidManifest.xml,定义权限
xml 复制代码
<permission
    android:name="com.my.ACCESS_TEST_SERVICE"
    android:protectionLevel="normal" />
  1. 在 Binder 方法中校验权限
java 复制代码
int result = checkCallingPermission("com.my.ACCESS_TEST_SERVICE");
Log.i(TAG, "权限检查结果: " + result);
if (result == PackageManager.PERMISSION_DENIED) {
    Log.i(TAG, "权限拒绝");
    throw new SecurityException("权限拒绝");
}
Log.i(TAG, "权限通过");

// 执行其他业务逻辑
(2)Client
  • AndroidManifest.xml,声明权限
xml 复制代码
<uses-permission android:name="com.my.ACCESS_TEST_SERVICE" />
2、UID / PID 校验
  • Server:在 Binder 方法中校验 UID / PID
java 复制代码
int callingUid = Binder.getCallingUid();

if (callingUid != 1000 && callingUid != 1001) {
    throw new SecurityException("无权调用");
}

// 执行其他业务逻辑
3、包名白名单校验
  • Server:在 Binder 方法中校验包名白名单
java 复制代码
private static final List<String> ALLOWED_PACKAGES = Arrays.asList(
        "com.my.server",
        "com.my.client"
);

private boolean allowedPackagesCheck() {
    int callingUid = Binder.getCallingUid();
    String[] packages = getPackageManager().getPackagesForUid(callingUid);
    if (packages != null) {
        for (String pkg : packages) {
            Log.i(TAG, "package: " + pkg);
            if (ALLOWED_PACKAGES.contains(pkg)) {
                return true;
            }
        }
    }
    return false;
}
java 复制代码
if (!allowedPackagesCheck()) {
    Log.i(TAG, "调用者未被授权");
    throw new SecurityException("调用者未被授权");
}
Log.i(TAG, "调用者已被授权");

// 执行其他业务逻辑

四、protectionLevel(补充学习)

  • protectionLevel 是 Android 权限系统中定义权限风险等级的属性
  1. normal:普通级别,安装时自动授予,无需用户确认

  2. dangerous:危险级别,运行时需用户明确授权

  3. signature:签名级别,只有相同签名的应用才能获得权限,安装时自动授予

  4. signatureOrSystem:签名或系统级别,只有相同签名的应用才能获得权限,或者位于 Android 系统映像的专用文件夹中的应用才能获得权限,在 API 级别 23 中已废弃,是 signature|privileged 的旧同义词,建议使用 signature 代替

  • 参考文档 1:https://developer.android.google.cn/guide/topics/manifest/permission-element?hl=zh-cn

  • 参考文档 2:https://developer.android.google.cn/reference/android/R.attr#protectionLevel

相关推荐
q***751818 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
xuboyok218 小时前
Spring Boot管理用户数据
java·spring boot·后端
泯仲18 小时前
从零起步学习MySQL || 第十五章:MySQL 可重复读隔离级别:它是如何工作的?是否完全解决幻读?
android·学习·mysql
桌面运维家18 小时前
Windows医疗云桌面:安全高效解决方案
安全
阳光下的米雪18 小时前
记一次pgsql中with as语法的使用以及with as介绍
java·数据库
qq_3677193018 小时前
Android MQTT开源库paho.mqtt.android+MQTTX软件使用记录
android·java·开源·android mqtt开源库·mqttx软件使用
6+h18 小时前
【java IO】字节流详解
java·开发语言·python
Mem0rin18 小时前
[Java面向对象]接口的声明和实现继承
java·开发语言
毕设源码-邱学长18 小时前
【开题答辩全过程】以 基于Android的仓库管理系统的设计与实现为例,包含答辩的问题和答案
android
jing-ya18 小时前
day 53 图论part5
java·数据结构·算法·图论