自定义安卓 Socket 通信模块(2): Request、Response

544 阅读4分钟

前面讲了 Socket 的相关使用,并且还简单封装了一个带初始化和接收的 Socket Service,我们先不急着直接去完善功能,下面先感受一下另外一种设计代码的思想,先把请求类和回复类设计好,再去完善我们的 Socket 功能!

下面是忽略通信模块后,从需求上设计请求类和回复类的过程。

场景分析

忽略整个socket 通信模块,我们先思考下请求和回复应该是什么样的,要有哪些功能。下面是我整理的一些问题:

请求和回复有哪些对应关系?

请求(request)和回复(response)是一一对应的吗?如果不是那还有哪些场景?

收到回复后,如何匹配需要的请求?

假设我们发出一条请求,并收到一条请求时,如何确定这条回复是属于刚发出的请求的呢?

如何处理请求的回调事件?

当我们收到一条回复,并且匹配到对应的回复时,如何给到需要的地方处理呢?

如何设置请求的超时时间?

假如我有一条请求,并期望在指定时间内收到回复,超出时间后希望得到一个超时处理,又应该如何实现这样一个功能呢?

问题解决

同样时忽略通信模块,我们只从请求(request)是和回复(response)设计方面去解决上面这些问题,这样也是解耦性的挑战。

解决请求和回复对应关系问题

对于请求和回复对应关系问题,思考一番后,我总结出了下面五种消息和回复的对应关系,更复杂的使用可以通过这五种情况组合使用:

  • 零对任意:不发出去但是想接收消息
  • 一对零:只发一条消息,但是不需要回复
  • 一对一:一条请求对应一条回复
  • 一对多:一条请求对应多条回复
  • 一对无限:一条请求对应无数条回复

我愿称上面的五种情况为消息的五种模型,下面讲解下如何去实现这五种消息模式。

  1. 零对任意

    发不发出去消息,可以用一个 boolean 型变量控制,例如设置 isSendOut 变量为 false,就不发出消息。

  2. 一对零、一对一、一对多

    这里一个对应零个、一个、有限个,实际是一个期待接受数目问题,我们可以设置一个 wantNumb 来控制,加上一个 nowNumb 来标志已接收数目,当两个值相等的时候,便达到了期望条件,这时候不再接收就可以了。

  3. 一对无限

    一对无限的情形,实际上就是无法满足期望条件,我们复用上面的属性,将 wantNumb 设置为负数,这时候将永远无法满足期望条件。

解决匹配请求问题

这里我们在请求和回复中都添加 MsgType 字段,标识请求和回复的唯一类型,并且在请求中再添加一个期望回复的 MsgType 字段。这样当回复到来时,比对回复消息类型和请求期望的消息类型,就能确定这个请求是否被新到的回复触发。

解决请求的回调问题

这里在请求中增加一个回调就可以解决,比对上回复的消息后,拿到回复的消息数据,通过回调接口去处理数据就可以。这里从异步线程转到 UI 线程,需要通信模块解决。

解决超时设计问题

如果只是从请求的角度来看超时问题的话,其实只要加上一个起始时间和一个超时门限就可以,例如加上一个由 System.currentTimeMillis() 获得时间的 startTime 属性,和以毫秒计算的 threshold 属性,只要定期检查是否超时即可。

具体代码

  • 请求基类
public class BaseRequest {
    //请求消息类型
    public int requestMsgType;
    //期待接收的消息类型
    public int responseMsgType;
    //发送数据
    public byte[] data;
    //回调接口
    public ResponseCallback callback;

    //起始时间
    private long startTime;
    //超时门限
    private long threshold;

    //期待接受数目
    public int wantNumb = 1;
    //已接收数目
    private int nowNumb = 0;

    //是否发送出去
    public boolean isSendOut = true;



    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    public void setThreshold(long threshold) {
        this.threshold = threshold;
    }

    void addNowNumb() {
        nowNumb++;
    }

    boolean isTimeout(long nowTime) {
        long threshold = nowTime - startTime;
        return threshold > this.threshold;
    }

    boolean isReachWant() {
       return nowNumb >= wantNumb;
    }

    boolean isWantInfinite() {
        return wantNumb < 0;
    }

    //将需要发送的数据转成字节数组,需要子类重写
    protected byte[] getByteData() {
        return new byte[0];
    }
}
  • 回调接口
public interface ResponseCallback {
    void onResponse(BaseResponse response);
    default void onError(int requestMsgType) {};
    default void onTimeout(int requestMsgType){};
}
  • 回复基类
public class BaseResponse implements Parcelable {
    //回复类型
    public int responseMsgType;
    //流水号,可去除
    public int serialNumber;
    //数据
    public byte[] data;
    //结果码,可去除
    public int resultCode = 1;

    //从字节数组里获得数据,需要子类重写
    protected void parseData() {

    }

    public static final Creator<BaseResponse> CREATOR = new Creator<BaseResponse>() {
        public BaseResponse createFromParcel(Parcel source) {
            BaseResponse response = new BaseResponse();
            response.responseMsgType = source.readInt();
            response.serialNumber = source.readInt();
            byte[] bytes = new byte[source.readInt()];;
            source.readByteArray(bytes);
            response.data = bytes;

            return response;
        }

        public BaseResponse[] newArray(int size) {
            return new BaseResponse[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(responseMsgType);
        parcel.writeInt(serialNumber);
        parcel.writeInt(data.length);
        parcel.writeByteArray(data);
    }

}

这里的回复基类加了序列化,后面会用到。

结语

上面的内容从忽略通信模块、探讨所需功能的角度上,设计了我们的请求基类和回复基类,我觉得这样的一种思维方式很棒。后面我们将用这请求基类和回复基类,完善我们的通信模块!

如果读者想继续了解我的《自定义安卓 socket 通信模块系列》文章,可以点击链接跳转查看哦!

end