通過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() 函數來把服務端的數據返回給客戶端。
以此完成客戶端和服務端的雙向通信。
下一篇,我們來動手將服務端預置在系統內。