基于okhttp封装网络库 (1)

基于okhttp封装网络库 (1)

前言:在前篇文章中我已经总结了 okhttp 库的特性,这次就来讲对该库封装思路,当然是在熟悉 okhttp 基本 api使用情况下。

封装思路

  1. 首先是确定协议,一般来讲公司都会采用一二级协议的格式,所以这篇是假设服务端跟客户端协定的协议为一二级协议的格式,比如:

(输入参数)

参数名称 类型 默认值 描述
id Integer 200 Book ID

(输出参数)

Response 类型 默认值 描述
code Integer 200 请求状态
message String 请求成功 请求信息
data BookDetail 请求二级协议
  1. 确定协议后首先确定的是 主机地址,然后确定具体的业务信息来构成 URL,一个推荐的方式是

    • URL: host + api 版本 + 业务请求地址
    • 实例: Http://api.example.com/v2/item/detail
    • 这样在 api 版本升级时或者发生改动时,旧版本的业务处理代码不需要做任何修改,直接在 URL 中修改参数就可。
  2. 下一步就是确定请求 request 报文:

    • 请求类型:GET / Post
      • GET:Accept
        • Http://api.example.com/v2/item/detail/id=xxx
      • Post :Content-Type :
        • application/x-www-form-urlencoded:id=xxx
        • application/json:{“id”,xxx}
  3. response body 已经在上列的拖中展示出来,在获得 data 后这时候我们假设 data 字段是 json 格式的,然后将 json 转化成 Bean,也就是我们 java 中的实体类。

总结:为完成上述的要求也同时为了代码重用和避免代码混乱,我们需要一个通用的模式,下面就是一个简单却又完整的 BaseRequest 抽象类来完成上述要求。


BaseRequest

public abstract class BaseRequest {

    //存放 POST 请求主体内容, protected 关键字是为了给子类访问到。
    protected HashMap mBodyMap = new HashMap();

     //存放 GET 请求参数
    protected HashMap mQueryMap = new HashMap();

    private OkHttpClient mClient;
    private Call mCall;

    public BaseRequest(OkHttpClient httpClient) {
        this.mClient = httpClient;
    }

    protected String getHost() {
        return HttpConfig.HOST;
    }

    //api 地址   
    public abstract String getApi();

    //请求方式
    public abstract int getHttpMethod();

    //返回 Bean
    public abstract Class getModelClass();

    //请求内容
    public abstract MediaType getMediaType();

    //将 mBodyMap 里面的 POST 请求体转成 String 格式传给 RequestBody.create() 方法
    //在子类中完成,跟 getMediaType() 组合来完成不同业务的请求需求。
    protected abstract String getRequestString();

    //发出请求必须要有 URL
    public String getUrl() {
        if (HttpMethod.GET == getHttpMethod()) {
            String queryString = getQueryString();
            if (!TextUtils.isEmpty(queryString)) {
                return String.format("%s%s?%s", getHost(), getApi(), queryString);
            }
        }
        return String.format("%s%s", getHost(), getApi());
    }

    //将我们的常规请求转换为 okhttp 中的 request
    protected Request buildRequest() {
        RequestBody body = null;
        Request.Builder builder = new Request.Builder();
        String strRequest = getRequestString();
        switch (getHttpMethod()) {
            case HttpMethod.GET:
                builder.url(getUrl());
                break;
            case HttpMethod.POST:

                body = RequestBody.create(getMediaType(),strRequest);
                builder.url(getUrl()).post(body);
                break;
            default:
                //TODO
                break;
        }
        return builder.build();

    }

    //返回 GET 请求参数集合,如 id=xxx&&name=xxx 
    public String getQueryString() {
        if (mQueryMap != null && mQueryMap.size() > 0) {
            return encodeParameters(mQueryMap, "utf-8");
        }
        return null;
    }

    //将 map 键值对取出来组装成 id=xxx&&name=xxx  格式
    protected String encodeParameters(Map params, String paramsEncoding) {
        try {
            if (params != null && params.size() > 0) {
                StringBuilder encodedParams = new StringBuilder();
                int index = 0;
                for (Map.Entry entry : params.entrySet()) {
                    if (TextUtils.isEmpty(entry.getKey()) || TextUtils.isEmpty(entry.getValue())) {
                        continue;
                    }
                    if (index > 0) encodedParams.append('&');
                    encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                    encodedParams.append('=');
                    encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
                    index++;
                }
                return encodedParams.toString();

            } else {
                return null;
            }
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
        }
    }

}

public class HttpMethod {
    public static final int POST = 1;
    public static final int GET = 2;
}

public class HttpConfig {
    public static final String HOST = "http://1.1.1.1";
}



public class HttpResponse {
    public Integer code;
    public String message;
    public T data;

    public HttpResponse(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public HttpResponse() {
    }

}

  • 以上就描述了一个基本的 GET 和 POST 方法,然后开始同步请求封装

同步请求封装

  /***
     * 同步调用
     *
     * @return
     * @throws Exception
     */
    public HttpResponse execute() throws IOException {
        Response response = this.mClient.newCall(buildRequest()).execute();
        int code = response.code();
        if (code == 200) {
            //正常请求
            ResponseBody body = response.body();
            return getResponse(body);
        } else {
            return null;
        }

    }

    //将服务器返回的 Json 数据解析成 HTTPResponse
    private HttpResponse getResponse(ResponseBody body) throws IOException {
        String strBody = body.string();
        JSONObject json = JSON.parseObject(strBody);
        HttpResponse httpResponse = new HttpResponse();
        httpResponse.code = json.getInteger("code");
        httpResponse.message = json.getString("message");
        String strData = json.getString("data");
        Object data = JSONObject.parseObject(strData, getModelClass());
        httpResponse.data = (T) data;
        return httpResponse;
    }

异步请求封装


    //主线程分发器,因为 okhttp 的异步调用方法发生在子线程,我们需要将结果发送回主线程
    private ExecutorDelivery mDelivery;

    /***
     * 异步调用
     */
    public boolean enqueue(final HttpCallback callback) {
        if (mCall == null) {
            mCall = mClient.newCall(buildRequest());
            mCall.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    HttpResponse response = new HttpResponse(ResponseCode.NET_ERROR, "网络错误", null);
                    mDelivery.postResponse(callback, BaseRequest.this, response, e);
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    HttpResponse httpResponse = null;
                    try {
                        httpResponse = getResponse(response.body());
                        mDelivery.postResponse(callback, BaseRequest.this, httpResponse, null);
                    } catch (IOException e) {
                        httpResponse = new HttpResponse(ResponseCode.NET_ERROR, "网络错误", null);
                        mDelivery.postResponse(callback, BaseRequest.this, httpResponse, e);
                    } catch (JSONException e) {
                        httpResponse = new HttpResponse(ResponseCode.JSON_ERROR,"Json解析错误", null);
                        mDelivery.postResponse(callback, BaseRequest.this, httpResponse, e);
                    }
                }
            });
        } else {
            if (mCall.isCanceled() || mCall.isExecuted()) {
                Log.e("TAG", "cancel executed");
            }
        }
        return true;


//请求回调接口
public interface HttpCallback {

    /***
     * 请求成功,httpcode 200,业务code == 200
     */
    public void onResponse(BaseRequest request, Object data);


    /**
     * @param request
     * @param e       异常:网络异常,json解析异常
     * @param code    业务code,若httpcode为200,后两个参数有效;服务端的code,
     * @param message 业务message
     */
    public void onFailure(BaseRequest request, Exception e, int code, String message);
}  

//注意下列 code 应该在一级协议中跟服务器约定好具体数值的含义!
public interface ResponseCode {
    public static final int SUCCESS = 200;
    public static final int FAILED = 2;
    //其他内部错误
    public static final int ERROR = -1;
    public static final int NET_ERROR = -200;
    /***
     * json格式错误
     */
    public static final int JSON_ERROR = -201;


}

主线程分发器 ExecutorDelivery

//利用主线程创建的 Handler 来完成分发器的初始化工作。
public class ExecutorDelivery {
    /**
     * Used for posting responses, typically to the main thread.
     */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     *
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }


    public void postResponse(HttpCallback httpCallback, BaseRequest request, HttpResponse response, Exception e) {
        mResponsePoster.execute(ResponseDeliveryRunnable2.response(httpCallback, request, response, e));
    }


    private static class ResponseDeliveryRunnable2 implements Runnable {
        private final BaseRequest request;
        private final Exception e;
        private HttpResponse httpResponse;
        private HttpCallback callback;

        private ResponseDeliveryRunnable2(HttpCallback callback, BaseRequest request, HttpResponse response, Exception e) {
            this.callback = callback;
            this.request = request;
            this.e = null;
            this.httpResponse = response;
        }


        public static ExecutorDelivery.ResponseDeliveryRunnable2 response(HttpCallback modelCallback, BaseRequest request, HttpResponse response, Exception e) {
            return new ExecutorDelivery.ResponseDeliveryRunnable2(modelCallback, request, response, e);
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            if (e == null && httpResponse.code == ResponseCode.SUCCESS) {
                this.callback.onResponse(request, httpResponse.data);
            } else {
                this.callback.onFailure(request, e, httpResponse.code, httpResponse.message);
            }

        }
    }
}

HttpClientWrapper 及新的 BaseRequest 构造方法

  public BaseRequest(HttpClientWrapper httpClient) {
        this.mClient = httpClient.getOkHttpClient();
        this.mDelivery = httpClient.getDelivery();
    }


//写这个网络封装器的目的是将业务层和底层网络库解耦,上层业务开发者不用关心用的是哪个网络库。
//在 Application 中初始化
public class HttpClientWrapper {
    private OkHttpClient mOkHttpClient;
    private ExecutorDelivery mDelivery;

    public HttpClientWrapper(Context context) {
        Handler handler = new Handler(Looper.getMainLooper());
        mDelivery = new ExecutorDelivery(handler);
        mOkHttpClient = new OkHttpClient.Builder().connectTimeout(3, TimeUnit.SECONDS).readTimeout(3, TimeUnit.SECONDS).writeTimeout(3, TimeUnit.SECONDS).build();
    }

    public OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    public ExecutorDelivery getDelivery() {
        return mDelivery;
    }

}

总结

  • 至此已经封装好了一个请求基类,下一步就是根据 GET/POST 请求实体内容来写具体的请求业务。

你可能感兴趣的:(Android)