前景
在某些业务场景下,我们需要在应用中单独开启一个进程进行一些操作。比如性能监控,如果让原始业务和性能监控本身的业务跑在同一个进程下,那么就会导致性能统计的数据的失真。
而进程间通信,一般采用AIDL机制的客户端与服务端通信。
AIDL基础
AIDL只能传递如下几类数据:
- java 基本数据类型
- 字符串类型 String 和 CharSequence
- 集合类型 List 和 Map
- 自定义的 Parcelable
- 其他 aidl 接口
当传递自定义 Parcelable 时,有三处地方需要注意:
- 必须要 import 这个 Parcelable ,即使这个 Parcelable 跟当前的 aidl 在同一个包下。
- 参数还需要用 in out inout三种修饰符修饰。in :表示参数由客户端设置;out :表示参数由服务端设置;inout:表示客户端和服务端皆可设置
- 必须单独新建一个 aidl 文件来声明 Parcelable,用关键字 parcelable
当传递其他 aidl 接口时,同样必须要 import 这个 aidl 文件
编写完 aidl 文件后,make一下工程,会在 build 下的 generated 下的 source 下的 aidl 目录生成对应的接口类文件。aidl 接口其实就是 API 接口,通过实现对应接口类的 Stub 子类来实现具体的 API 逻辑;通过对应接口类的 Stub 子类的 asInterface 方法得到具体的实现类,调用具体的 API 方法。
AIDL通信结构
一个基本的客户端服务端的通信结构一般包括如下功能
客户端的功能
- 主动连接服务端
- 主动断开服务端
- 从服务端拉取数据
- 向服务端推送数据
服务端的功能
- 主动连接客户端
- 主动断开客户端
客户端的相关功能实现比较简单,麻烦的是服务端的功能。因为 AIDL 接口定义的都是服务端的接口,是由客户端来调用的。而想要实现服务端反向调用客户端则需要通过其他手段实现。
想要实现服务端主动连接客户端,最好的办法就是服务端发送广播,客户端收到广播后再主动连接服务端,通过这种方式变相地实现服务端主动连接客户端的功能
想要实现服务端主动断开客户端,除了上面发送广播是一种实现方式外,还可以通过 android 的系统API RemoteCallbackList,用包名作为key值来注册远程回调接口的方式,让服务端持有客户端的回调接口,服务端调用回调接口,客户端在回调接口中实现主动断开服务端,通过这种方式变量地实现服务端主动断开客户端的功能。而采用后者会显得更加优雅
既然所有的操作归根结底都是由客户端来完成的,那么客户端必须得有如下的功能模块:
- 通过 bindService 的方式连接服务端,注意 ServiceConnection 的 onServiceDisconnected 只会在连接意外断开时会被调用,手动调用 unbindService 方法不会被触发
- 注册广播接收器,接受来自服务端的连接请求,然后再主动连接服务端
- 通过 unbindService 的方式断开服务端
- 实现远程回调接口,在连接成功后注册回调接口,在接口中实现主动断开服务端
- 开启工作子线程,实现一些具体的业务操作,如连接状态的触发回调等
服务端必须得有的功能模块:
- 发送广播给客户端通知申请连接自己
- 实现客户端回调接口注册逻辑
- 调用客户端的回调接口来断开连接
- 实现客户端回调接口的反注册逻辑
- 开启工作子线程,实现一些具体的业务操作
那么,整体的通信流程就是如下的步骤:
- 客户端主动或者接受到服务端的连接请求广播后,通过 bindService 的方式连接服务端
- 在连接成功后的 ServiceConnection 回调中注册回调接口,因为是进程间通信,这个回调接口同样必须是 aidl 接口,只不过是在客户端这边实现在服务端调用。
- 服务端处理注册回调接口操作,根据具体的业务实现注册操作,比如只允许一个客户端连接
- 注册回调接口成功后,开启服务端的工作线程
- 进行数据的拉取和推送,完成进程间的数据通信
- 客户端主动或者回调接口调用后,首先停止服务端的工作线程,然后通过 unbindService 的方式断开连接
- 在服务端的 onUnbind 方法中,对回调接口进行反注册操作
关键代码的讲解
首先是通信的 aidl 接口定义
// IBinder 的实际连接
interface IGT {
// 客户端连接成功后,需要注册回调,以便于服务端反向调用客户端
int register(String key, ICallback callback);
// 开始业务
void startBusiness();
// 结束业务
void stopBusiness();
// 从服务端拉取数据
TransportObject pull();
// 客户端向服务器推送数据
void push(in TransportObject object);
}
然后是客户端的连接操作与断开连接操作,包括广播接收者的注册以及回调接口的实现
public void init(Context context) {
mContext = context.getApplicationContext();
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Constants.ACTION_CONNECT.equals(intent.getAction())) {
connect();
}
}
}, new IntentFilter(Constants.ACTION_CONNECT));
}
private ServiceConnection mConnect = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IGT gt = IGT.Stub.asInterface(service);
try {
int result = gt.register(mContext.getPackageName(), mRemoteCallback);
if (result == Constants.REGISTER_SUCCESS) {
// 注册成功,开启业务
mGT = gt;
mGT.startBusiness();
mHandler.sendEmptyMessage(Constants.CLIENT_WHAT_CONNECT_SUCCESS);
} else {
// 注册失败
if (isConnected()) {
mGT.stopBusiness();
}
mGT = null;
mHandler.sendEmptyMessage(Constants.CLIENT_WHAT_CONNECT_FAILED);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 只有连接意外被终止才会回调,正常关闭连接不会回调
mGT = null;
mHandler.sendEmptyMessage(Constants.CLIENT_WHAT_DISCONNECT);
}
};
private ICallback.Stub mRemoteCallback = new ICallback.Stub() {
@Override
public void disconnected() throws RemoteException {
disconnect();
}
};
public void connect() {
if (isConnected()) {
return;
}
Intent intent = new Intent(mContext, GTService.class);
mContext.bindService(intent, mConnect, Context.BIND_AUTO_CREATE);
}
public void disconnect() {
if (!isConnected()) {
return;
}
// 断开前,先结束业务
try {
mGT.stopBusiness();
} catch (RemoteException e) {
e.printStackTrace();
}
mContext.unbindService(mConnect);
mGT = null;
mHandler.sendEmptyMessage(Constants.CLIENT_WHAT_DISCONNECT);
}
然后是客户端的拉取数据和推送数据操作
public TransportObject pull() {
if (!isConnected()) {
return null;
}
try {
return mGT.pull();
} catch (RemoteException e) {
e.printStackTrace();
}
return null;
}
public void push(TransportObject object) {
if (!isConnected()) {
return;
}
if (object == null) {
return;
}
try {
mGT.push(object);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public boolean isConnected() {
return mGT != null;
}
接着是服务端的 iBinder 接口的实现,完成回调接口的注册、业务子线程的开启和关闭、数据的推送和数据的拉取操作
private IGT.Stub mBinder = new IGT.Stub() {
@Override
public int register(String key, ICallback callback) throws RemoteException {
if (TextUtils.isEmpty(key)) {
callback(key, callback, Constants.REGISTER_ERROR_NULL_KEY);
return Constants.REGISTER_ERROR_NULL_KEY;
}
if (callback == null) {
callback(key, callback, Constants.REGISTER_ERROR_NULL_CALLBACK);
return Constants.REGISTER_ERROR_NULL_CALLBACK;
}
try {
int size = remoteCallbacks.beginBroadcast();
if (size >= Constants.MAX_CLIENT_CONNECT) {
callback(key, callback, Constants.REGISTER_ERROR_MAX_LIMIT);
return Constants.REGISTER_ERROR_MAX_LIMIT;
}
for (int i = 0; i < size; i++) {
if (key.equals(remoteCallbacks.getBroadcastCookie(i))) {
// 已经注册过
callback(key, callback, Constants.REGISTER_ERROR_REPEAT);
return Constants.REGISTER_ERROR_REPEAT;
}
}
// 开始注册
if (remoteCallbacks.register(callback, key)) {
callback(key, callback, Constants.REGISTER_SUCCESS);
return Constants.REGISTER_SUCCESS;
} else {
callback(key, callback, Constants.REGISTER_ERROR_UNKNOW);
return Constants.REGISTER_ERROR_UNKNOW;
}
} finally {
remoteCallbacks.finishBroadcast();
}
}
@Override
public void startBusiness() throws RemoteException {
if (mHandlerThread == null) {
mHandlerThread = new HandlerThread(GTService.class.getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case Constants.SERVER_WHAT_CREATE:
// 模拟1s耗时
try {
TimeUnit.SECONDS.sleep(1);
TransportObject object = new TransportObject("" + System.currentTimeMillis(), "create by GTService");
datas.offer(object, Constants.TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
case Constants.SERVER_WHAT_CONSUME:
// 模拟1s耗时
try {
TimeUnit.SECONDS.sleep(1);
datas.poll(Constants.TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
};
}
}
@Override
public void stopBusiness() throws RemoteException {
if (mHandlerThread == null) {
return;
}
mHandlerThread.quit();
mHandler.removeCallbacksAndMessages(null);
mHandlerThread = null;
mHandler = null;
}
@Override
public TransportObject pull() throws RemoteException {
if (datas.size() == 0) {
mHandler.sendEmptyMessage(Constants.SERVER_WHAT_CREATE);
}
try {
TransportObject object = datas.poll(Constants.TIMEOUT, TimeUnit.SECONDS);
callback(null, null, Constants.REGISTER_DATA_CHANGE);
return object;
} catch (InterruptedException e) {
e.printStackTrace();
}
callback(null, null, Constants.REGISTER_DATA_CHANGE);
return null;
}
@Override
public void push(TransportObject object) throws RemoteException {
if (datas.size() == Constants.MAX_SERVER_DATA_CONTAIN) {
mHandler.sendEmptyMessage(Constants.SERVER_WHAT_CONSUME);
}
try {
datas.offer(object, Constants.TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback(null, null, Constants.REGISTER_DATA_CHANGE);
}
};
然后是服务端的主动连接和主动断开连接操作
public void connect() {
Intent intent = new Intent(Constants.ACTION_CONNECT);
sendBroadcast(intent);
}
public void disconnect() {
try {
int size = remoteCallbacks.beginBroadcast();
while (size > 0) {
ICallback callback = remoteCallbacks.getBroadcastItem(size - 1);
// 这里只调用业务的disconnected,具体的回调在onUnBind方法中处理
callback.disconnected();
size--;
}
} catch (RemoteException e) {
e.printStackTrace();
} finally {
remoteCallbacks.finishBroadcast();
}
}
public void disconnect(String key) {
if (TextUtils.isEmpty(key)) {
return;
}
try {
int size = remoteCallbacks.beginBroadcast();
for (int i = 0; i < size; i++) {
if (key.equals(remoteCallbacks.getBroadcastCookie(i))) {
ICallback callback = remoteCallbacks.getBroadcastItem(i);
// 这里只调用业务的disconnected,具体的回调在onUnBind方法中处理
callback.disconnected();
break;
}
}
} catch (RemoteException e) {
e.printStackTrace();
} finally {
remoteCallbacks.finishBroadcast();
}
}
public boolean isConnected() {
int size = remoteCallbacks.getRegisteredCallbackCount();
return size > 0;
}
最后是服务端的 onUnbind 方法的实现,对回调接口进行反注册
public boolean onUnbind(Intent intent) {
if (intent != null && intent.getComponent() != null
&& !TextUtils.isEmpty(intent.getComponent().getPackageName())) {
String packageName = intent.getComponent().getPackageName();
try {
int size = remoteCallbacks.beginBroadcast();
for (int i = 0; i < size; i++) {
if (packageName.equals(remoteCallbacks.getBroadcastCookie(i))) {
ICallback callback = remoteCallbacks.getBroadcastItem(i);
remoteCallbacks.unregister(callback);
// 状态回调通知
callback(packageName, callback, Constants.REGISTER_DISCONNECT);
break;
}
}
} finally {
remoteCallbacks.finishBroadcast();
}
}
// 服务插件,这种设计可以在一个Service上开启多个插件,尽量重载Service的每个生命周期
for (IServicePlugin plugin : plugins) {
plugin.onUnbind(intent);
}
return super.onUnbind(intent);
}
使用
服务端模仿 FloatViewPlugin 自定义插件,实现 IServicePlugin 接口,定制个性化的悬浮窗插件
public class FloatViewPlugin implements IServicePlugin {
private IServiceCallback callback = new IServiceCallback() {
@Override
public void onConnected(String key, ICallback callback) {
refreshStatus();
}
@Override
public void onConnectFailed(String key, ICallback callback, int errorType) {
refreshStatus();
}
@Override
public void onDisConnected(String key, ICallback callback) {
refreshStatus();
}
@Override
public void onDataChanged() {
refreshDataStatus();
}
};
@Override
public void onBind(Intent intent) {
if (GTService.getInstance() == null) {
return;
}
GTService.getInstance().registerCallback(callback);
}
@Override
public boolean onUnbind(Intent intent) {
if (GTService.getInstance() == null) {
return false;
}
GTService.getInstance().unregisterCallback(callback);
return false;
}
private View initView(Context context) {
View contentView = View.inflate(context, R.layout.layout_float_view, null);
connectBtn = contentView.findViewById(R.id.btn_connect);
disconnectBtn = contentView.findViewById(R.id.btn_disconnect);
clientListView = contentView.findViewById(R.id.lv_client);
clientAdapter = new StringAdapter();
clientListView.setAdapter(clientAdapter);
contentListView = contentView.findViewById(R.id.lv_content);
contentAdapter = new StringAdapter();
contentListView.setAdapter(contentAdapter);
connectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (GTService.getInstance() == null) {
return;
}
GTService.getInstance().connect();
}
});
disconnectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (GTService.getInstance() == null) {
return;
}
GTService.getInstance().disconnect();
}
});
clientListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (GTService.getInstance() == null) {
return;
}
GTService.getInstance().disconnect(clientAdapter.getItem(position));
}
});
return contentView;
}
private void refreshStatus() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (GTService.getInstance() == null) {
return;
}
if (GTService.getInstance().isConnected()) {
connectBtn.setEnabled(false);
disconnectBtn.setEnabled(true);
} else {
connectBtn.setEnabled(true);
disconnectBtn.setEnabled(false);
}
if (clientAdapter != null) {
clientAdapter.setDatas(GTService.getInstance().getAllClientKeys());
}
}
});
}
private void refreshDataStatus() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (GTService.getInstance() == null) {
return;
}
if (contentAdapter != null) {
List allDatas = GTService.getInstance().getAllDatas();
List stringDatas = new ArrayList<>();
if (allDatas != null) {
for (TransportObject object : allDatas) {
stringDatas.add(object.getKey() + "---" + object.getValue());
}
}
contentAdapter.setDatas(stringDatas);
}
}
});
}
}
客户端在 Appliaction 的 onCreate方法中初始化
private void initGTModule() {
GTClient.getInstance().init(this);
}
在 MainActivity 上实现连接、断开、数据通信
public class MainActivity extends AppCompatActivity {
private IClientCallback callback = new IClientCallback() {
@Override
public void onConnected() {
refreshBtnStatus();
}
@Override
public void onConnectFailed() {
refreshBtnStatus();
}
@Override
public void onDisConnected() {
refreshBtnStatus();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
connectBtn = findViewById(R.id.btn_connect);
disconnectBtn = findViewById(R.id.btn_disconnect);
pullBtn = findViewById(R.id.btn_pull);
pushBtn = findViewById(R.id.btn_push);
connectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GTClient.getInstance().connect();
}
});
disconnectBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GTClient.getInstance().disconnect();
}
});
pullBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TransportObject object = GTClient.getInstance().pull();
if (object != null) {
Toast.makeText(MainActivity.this, "获取到数据:key=" + object.getKey() + ",value=" + object.getValue(), Toast.LENGTH_SHORT).show();
}
}
});
pushBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TransportObject object = new TransportObject("" + System.currentTimeMillis(), "create by client");
GTClient.getInstance().push(object);
Toast.makeText(MainActivity.this, "数据发送成功:key=" + object.getKey() + ",value=" + object.getValue(), Toast.LENGTH_SHORT).show();
}
});
GTClient.getInstance().registerConnectCallback(callback);
}
@Override
protected void onDestroy() {
GTClient.getInstance().unregisterConnectCallback(callback);
super.onDestroy();
}
private void refreshBtnStatus() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (GTClient.getInstance().isConnected()) {
connectBtn.setEnabled(false);
disconnectBtn.setEnabled(true);
pullBtn.setEnabled(true);
pushBtn.setEnabled(true);
} else {
connectBtn.setEnabled(true);
disconnectBtn.setEnabled(false);
pullBtn.setEnabled(false);
pushBtn.setEnabled(false);
}
}
});
}
}