安卓 RemoteCallbackList 的使用 (應用篇)


通過aidl,我們可以實現client(后稱客戶端)和server(服務端)的雙向通信,有時候server和client處於不同的進程當中,如果client意外退出,server再向client發送消息的話,就有可能導致server端也退出。

安卓提供了 RemoteCallbackList 來為我們隱式解決了這種問題。

下面來看一個示范。

首先我們定義用於客戶端向服務端通信的一個 aidl

// ICallBackTestInterface.aidl
package com.callback;

// Declare any non-default types here with import statements

import com.callback.ICallbackTestCallback;

interface ICallBackTestInterface {
    // 向服務端注冊客戶端回調
    void register(ICallbackTestCallback callback);
    // 向服務端注銷客戶端回調
    void unregister(ICallbackTestCallback callback);
    // 向服務端發送消息
    void callServer(String msg);
}

其內的 ICallbackTestCallback 也是一個 aidl,其內容如下

// ICallbackTestCallback.aidl
package com.callback;

// Declare any non-default types here with import statements

interface ICallbackTestCallback {
    /**
     * 服務端調用客戶端的回調
     **/
    void onReceived(String msg);
}

編譯之后我們可以看到,實際上 aidl 編譯之后就會變成類似這樣的內容

public interface ICallbackTestCallback extends android.os.IInterface
{
    public static abstract class Stub extends android.os.Binder implements com.callback.ICallbackTestCallback{
...

這個也是aidl的核心,實際上這個Stub是一個 Binder 的實現,並且還實現了我們定義在 aidl 里面的接口

服務端

服務端比較簡單,直接貼代碼

//callbackserver.java
package com.callback.server;

import androidx.annotation.Nullable;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.callback.ICallBackTestInterface;
import com.callback.ICallbackTestCallback;

public class callbackserver extends Service {

    private final String TAG = "testcallback";

    // 這里的 clients 就是 RemoteCallbackList 的用法了,我們用它來存儲注冊了的客戶端,然后在某些時候向注冊了的客戶端發送消息。
    private final RemoteCallbackList<ICallbackTestCallback> clients = new RemoteCallbackList<>();

    CallBackServer server = new CallBackServer();

    private boolean serverRunning = false;

    @Override
    public void onCreate() {
        Log.d(TAG,"callback test server create");
        // service 創建的時候,開一個線程去向注冊了的客戶端發送消息
        serverRunning = true;
        new Thread(serverRunnable).start();
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG,"callback test server destroy");
        serverRunning = false;
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 將一個實現了 ICallBackTestInterface.Stub 的Binder對象返回
        // 客戶端調用 bind service 時會拿到一個返回的 Binder 對象,就是這里的 server,也就是一個
        // ICallBackTestInterface.Stub 實例
        return server;
    }

    /**
     * 返回給客戶端的 Binder 對象的實現
     */
    private class CallBackServer extends ICallBackTestInterface.Stub {

        @Override
        public void register(ICallbackTestCallback callback) throws RemoteException {
            Log.d(TAG,"register callback from pid=" + Binder.getCallingPid());
            clients.register(callback);
        }

        @Override
        public void unregister(ICallbackTestCallback callback) throws RemoteException {
            Log.d(TAG,"unregister callback from pid=" + Binder.getCallingPid());
            clients.unregister(callback);
        }

        @Override
        public void callServer(String msg) throws RemoteException {
            Log.d(TAG,"received msg: " + msg + " . from pid=" + Binder.getCallingPid());
        }
    }

