前面已经将 socket 发送接收的 Service 讲完了,这一节将再封装一些功能,将模块统一对外展示。
待解决问题
和上一篇博客一样,我们先列举还剩下的问题,后面一个一个解决,问题如下:
- 连接 Service、关闭 Service、设置 Service 初始参数
- 异步线程转换为主线程
- 对外提供发送请求的方法
- 消息(异常及回复数据)和请求类的匹配
- 请求类超时设计
下面来解决上面的问题,通过新增一个 ConnectManager 类实现。
连接、关闭、设置 Service
这个便是通过 Binder 机制连接、关闭 Service 了,设置参数需要由外面设置,下面看代码:
private ConnectService.ConnectBinder mBinder;
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBinder = (ConnectService.ConnectBinder) iBinder;
//回调处理发送失败
mBinder.getService().setOnPostMessageListener(new ConnectService.OnPostMessageListener() {
@Override
public void onPostError(int typeId) {
sendErrorToUiThread(typeId);
}
@Override
public void onPostResponse(BaseResponse response) {
sendResponseToUiThread(response);
}
});
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
这里就是使用 binder 连接 service 了,下面还有几个对外接口,供外面连接、关闭、设置 service。
public void setUpSocketConfigure(Bundle data) {
mBinder.setUpSocketConfigure(data);
uiHandler.sendEmptyMessage(-1);
}
public void bindConnectService(Context context) {
Intent intent = new Intent(context, ConnectService.class);
context.bindService(intent, connection, BIND_AUTO_CREATE);
}
public void unbindConnectService(Context context) {
context.unbindService(connection);
}
异步线程转换为主线程
因为我们的数据从接收线程读取过来,如果直接回调将不能做 UI 操作,所以需要做下转换。
private void sendErrorToUiThread(int typeId) {
Message message = uiHandler.obtainMessage(-1, typeId, 0);
uiHandler.sendMessage(message);
}
private void sendResponseToUiThread(BaseResponse response) {
Message message = uiHandler.obtainMessage(1);
Bundle bundle = new Bundle();
bundle.putParcelable("response", response);
message.setData(bundle);
uiHandler.sendMessage(message);
}
@SuppressLint("HandlerLeak")
private final Handler uiHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 0:
handleError(msg.arg1);
break;
case 1:
BaseResponse response = msg.getData().getParcelable("response");
if (response != null) {
handleResponse(response);
}
break;
case -1:
handleTimeout();
uiHandler.sendEmptyMessageDelayed(-1, 100);
default:
}
}
};
这里没什么好说的,就是在 mBinder 中拿到数据通过主线程的 Handler 处理一下。异常数据我们只需要请求的消息类型就可以了(int 型),而回复要通过 Bundle 传递,所以我们前面设计回复基类的时候使用到了序列化。
对外提供发送请求的方法
我们需要使用通信模块的地方,希望通过一个请求就可以通过回调处理结果,所以需要向外提供一个接收请求的方法:
//注意线程安全,ArrayList出问题
private final List<BaseRequest> requestList = new CopyOnWriteArrayList<>();
public void sendMessage(BaseRequest request) {
requestList.add(request);
//TODO 解决mBinder为空
if (request.isSendOut && mBinder != null) {
mBinder.sendMessage(request);
}
}
这里用了一个 CopyOnWriteArrayList 储存请求,因为我们的请求可在任意地方触发,需要线程同步处理。另外前面设计请求类的 isSendOut 字段,在这里生效了。
这里有个 mBinder 会为 null 的问题,很奇怪,我没有解决。
消息(异常及回复数据)和请求类的匹配
接下来就是异常及回复数据的匹配问题了,异常时拿着请求的类型,回复由回复的类型,我们遍历请求的列表就能找到需要触发的回复了。
private void handleError(int typeId) {
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.requestMsgType == typeId) {
request.callback.onError(typeId);
requestList.remove(request);
}
}
}
/* --处理说明--
一对零:不求回复,直接移除(checkTimeout)
一对一:一般模式,直接结束
一对多:定位报值,多条接受
零/一对无限:wantNumb = -1,特别处理
*/
private void handleResponse(@NonNull BaseResponse response) {
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.responseMsgType == response.responseMsgType) {
if (!request.isReachWant() || request.isWantInfinite()) {
request.callback.onResponse(response);
request.addNowNumb();
if (request.isReachWant() && !request.isWantInfinite()) {
requestList.remove(request);
}
}else {
requestList.remove(request);
}
}
}
}
这里异常匹配到了请求,就会触发请求的回调接口,传递异常出去,并不做判断,直接移除该请求。
对回复的处理就复杂多了,涉及到多种消息模型,可以看方法顶部注释,解释的很清楚。
这里遍历请求数组,如果不使用线程安全的数组会造成一个 ConcurrentModifyException,很有意思,让我学到了,读者有兴趣可以了解下。
请求类超时设计
我们在请求类中就设计了超时相关属性,前面都没有提到,肯定要解决啊,下面看代码:
public void setUpSocketConfigure(Bundle data) {
mBinder.setUpSocketConfigure(data);
uiHandler.sendEmptyMessage(-1);
}
@SuppressLint("HandlerLeak")
private final Handler uiHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
...
case -1:
handleTimeout();
//清除一下“-1”消息???
uiHandler.sendEmptyMessageDelayed(-1, 100);
default:
}
}
};
private void handleTimeout() {
long nowTime = System.currentTimeMillis();
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.wantNumb == 0) {
requestList.remove(request);
} else if (request.isTimeout(nowTime) && !request.isWantInfinite()) {
request.callback.onTimeout(request.requestMsgType);
requestList.remove(request);
}
}
}
这里利用上面的 uiHandler,利用它每隔 100ms 就处理一次超时方法,并在初始化的时候启动这个定时。handleTimeout 里面也有一些逻辑,读者可以自行理解下。
完整代码
public class ConnectManager {
private volatile static ConnectManager mConnectManager = null;
private final List<BaseRequest> requestList = new CopyOnWriteArrayList<>();//注意线程安全,ArrayList出问题
@SuppressLint("HandlerLeak")
private final Handler uiHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 0:
handleError(msg.arg1);
break;
case 1:
BaseResponse response = msg.getData().getParcelable("response");
if (response != null) {
handleResponse(response);
}
break;
case -1:
handleTimeout();
uiHandler.sendEmptyMessageDelayed(-1, 100);
default:
}
}
};
private ConnectService.ConnectBinder mBinder;
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBinder = (ConnectService.ConnectBinder) iBinder;
//回调处理发送失败
mBinder.getService().setOnPostMessageListener(new ConnectService.OnPostMessageListener() {
@Override
public void onPostError(int typeId) {
sendErrorToUiThread(typeId);
}
@Override
public void onPostResponse(BaseResponse response) {
sendResponseToUiThread(response);
}
});
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
//DCL
public static ConnectManager getInstance() {
if (mConnectManager == null) {
synchronized (ConnectManager.class) {
if (mConnectManager == null) {
mConnectManager = new ConnectManager();
}
}
}
return mConnectManager;
}
public void setUpSocketConfigure(Bundle data) {
mBinder.setUpSocketConfigure(data);
uiHandler.sendEmptyMessage(-1);
}
public void sendMessage(BaseRequest request) {
requestList.add(request);
//TODO 解决mBinder为空
if (request.isSendOut && mBinder != null) {
mBinder.sendMessage(request);
}
}
public void bindConnectService(Context context) {
Intent intent = new Intent(context, ConnectService.class);
context.bindService(intent, connection, BIND_AUTO_CREATE);
}
public void unbindConnectService(Context context) {
context.unbindService(connection);
}
private void sendErrorToUiThread(int typeId) {
Message message = uiHandler.obtainMessage(-1, typeId, 0);
uiHandler.sendMessage(message);
}
private void sendResponseToUiThread(BaseResponse response) {
Message message = uiHandler.obtainMessage(1);
Bundle bundle = new Bundle();
bundle.putParcelable("response", response);
message.setData(bundle);
uiHandler.sendMessage(message);
}
private void handleError(int typeId) {
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.requestMsgType == typeId) {
request.callback.onError(typeId);
requestList.remove(request);
}
}
}
/* --处理说明--
一对零:不求回复,直接移除(checkTimeout)
一对一:一般模式,直接结束
一对多:定位报值,多条接受
零/一对无限:wantNumb = -1,特别处理
*/
private void handleResponse(@NonNull BaseResponse response) {
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.responseMsgType == response.responseMsgType) {
if (!request.isReachWant() || request.isWantInfinite()) {
request.callback.onResponse(response);
request.addNowNumb();
if (request.isReachWant() && !request.isWantInfinite()) {
requestList.remove(request);
}
}else {
requestList.remove(request);
}
}
}
}
private void handleTimeout() {
long nowTime = System.currentTimeMillis();
for (BaseRequest request : requestList) {
if (request == null) {
requestList.remove(null);
} else if (request.wantNumb == 0) {
requestList.remove(request);
} else if (request.isTimeout(nowTime) && !request.isWantInfinite()) {
request.callback.onTimeout(request.requestMsgType);
requestList.remove(request);
}
}
}
}
结语
一篇文章下来,我觉得整个 socket 通信模块都讲的差不多喽,下面再写一篇用法吧!
end