public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("zxm", "onServiceConnected: ");
try {
ktvController = IKtvController.Stub.asInterface(service);
} catch (Exception e) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("zxm", "onServiceDisconnected: ");
}
}, Context.BIND_AUTO_CREATE);
}
}
![](https://img-blog.csdnimg.cn/358cb2be94d442bebc6d6999149645a5.png)
此时两端代码编写完毕,先后把服务和客户端的代码重新 run 一下,可以看到 pid 3919/3842 的两个进程
![](https://img-blog.csdnimg.cn/d8aaba7b0ebf4ef08f62346da31a261d.png)
![](https://img-blog.csdnimg.cn/3982fde030484dbe8f0e93780d04a70e.png)
启动日志:
绑定服务的时候服务端调用了 onCreate 和 onBind 方法。客户端则成功的获取到了 ktv 的控制器的 bind 实例。我们用控制器实例才试着处理下夸进程的数据传输一个为暂停、一个为播放:
![](https://img-blog.csdnimg.cn/b363c17130094de1945d8cbfa4320ee5.png)
历经了一些小的挫折之后,成功的把客户端的数据通过 aidl 的跨进程传输的方式输出到了服务端,见上方日志。
### 双向通信
上方我们完成了两个独立进程的跨进程通信,但是不知道大家有没有发现只有一个客户端向服务端的单向通信。但是实际在跨进程通信中双向通信的使用场景特别多。下面我们就接着刚刚的例子,当在客户端点击暂停和播放以后服务端给客户端回复一个成功或者失败的状态。
#### 1 修改 AIDL 文件,我们新增了一个状态的 aidl 文件我看看 AIDL 这块的变化
interface IConrtollerStatusListener {
void onPauseSuccess();
void onPauseFailed(int errorCode);
void onPlaySuccess();
void onPlayFailed(int errorCode);
}
import com.thunder.ktvaidlservice.IConrtollerStatusListener;
interface IKtvController {
void setOnControllerStatusListener(in IConrtollerStatusListener i);
void setPause(String pause);
void setPlay(String play);
}
分别重新添加到服务端以及客户端的工程中,然后 make 各自的项目。
#### 2 客户端的工作很简单,把监听设置上即可代码如下,注意回来后操作UI需要自己切线程:
private void bindKtvService() {
Intent intent = new Intent();
intent.setClassName(“com.thunder.ktvaidlservice”, “com.thunder.ktvaidlservice.KtvService”);
getApplicationContext().bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(“zxm”, "onServiceConnected: ");
try {
ktvController = IKtvController.Stub.asInterface(service);
ktvController.setOnControllerStatusListener(new IConrtollerStatusListener.Stub() {
@Override
public void onPauseSuccess() {
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(MainActivity.this, “onPauseSuccess”, Toast.LENGTH_SHORT).show();
});
}
@Override
public void onPauseFailed(int errorCode) {
new Handler(Looper.getMainLooper()).post(() -> {
Toast.makeText(MainActivity.this, "onPauseFailed" + errorCode, Toast.LENGTH_SHORT).show();
});
}
@Override
public void onPlaySuccess() {
Toast.makeText(MainActivity.this, "onPlaySuccess", Toast.LENGTH_SHORT).show();
}
@Override
public void onPlayFailed(int errorCode) throws RemoteException {
Toast.makeText(MainActivity.this, "onPlayFailed" + errorCode, Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("zxm", "onServiceDisconnected: ");
}
}, Context.BIND_AUTO_CREATE);
}
#### 3 服务端代码,我们来回调暂停和播放。各自模拟一个 1 秒的耗时操作,代码如下:
private static class KtvBinder extends IKtvController.Stub {
private IConrtollerStatusListener listener;
@Override
public void setOnControllerStatusListener(IConrtollerStatusListener i) throws RemoteException {
listener = i;
}
@Override
public void setPause(String pause) throws RemoteException {
Log.e("zxm", pause);
//模拟暂停耗时 1000 毫秒
if (listener != null) {
new Thread(() -> {
try {
Thread.sleep(1000);
if (System.currentTimeMillis() % 2 == 0) {
listener.onPauseSuccess();
} else {
listener.onPauseFailed(1002);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException remoteException) {
remoteException.printStackTrace();
}
}).start();
}
}
@Override
public void setPlay(String play) throws RemoteException {
Log.e("zxm", play);
//模拟播放耗时 1000 毫秒
if (listener != null) {
new Thread(() -> {
try {
Thread.sleep(1000);
if (System.currentTimeMillis() % 2 == 0) {
listener.onPlaySuccess();
} else {
listener.onPlayFailed(1001);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException remoteException) {
remoteException.printStackTrace();
}
}).start();
}
}
}
这样既完成了一个两个进程之间的双向通信,我们来看下 UI 效果:
![](https://img-blog.csdnimg.cn/fe9407b036e34d9fbc1d4292cf2111e9.png)
![](https://img-blog.csdnimg.cn/285f3a0342184c29a87e5bce9f2d5210.png)
失败和成功都是从另外一个进程传回来,包括一次错误码的数据输出。
### in out inout
注意到我们上面修改的 AIDL 文件代码有的 in ,这和我们平时看到的 Java 代码不一样。
![](https://img-blog.csdnimg.cn/3c827a4fc238416f8eb2795cafaebf9e.png)
不妨我们把 AIDL 的这个 in 修改为 out 试试?看看是否还能进行服务端返回给客户端的通信。
![](https://img-blog.csdnimg.cn/d3cdf960a81c473696b97e0fe7ecb856.png)
out 直接报错
![](https://img-blog.csdnimg.cn/c0291870b16746d1aa1825ed52862c39.png)
inout 从字面理解是支持双流向,但是编译也报错
我们来看下这篇文章的解释
(https://maimai.cn/article/detail?fid=1609989144&efid=hY89uIb6wFrJ4mChypxecQ)
### 注意事项
#### 1 点击设置错误
2021-10-16 14:33:07.061 3919-3919/com.thunder.ktvaidlclient E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.thunder.ktvaidlclient, PID: 3919
java.lang.IllegalStateException: Could not execute method for android:onClick
at androidx.appcompat.app.AppCompatViewInflater D e c l a r e d O n C l i c k L i s t e n e r . o n C l i c k ( A p p C o m p a t V i e w I n f l a t e r . j a v a : 414 ) a t a n d r o i d . v i e w . V i e w . p e r f o r m C l i c k ( V i e w . j a v a : 6256 ) a t c o m . g o o g l e . a n d r o i d . m a t e r i a l . b u t t o n . M a t e r i a l B u t t o n . p e r f o r m C l i c k ( M a t e r i a l B u t t o n . j a v a : 992 ) a t a n d r o i d . v i e w . V i e w DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414) at android.view.View.performClick(View.java:6256) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) at android.view.View Declare