    // 向客戶端發送消息的具體實現
    // 簡單的做一個自增運算,然后發送回客戶端
    private Runnable serverRunnable = () ->{
        int count = 0;
        while(serverRunning){
            try {
                Thread.sleep(500);
                noteClients(Integer.toString(count++));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * 這里就是 RemoteCallbackList 的關鍵用法了
     * beginBroadcast 和 finishBroadcast 需要配套使用
     * beginBroadcast 會返回注冊了的客戶端數量,然后開一個循環依次取出客戶端注冊的回調,並調用回調內的
     * onReceived 函數。這個函數需要客戶端實現 ICallbackTestCallback.Stub 之后,注冊給服務端
     * @param msg
     */
    private void noteClients(String msg){
        int cb = clients.beginBroadcast();
        for(int i=0;i<cb;i++){
            try {
                clients.getBroadcastItem(i).onReceived(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        clients.finishBroadcast();
    }
}

服務端對應的 AndroidMenifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.callback.server">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Server">
        <service android:name=".callbackserver"
            android:exported="true">

        </service>
    </application>

</manifest>

簡單的表示有一個service就好,其他的內容和一般的app一樣。
p.s: 這里我是將服務端和客戶端分為兩個app來實現了。

服務端的文件的結構如下

C:.
├─aidl
│  └─com
│      └─callback
│            ICallbackTestCallback.aidl
│            ICallBackTestInterface.aidl
├─java
│  └─com
│      └─callback
│          └─server
│                  callbackserver.java
└─res
    ├─drawable
    ├─drawable-v24
    ├─layout
    ├─mipmap-anydpi-v26
    ├─mipmap-hdpi
    ├─mipmap-mdpi
    ├─mipmap-xhdpi
    ├─mipmap-xxhdpi
    ├─mipmap-xxxhdpi
    ├─values
    └─values-night

服務端的全部內容如上面所示。

客戶端

客戶端我們使用兩個獨立的進程(當時在同一個app里面)
客戶端的 AndroidMenifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.callback.client">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Client"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Client">
        <activity android:name=".ActivityB"
            android:launchMode="singleInstance"
            android:process="com.callback.client.ActivityB"
            android:label="Activityb">
        </activity>

        <activity
            android:name=".ActivityA"
            android:launchMode="singleInstance"
            android:process="com.callback.client.ActivityA"
            android:label="ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

我定義了兩個Activity,一個ActivityA,一個ActivityB。
ActivityA的實現如下

package com.callback.client;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import com.callback.ICallbackTestCallback;
import com.callback.ICallBackTestInterface;

public class ActivityA extends AppCompatActivity implements View.OnClickListener{

    private final String TAG = "testcallback";

    private boolean bound = false;

    ICallBackTestInterface remoteServer = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button activityb = findViewById(R.id.startactivity);
        activityb.setText("open activity b");
        initClickListener();
    }

    @Override
    protected void onResume() {
        if(bound){
            registerCallback();
        }
        super.onResume();
    }

    @Override
    protected void onPause() {
        if(bound){
            unregisterCallback();
        }
        super.onPause();
    }



    private final ServiceConnection serviceConnection = new ServiceConnection(){

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            remoteServer = ICallBackTestInterface.Stub.asInterface(service);
            registerCallback();
            bound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    private ICallbackTestCallback callback = new ICallbackTestCallback.Stub(){

        @Override
        public void onReceived(String msg) throws RemoteException {
            Log.d(TAG,"received msg: " + msg + " . from server pid=" + Binder.getCallingPid());
        }
    };

    private void initClickListener(){
        Button button = findViewById(R.id.registerBotton);
        button.setOnClickListener(this);
        button=findViewById(R.id.unregisterBotton);
        button.setOnClickListener(this);
        button=findViewById(R.id.bindServer);
        button.setOnClickListener(this);
        button=findViewById(R.id.startactivity);
        button.setOnClickListener(this);
    }

    private void unregisterCallback(){
        if(remoteServer != null){
            try {
                remoteServer.unregister(callback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e(TAG," null remoteServer");
        }
    }

    private void callServer(String msg){
        if(remoteServer != null){
            try {
                remoteServer.callServer(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e(TAG," null remoteServer");
        }
    }

    public void bindServer(){
        Intent serverIntent = new Intent();
        // 這里通過包名和class名來Bindservice。
        serverIntent.setComponent(new ComponentName("com.callback.server","com.callback.server.callbackserver"));
        bindService(serverIntent,serviceConnection,Context.BIND_AUTO_CREATE);
    }

    private void startActivity(){
        Intent intent = new Intent(ActivityA.this,ActivityB.class);
        startActivity(intent);
    }

    public void registerCallback() {
        if(remoteServer != null){
            try {
                remoteServer.register(callback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e(TAG," null remoteServer");
        }
    }

    // Activity 實現了 View.OnClickListener 的話,需要實現對應的接口 onClick
    @Override
    public void onClick(View view) {
        int id = view.getId();
        switch (id){
            case R.id.registerBotton:
                registerCallback();
                break;
            case R.id.unregisterBotton:
                unregisterCallback();
                break;
            case R.id.bindServer:
                bindServer();
                break;
            case R.id.startactivity:
                startActivity();
                break;
        }
    }
}

ActivityA對應的資源文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ActivityA">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/registerBotton"
        android:textAllCaps="false"
        android:layout_marginLeft="5dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="register"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/unregisterBotton"
        android:textAllCaps="false"
        android:layout_marginLeft="5dp"
        app:layout_constraintTop_toBottomOf="@id/registerBotton"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="unregister"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/bindServer"
        android:textAllCaps="false"
        android:layout_marginLeft="5dp"
        app:layout_constraintTop_toBottomOf="@id/unregisterBotton"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="Bind server"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/startactivity"
        android:textAllCaps="false"
        android:layout_marginLeft="5dp"
        app:layout_constraintTop_toBottomOf="@id/bindServer"
        app:layout_constraintLeft_toLeftOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

ActivityB 的實現和 ActivityA基本一致。
客戶端的文件結構如下

C:.
├─aidl
│  └─com
│      └─callback
│              ICallbackTestCallback.aidl
│              ICallBackTestInterface.aidl
├─java
│  └─com
│      └─callback
│          └─client
│                  ActivityA.java
│                  ActivityB.java
└─res
    ├─drawable
    ├─drawable-v24
    ├─layout
    │      activity_main.xml
    ├─mipmap-anydpi-v26
    ├─mipmap-hdpi
    ├─mipmap-mdpi
    ├─mipmap-xhdpi
    ├─mipmap-xxhdpi
    ├─mipmap-xxxhdpi
    ├─values
    └─values-night

然后我們可以得到這樣一個應用

上面有4個按鈕,分別用來向服務端注冊和注銷自己,綁定服務端,和打開另外一個Activity。

使用

在分屏模式下可以同時打開兩個Activity,分別點擊兩個Activity內的bind server和register。就可以在logcat內看到對應的內容了

05-08 12:48:45.938  6671  6671 D testcallback: callback test server create
05-08 12:48:45.940  6671  6687 D testcallback: register callback from pid=6631
05-08 12:48:46.439  6631  6653 D testcallback: received msg: 0 . from server pid=6671
05-08 12:48:46.940  6631  6653 D testcallback: received msg: 1 . from server pid=6671
05-08 12:48:47.441  6631  6653 D testcallback: received msg: 2 . from server pid=6671
05-08 12:48:47.944  6631  6653 D testcallback: received msg: 3 . from server pid=6671
05-08 12:48:48.409  6671  6687 D testcallback: register callback from pid=6597
05-08 12:48:48.445  6597  6614 D testcallback: received msg: 4 . from server pid=6671
05-08 12:48:48.446  6631  6648 D testcallback: received msg: 4 . from server pid=6671
05-08 12:48:48.948  6597  6614 D testcallback: received msg: 5 . from server pid=6671
05-08 12:48:48.949  6631  6648 D testcallback: received msg: 5 . from server pid=6671
05-08 12:48:49.451  6597  6614 D testcallback: received msg: 6 . from server pid=6671
05-08 12:48:49.451  6631  6648 D testcallback: received msg: 6 . from server pid=6671
05-08 12:48:49.953  6597  6614 D testcallback: received msg: 7 . from server pid=6671
05-08 12:48:49.954  6631  6648 D testcallback: received msg: 7 . from server pid=6671
05-08 12:48:50.456  6597  6614 D testcallback: received msg: 8 . from server pid=6671
05-08 12:48:50.457  6631  6648 D testcallback: received msg: 8 . from server pid=6671
05-08 12:48:50.958  6597  6614 D testcallback: received msg: 9 . from server pid=6671
05-08 12:48:50.959  6631  6648 D testcallback: received msg: 9 . from server pid=6671
05-08 12:48:51.461  6597  6614 D testcallback: received msg: 10 . from server pid=6671
05-08 12:48:51.461  6631  6648 D testcallback: received msg: 10 . from server pid=6671
05-08 12:48:51.964  6597  6614 D testcallback: received msg: 11 . from server pid=6671
05-08 12:48:51.964  6631  6648 D testcallback: received msg: 11 . from server pid=6671
05-08 12:48:52.465  6597  6614 D testcallback: received msg: 12 . from server pid=6671
05-08 12:48:52.466  6631  6648 D testcallback: received msg: 12 . from server pid=6671
05-08 12:48:52.967  6597  6614 D testcallback: received msg: 13 . from server pid=6671
05-08 12:48:52.967  6631  6648 D testcallback: received msg: 13 . from server pid=6671
05-08 12:48:53.469  6597  6614 D testcallback: received msg: 14 . from server pid=6671
05-08 12:48:53.470  6631  6648 D testcallback: received msg: 14 . from server pid=6671
05-08 12:48:53.972  6597  6614 D testcallback: received msg: 15 . from server pid=6671
05-08 12:48:53.972  6631  6648 D testcallback: received msg: 15 . from server pid=6671
05-08 12:48:54.474  6597  6614 D testcallback: received msg: 16 . from server pid=6671
05-08 12:48:54.475  6631  6648 D testcallback: received msg: 16 . from server pid=6671
05-08 12:48:54.977  6597  6614 D testcallback: received msg: 17 . from server pid=6671
05-08 12:48:54.977  6631  6648 D testcallback: received msg: 17 . from server pid=6671
05-08 12:48:55.478  6597  6614 D testcallback: received msg: 18 . from server pid=6671
05-08 12:48:55.479  6631  6648 D testcallback: received msg: 18 . from server pid=6671
05-08 12:48:55.981  6597  6614 D testcallback: received msg: 19 . from server pid=6671
05-08 12:48:55.982  6631  6648 D testcallback: received msg: 19 . from server pid=6671
05-08 12:48:56.484  6597  6614 D testcallback: received msg: 20 . from server pid=6671
05-08 12:48:56.484  6631  6648 D testcallback: received msg: 20 . from server pid=6671
05-08 12:48:56.987  6597  6614 D testcallback: received msg: 21 . from server pid=6671
05-08 12:48:56.987  6631  6648 D testcallback: received msg: 21 . from server pid=6671
05-08 12:48:57.489  6597  6614 D testcallback: received msg: 22 . from server pid=6671
05-08 12:48:57.489  6631  6648 D testcallback: received msg: 22 . from server pid=6671
05-08 12:48:57.992  6597  6614 D testcallback: received msg: 23 . from server pid=6671
05-08 12:48:57.992  6631  6648 D testcallback: received msg: 23 . from server pid=6671
05-08 12:48:58.494  6597  6614 D testcallback: received msg: 24 . from server pid=6671
05-08 12:48:58.495  6631  6648 D testcallback: received msg: 24 . from server pid=6671
05-08 12:48:58.997  6597  6614 D testcallback: received msg: 25 . from server pid=6671
05-08 12:48:58.998  6631  6648 D testcallback: received msg: 25 . from server pid=6671
05-08 12:48:59.501  6597  6614 D testcallback: received msg: 26 . from server pid=6671
05-08 12:48:59.501  6631  6648 D testcallback: received msg: 26 . from server pid=6671
05-08 12:49:00.003  6597  6614 D testcallback: received msg: 27 . from server pid=6671
05-08 12:49:00.004  6631  6648 D testcallback: received msg: 27 . from server pid=6671
05-08 12:49:00.506  6597  6614 D testcallback: received msg: 28 . from server pid=6671
05-08 12:49:00.506  6631  6648 D testcallback: received msg: 28 . from server pid=6671
05-08 12:49:01.009  6597  6614 D testcallback: received msg: 29 . from server pid=6671
05-08 12:49:01.010  6631  6648 D testcallback: received msg: 29 . from server pid=6671
05-08 12:49:01.511  6597  6614 D testcallback: received msg: 30 . from server pid=6671
05-08 12:49:01.512  6631  6648 D testcallback: received msg: 30 . from server pid=6671
05-08 12:49:02.014  6597  6614 D testcallback: received msg: 31 . from server pid=6671
05-08 12:49:02.014  6631  6648 D testcallback: received msg: 31 . from server pid=6671
05-08 12:49:02.517  6597  6614 D testcallback: received msg: 32 . from server pid=6671
05-08 12:49:02.517  6631  6648 D testcallback: received msg: 32 . from server pid=6671
05-08 12:49:03.019  6597  6614 D testcallback: received msg: 33 . from server pid=6671
05-08 12:49:03.019  6631  6648 D testcallback: received msg: 33 . from server pid=6671
05-08 12:49:03.521  6597  6614 D testcallback: received msg: 34 . from server pid=6671
05-08 12:49:03.522  6631  6648 D testcallback: received msg: 34 . from server pid=6671
05-08 12:49:04.024  6597  6614 D testcallback: received msg: 35 . from server pid=6671
05-08 12:49:04.024  6631  6648 D testcallback: received msg: 35 . from server pid=6671
05-08 12:49:04.527  6597  6614 D testcallback: received msg: 36 . from server pid=6671
05-08 12:49:04.527  6631  6648 D testcallback: received msg: 36 . from server pid=6671
05-08 12:49:05.030  6597  6614 D testcallback: received msg: 37 . from server pid=6671
05-08 12:49:05.030  6631  6648 D testcallback: received msg: 37 . from server pid=6671
05-08 12:49:05.329  6671  6687 D testcallback: unregister callback from pid=6597
05-08 12:49:05.533  6631  6648 D testcallback: received msg: 38 . from server pid=6671
05-08 12:49:06.034  6631  6648 D testcallback: received msg: 39 . from server pid=6671
05-08 12:49:06.537  6631  6648 D testcallback: received msg: 40 . from server pid=6671
05-08 12:49:07.039  6631  6648 D testcallback: received msg: 41 . from server pid=6671
05-08 12:49:07.542  6631  6648 D testcallback: received msg: 42 . from server pid=6671
05-08 12:49:08.043  6631  6648 D testcallback: received msg: 43 . from server pid=6671
05-08 12:49:08.545  6631  6648 D testcallback: received msg: 44 . from server pid=6671
05-08 12:49:09.046  6631  6648 D testcallback: received msg: 45 . from server pid=6671
05-08 12:49:09.548  6631  6648 D testcallback: received msg: 46 . from server pid=6671
05-08 12:49:10.051  6631  6648 D testcallback: received msg: 47 . from server pid=6671
05-08 12:49:10.513  6671  6687 D testcallback: unregister callback from pid=6631
05-08 12:49:14.176  6671  6687 D testcallback: unregister callback from pid=6631
05-08 12:49:14.896  6671  6687 D testcallback: unregister callback from pid=6597

服務端的功能是自增一個整數,並不斷的返回給客戶端。客戶端通過bindservice拿到了服務端的Binder對象之后,通過調用服務端Binder對象的register和unregister來注冊和注銷實現的 ICallbackTestCallback 。

服務單通過 RemoteCallbackList 取到客戶端注冊的 ICallbackTestCallback 之后,調用對應的 onReceived() 函數來把服務端的數據返回給客戶端。

以此完成客戶端和服務端的雙向通信。

下一篇,我們來動手將服務端預置在系統內。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM