如何在Android中使用RemoteCallbackList?

在 Android 中,RemoteCallbackList是专门用于跨进程回调管理 的工具类,能解决多进程场景下回调的注册、解注册、批量通知及无效回调自动清理问题。以下是完整的使用步骤 + 代码示例,涵盖 AIDL 定义、服务端实现、客户端调用全流程:

一、核心前置知识

RemoteCallbackList的核心作用是管理实现IInterface的回调接口(通常是 AIDL 定义的接口),需配合AIDL 跨进程通信使用。其关键特性:

  • 自动监听 Binder 死亡,清理崩溃客户端的回调;
  • 线程安全的注册 / 解注册 / 遍历操作;
  • 批量通知所有有效回调。

二、使用步骤(完整示例)

步骤 1:定义 AIDL 接口

需创建两个 AIDL 文件:服务接口 (供客户端调用注册 / 解注册)和回调接口(服务端向客户端推送事件)。

1.1 定义回调接口(IStatusCallback.aidl
复制代码
// IStatusCallback.aidl
package com.example.remotecallbackdemo;

// 跨进程回调接口:服务端通过此接口向客户端推送状态
interface IStatusCallback {
    /**
     * 状态变化回调方法
     * @param status 状态值(如0=正常,1=低电量,2=故障)
     * @param msg 状态描述
     */
    void onStatusChanged(int status, String msg);
}
1.2 定义服务接口(IMyRemoteService.aidl
复制代码
// IMyRemoteService.aidl
package com.example.remotecallbackdemo;

// 导入回调接口
import com.example.remotecallbackdemo.IStatusCallback;

// 服务端暴露的接口:供客户端注册/解注册回调
interface IMyRemoteService {
    /**
     * 注册回调
     * @param callback 客户端传入的回调实例
     */
    void registerCallback(IStatusCallback callback);

    /**
     * 解注册回调
     * @param callback 客户端传入的回调实例
     */
    void unregisterCallback(IStatusCallback callback);

    /**
     * 模拟服务端主动触发状态更新(测试用)
     */
    void triggerStatusUpdate(int status, String msg);
}
步骤 2:服务端实现(RemoteService.java)

在 Service 中初始化RemoteCallbackList,实现 AIDL 接口的方法,并管理回调通知:

复制代码
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

public class RemoteService extends Service {
    private static final String TAG = "RemoteService";

    // 核心:初始化RemoteCallbackList,泛型为回调接口类型
    private final RemoteCallbackList<IStatusCallback> mCallbackList = new RemoteCallbackList<>();

    // 实现AIDL服务接口的Stub类(服务端核心逻辑)
    private final IMyRemoteService.Stub mBinder = new IMyRemoteService.Stub() {
        @Override
        public void registerCallback(IStatusCallback callback) throws RemoteException {
            if (callback != null) {
                mCallbackList.register(callback);
                Log.d(TAG, "回调已注册,当前回调数:" + mCallbackList.getRegisteredCallbackCount());
            }
        }

        @Override
        public void unregisterCallback(IStatusCallback callback) throws RemoteException {
            if (callback != null) {
                boolean unregistered = mCallbackList.unregister(callback);
                if (unregistered) {
                    Log.d(TAG, "回调已解注册,当前回调数:" + mCallbackList.getRegisteredCallbackCount());
                }
            }
        }

        @Override
        public void triggerStatusUpdate(int status, String msg) throws RemoteException {
            // 触发状态更新:通知所有注册的回调
            notifyAllCallbacks(status, msg);
        }
    };

    /**
     * 批量通知所有有效回调
     */
    private void notifyAllCallbacks(int status, String msg) {
        // 1. 开始遍历回调(必须调用beginBroadcast())
        int callbackCount = mCallbackList.beginBroadcast();
        Log.d(TAG, "开始通知回调,有效回调数:" + callbackCount);

        // 2. 遍历所有回调并调用方法
        for (int i = 0; i < callbackCount; i++) {
            IStatusCallback callback = mCallbackList.getBroadcastItem(i);
            if (callback != null) {
                try {
                    // 调用客户端的回调方法(跨进程通信)
                    callback.onStatusChanged(status, msg);
                } catch (RemoteException e) {
                    // 客户端进程崩溃,无需处理:finishBroadcast()会自动清理
                    Log.e(TAG, "回调调用失败,客户端已断开:" + e.getMessage());
                }
            }
        }

        // 3. 结束遍历(必须调用finishBroadcast(),自动清理无效回调)
        mCallbackList.finishBroadcast();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回服务Binder对象,供客户端绑定
        return mBinder;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 服务销毁时清空回调(可选,RemoteCallbackList会自动释放)
        mCallbackList.kill();
    }
}
步骤 3:客户端实现(Activity/Fragment)

客户端通过绑定 Service 获取服务接口,注册回调并处理服务端的事件推送:

复制代码
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import com.example.remotecallbackdemo.IMyRemoteService;
import com.example.remotecallbackdemo.IStatusCallback;

public class ClientActivity extends AppCompatActivity {
    private static final String TAG = "ClientActivity";
    private IMyRemoteService mRemoteService; // 服务端接口实例
    private boolean isServiceBound = false;

    // 客户端回调实例(必须是Stub子类,用于跨进程通信)
    private final IStatusCallback mStatusCallback = new IStatusCallback.Stub() {
        @Override
        public void onStatusChanged(int status, String msg) throws RemoteException {
            // 服务端回调触发(运行在Binder线程池,需切换到主线程更新UI)
            runOnUiThread(() -> {
                Log.d(TAG, "收到服务端回调:status=" + status + ", msg=" + msg);
                // TODO: 更新UI(如显示状态提示)
            });
        }
    };

    // 服务连接回调
    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 绑定成功:获取服务端接口实例
            mRemoteService = IMyRemoteService.Stub.asInterface(service);
            isServiceBound = true;
            Log.d(TAG, "服务绑定成功");

            // 注册回调到服务端
            try {
                mRemoteService.registerCallback(mStatusCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "注册回调失败:" + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 服务断开(如服务崩溃)
            mRemoteService = null;
            isServiceBound = false;
            Log.d(TAG, "服务断开连接");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_client);

        // 绑定服务按钮
        Button btnBindService = findViewById(R.id.btn_bind_service);
        btnBindService.setOnClickListener(v -> bindRemoteService());

        // 触发服务端更新状态按钮(测试用)
        Button btnTriggerUpdate = findViewById(R.id.btn_trigger_update);
        btnTriggerUpdate.setOnClickListener(v -> triggerStatusUpdate());
    }

    /**
     * 绑定远程服务
     */
    private void bindRemoteService() {
        Intent intent = new Intent(this, RemoteService.class);
        // 注意:Android 5.0+ 隐式Intent绑定Service需设置包名,或用显式Intent
        intent.setPackage("com.example.remotecallbackdemo");
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    /**
     * 测试:触发服务端更新状态,通知所有客户端
     */
    private void triggerStatusUpdate() {
        if (!isServiceBound || mRemoteService == null) {
            Log.w(TAG, "服务未绑定,无法触发更新");
            return;
        }
        try {
            // 调用服务端方法,触发回调通知
            mRemoteService.triggerStatusUpdate(1, "低电量警告:剩余续航不足50km");
        } catch (RemoteException e) {
            Log.e(TAG, "触发状态更新失败:" + e.getMessage());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解注册回调 + 解绑服务,避免内存泄漏
        if (isServiceBound && mRemoteService != null) {
            try {
                mRemoteService.unregisterCallback(mStatusCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "解注册回调失败:" + e.getMessage());
            }
            unbindService(mServiceConnection);
            isServiceBound = false;
        }
    }
}
步骤 4:配置 Service(AndroidManifest.xml)

确保 Service 在 Manifest 中注册(若为跨应用通信,需设置android:exported="true"):

复制代码
<service
    android:name=".RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote"> <!-- 可选:让Service运行在独立进程,模拟跨进程场景 -->
</service>

三、关键注意事项

  1. 回调遍历规范 必须通过beginBroadcast()开始遍历,finishBroadcast()结束遍历,二者必须配对使用finishBroadcast()会自动移除已死亡的回调(如客户端崩溃)。

  2. 注册 / 解注册匹配 客户端注册和解注册必须使用同一个回调实例 (如上例的mStatusCallback),否则RemoteCallbackList无法识别(内部通过 Binder 对象的唯一标识匹配)。

  3. 线程处理 服务端调用回调方法时,客户端的onStatusChanged()运行在Binder 线程池 ,需通过runOnUiThread()切换到主线程更新 UI。

  4. 内存泄漏防护 客户端退出时必须解注册回调 + 解绑服务 ;服务端销毁时可调用mCallbackList.kill()清空所有回调。

  5. 跨应用通信 若服务端和客户端属于不同应用,需确保 AIDL 文件的包名、接口名完全一致,且 Service 的android:exported="true"

四、与普通集合(如 ArrayList)的对比

特性 RemoteCallbackList ArrayList<IStatusCallback>
跨进程回调管理 原生支持,自动处理 Binder 死亡 需手动检测,易出现空指针
线程安全 所有操作线程安全 需手动加锁(如 synchronized)
无效回调清理 自动清理(finishBroadcast) 需遍历检测 RemoteException
批量通知效率 高效(内部优化遍历逻辑) 低效(需逐个处理异常)

五、适用场景

  • 多进程通信中的事件推送(如车载系统中服务向多个应用通知车辆状态);
  • 需要管理大量跨进程回调的场景(如推送服务向多个客户端推送消息);
  • 需自动清理无效回调的场景(避免因客户端崩溃导致内存泄漏)。

通过RemoteCallbackList,可安全、高效地实现 Android 跨进程回调管理,是多进程通信的最佳实践之一。

相关推荐
Digitally2 小时前
如何将照片从vivo手机传输到电脑
android
小兔薯了11 小时前
7. LNMP-wordpress
android·运维·服务器·数据库·nginx·php
L***d67011 小时前
mysql的主从配置
android·mysql·adb
Sammyyyyy13 小时前
PHP 8.5 新特性:10 大核心改进
android·php·android studio
TO_ZRG13 小时前
Unity 通过 NativePlugin 接入Android SDK 指南
android·unity·游戏引擎
n***840713 小时前
Springboot-配置文件中敏感信息的加密:三种加密保护方法比较
android·前端·后端
方白羽15 小时前
一次由 by lazy 引发的“数据倒灌”,深入理解 `by`关键字、`lazy`函数的本质
android·kotlin·app
v***553415 小时前
MySQL 中如何进行 SQL 调优
android·sql·mysql