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

相关推荐
一个有梦有戏的人1 小时前
Java 网络编程核心:BIO、NIO、AIO IO 模型深度解析与实战
java·网络·后端·netty·nio
这波不该贪内存的1 小时前
Linux文件编程:流与操作全解析
java·服务器·前端
重生之后端学习1 小时前
35. 搜索插入位置
java·数据结构·算法·leetcode·职场和发展·深度优先
昱宸星光1 小时前
Xnio源码分析
java·jvm·spring
黑白极客1 小时前
ACP大模型认证刷题工具开源,助力高效备考
java·ai·github·llama·认证
程序员南飞1 小时前
算法笔试-求一个字符串的所有子串
java·开发语言·数据结构·python·算法·排序算法
MyY_DO1 小时前
你应该有属于自己的作品
java
苏天夏2 小时前
Passport 插件:Typecho 密码安全的技术守护者
安全·网络安全·php
清风徐来QCQ2 小时前
java总结
java·开发语言·数据结构