0%

安卓 RemoteCallbackList 的使用 (应用篇)

通过aidl,我们可以实现client(后称客户端)和server(服务端)的双向通信,有时候server和client处于不同的进程当中,如果client意外退出,server再向client发送消息的话,就有可能导致server端也退出。

安卓提供了 RemoteCallbackList 来为我们隐式解决了这种问题。

下面来看一个示范。

首先我们定义用于客户端向服务端通信的一个 aidl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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,其内容如下

1
2
3
4
5
6
7
8
9
10
11
// ICallbackTestCallback.aidl
package com.callback;

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

interface ICallbackTestCallback {
/**
* 服务端调用客户端的回调
**/
void onReceived(String msg);
}

编译之后我们可以看到,实际上 aidl 编译之后就会变成类似这样的内容

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

这个也是aidl的核心,实际上这个Stub是一个 Binder 的实现,并且还实现了我们定义在 aidl 里面的接口

服务端

服务端比较简单,直接贴代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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来实现了。

服务端的文件的结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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基本一致。
客户端的文件结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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内看到对应的内容了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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