自定义 Socket 通信模块管理类设计

自定义安卓 socket 通信模块系列

前面已经将 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值