Android 网络框架Volley+okHttp+Gson支持https

项目地址

https://github.com/fengxing1234/VolleyOkhttpGson/tree/master

Volley

Volley官方

Github Volley

官方教程

Android Volley完全解析(一),初识Volley的基本用法

Android Volley完全解析(二),使用Volley加载网络图片

Android Volley完全解析(三),定制自己的Request

Android Volley 之自定义Request

Volley是Google开发的HTTP库,它使Android应用程序的网络更容易,最重要的是,更快。

Volley非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。

官方给出的Volley优点:
image.png

本人英文不好,用软件翻译一下。。

  1. 自动调度网络请求。
  2. 多个并发网络连接。
  3. 具有标准HTTP缓存一致性的透明磁盘和内存响应缓存。
  4. 支持请求优先级。
  5. 取消请求API。
  6. 可以取消单个请求,也可以设置要取消的请求块或范围。
  7. 易于定制,例如,重试和退避。
  8. 强大的排序,可以使用从网络异步获取的数据轻松正确填充UI。
  9. 调试和跟踪工具。

OkHttp

Gson

使用

  1. 在Application中初始化HttpClient
public class VolleyApplication extends Application {

    private static Context context;
    private HttpClient httpClient;

    @Override
    public void onCreate() {
        super.onCreate();
        httpClient = new HttpClient(this);
        context = this;
    }

    public HttpClient getClient() {
        return httpClient;
    }

    public static Context getContext() {
        return context;
    }
}

  1. 配置 HttpClient
    根据接口文档配置头信息
public Map<String, String> generateHeader(RequestBody requestBody) {
        HashMap<String, String> map = new HashMap<>();
        map.put("", "8");
        map.put("", "123456");
        map.put("", DeviceUtil.getUUID(VolleyApplication.getContext()));
        map.put("", "1");


        String token = "";
        if (!"".equals(token)) {
            map.put("picc-m-sid", token);
        }
        return map;
    }
  1. 创建子类实现DataHandler
    根据接口文档填充。
public class DataHandler {

    public JsonElement getData(NetworkResponse response, JsonObject json) {
        return json.get("data");
    }

    public int getStatusCode(NetworkResponse response, JsonObject json) {
        return json.get("state").getAsInt();
    }

    public String getMessage(NetworkResponse response, JsonObject json) {
        return json.get("msg").getAsString();
    }

    public boolean isSuccess(NetworkResponse response, JsonObject json) {
        return getStatusCode(response, json) == 200;
    }

    public String getStringFromobject(JsonElement element, String name) {
        return element.getAsJsonObject().get(name).getAsString();
    }

    public boolean getBooleanFromobject(JsonElement element, String name) {
        return element.getAsJsonObject().get(name).getAsBoolean();
    }

    public JsonElement parseGsonResponse(NetworkResponse response) throws UnsupportedEncodingException {
        //Log.w("fengxing", "parseGsonResponse: "+new String(response.data,HttpHeaderParser.parseCharset(response.headers)) );
        //{"flag":true,"data":{"url":"/mcph5Version/downLoad","version":"254"},"state":"200","msg":"成功返回"}
        // trim the string to prevent start with blank, and test if the string
        // is valid JSON, because the parser don't do this :(. If Json is not
        // valid this will return null;
        return new JsonParser().parse(new String(response.data, HttpHeaderParser.parseCharset(response.headers)).trim());
    }
}
  1. 定义接口
public Request<?> queryClaimPage(Context context, String page_id, String report_id, Response.Listener<JsonElement> listener, Response.ErrorListener error) {
        String url = API_CLAIM_QUERY_CLAIM_PAGE;
        JsonBuilder builder = new JsonBuilder();
        builder.add("page_id", page_id);
        builder.add("report_id", report_id);
        RequestBody requestBody = builder.build();
        return addGsonRequest(context, POST, url, requestBody, requestBody, listener, error);
    }
  1. 代码中使用
    首先初始化HttpClient
client = HttpClient.getClient(this);
@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.test_1:
                request = client.getProvinceCity(this, new Response.Listener<JsonElement>() {
                    @Override
                    public void onResponse(JsonElement response) {
                        tv.setText(response.toString());
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        toast(error.getMessage());
                    }
                });
                break;
            case R.id.test_2:
                client.queryClaimPage(this, "4", "08cc85f8db9f4befbebf2e57f97eb016", new Response.Listener<JsonElement>() {
                    @Override
                    public void onResponse(JsonElement response) {
                        tv.setText(response.toString());
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        toast(error.getMessage());
                    }
                });
                break;
            case R.id.test_cancel:
                client.cancelAll(this);
                boolean cancel = client.isCancel(request);
                Log.d("isCancel", "onClick: " + cancel);
                break;
        }
    }

主要
根据接口文档一般的项目传递的都是Json格式,在定义接口时可以使用``JsonBuilder builder = new JsonBuilder();```来添加参数。

如果有特殊需求例如 使用表单就不能使用JsonBuilder类。应该使用FormBuilder类。
区别在于:
表单的type:application/x-www-form-urlencoded
Json的type:application/json; charset=utf-8

现在就可以正常使用了

取消请求

每一个接口都会传递一个context上下文,如果页面关闭,请求队列还是会在后台请求工作,当请求成功一般都设置设置数据,但是页面关闭了,如果不处理很容易造成崩溃,解决办法就是在页面关闭后者失去焦点时,取消请求。

@Override
    protected void onDestroy() {
        super.onDestroy();
        client.cancelAll(this);
    }

request封装

自定义OkRequest类继承Request(Volley)类。


  private RequestBody requestBody;

    public OkRequest(int method, String url, RequestBody body, @Nullable Response.ErrorListener listener) {
        super(method, url, listener);
        this.requestBody = body;
    }

    @Override
    public String getUrl() {
        if (isRequestBodyNoNeed())
            return super.getUrl() + "?" + getParamsString();
        else
            return super.getUrl();
    }

    protected String getParamsString() {
        Buffer buffer = new Buffer();
        try {
            requestBody.writeTo(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer.readUtf8();
    }

    protected boolean isRequestBodyNoNeed() {
        return (getMethod() == Method.GET || getMethod() == Method.DELETE) && requestBody != null;
    }

    public void setRequestBody(RequestBody body) {
        this.requestBody = body;
    }

    public RequestBody getRequestBody() {
        return requestBody;
    }

    @Override
    public String getBodyContentType() {
        if (!isRequestBodyNoNeed() && requestBody != null) {
            return requestBody.contentType().toString();
        }
        return super.getBodyContentType();
    }

    @Override
    public byte[] getBody() throws AuthFailureError {
        if (!isRequestBodyNoNeed() && requestBody != null) {
            Buffer buffer = new Buffer();
            try {
                requestBody.writeTo(buffer);
                return buffer.readByteArray();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            } finally {
                buffer.close();
            }
        } else {
            return super.getBody();
        }

在这里处理url的拼接,请求体,和数据类型(content-type)以及头信息。
头信息我放在了HttpClient类处理。

DataRequest MessageRequest GsonRequest ModelRequest

这四个类都继承OkRequest。区别在与处理数据不通。

  • DataRequest:

  • MessageRequest:
    DataRequest和MessageRequest 差不多相同,都时根据业务逻辑和接口文档定义出来的。
    适用于这样的返回
    {"flag":true,"data":{"url":"/mcph5Version/downLoad","version":"254"},"state":"200","msg":"成功返回"}
    DataRequest:取出来的数据data数据。
    MessageRequest:取出来的时msg数据。

  • GsonRequest:
    获取到的JsonElement数据,可以说时万能的request。

private DataHandler mHandler;

    private Response.Listener<JsonElement> mListener;
    public GsonRequest(int method, String url, RequestBody requestBody, DataHandler handler, Response.Listener<JsonElement> listener, @Nullable Response.ErrorListener errorListener) {
        super(method, url, requestBody, errorListener);
        this.mListener = listener;
        if(handler == null){
            handler = new DataHandler();
        }
        this.mHandler = handler;
    }

    public GsonRequest(int method, String url, @NonNull Response.Listener<JsonElement> listener, Response.ErrorListener errorListener) {
        this(method, url, null, listener, errorListener);
    }

    public GsonRequest(int method, String url, RequestBody body, @NonNull Response.Listener<JsonElement> listener, Response.ErrorListener errorListener) {
        super(method, url, body, errorListener);
        this.mListener = listener;
    }

    @Override
    protected Response<JsonElement> parseNetworkResponse(NetworkResponse response) {
        try {
            dd(new String(response.data, HttpHeaderParser.parseCharset(response.headers)).trim());
            JsonElement element = new JsonParser().parse(new String(response.data, HttpHeaderParser.parseCharset(response.headers)).trim());
            return Response.success(element, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(JsonElement response) {
        mListener.onResponse(response);
    }
}

在成功回调中使用Gson就可以解析出来。

  • ModelRequest:
public class ModelRequest<T> extends OkRequest<T> {
    private Type type;
    private Response.Listener<T> mListener;
    private DataHandler mHandler;

    public ModelRequest(int method, String url, Type type, @NonNull Response.Listener<T> listener, Response.ErrorListener errorListener) {
        this(method, url, null, type, null, listener, errorListener);
    }

    public ModelRequest(int method, String url, RequestBody requestBody, Type type, @NonNull Response.Listener<T> listener, Response.ErrorListener errorListener) {
        this(method, url, requestBody, type, null, listener, errorListener);
    }

    public ModelRequest(int method, String url, Type type, DataHandler dataHandler, @NonNull Response.Listener<T> listener, Response.ErrorListener errorListener) {
        this(method, url, null, type, dataHandler, listener, errorListener);
    }

    public ModelRequest(int method, String url, RequestBody requestBody, Type type, DataHandler dataHandler, @NonNull Response.Listener<T> listener, Response.ErrorListener errorListener) {
        super(method, url, requestBody, errorListener);
        this.type = type;
        this.mListener = listener;
        if (dataHandler == null) {
            dataHandler = new DataHandler();
        }
        mHandler = dataHandler;
    }

    public Type getType() {
        return type;
    }

    public DataHandler getDataHandler() {
        return mHandler;
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            JsonObject json = mHandler.parseGsonResponse(response).getAsJsonObject();
            if (mHandler.isSuccess(response, json)) {
                return Response.success((T) Model.commonCreate(mHandler.getData(response, json), type), HttpHeaderParser.parseCacheHeaders(response));
            } else {
                return Response.error(new VolleyRequestError(this, mHandler.getStatusCode(response, json), mHandler.getMessage(response, json)));
            }
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

使用时需要传入转换的类型,然后由Gson把亲故的数据转换成实体类。

在定义接口时:

public Request<?> queryHospitalInfo(Context context, int now_page, int row_num, String hospital_name, Response.Listener<HospitalInfo> listener, Response.ErrorListener error) {
        String url = API_CLAIM_QUERY_HOSPITAL_INFO;
        McpJsonBuilder builder = new McpJsonBuilder();
        builder.add("now_page", 1);
        builder.add("row_num", 20);
        builder.add("hospital_name", hospital_name);
        RequestBody requestBody = builder.build();
        return addModelRequest(context, POST, url, requestBody, requestBody, HospitalInfo.class, listener, error);
    }

使用ModelRequest 在定义接口时,需要传入类型,HospitalInfo.class

如果是一个集合应该这样写:

public Request<?> queryBankInfo(Context context, Response.Listener<List<BankData>> listener, Response.ErrorListener error) {
        String url = API_CLAIM_QUERY_BANK_INFO;
        McpJsonBuilder builder = new McpJsonBuilder();
        RequestBody requestBody = builder.build();
        return addModelRequest(context, POST, url, requestBody, requestBody, new TypeToken<List<BankData>>() {
        }.getType(), listener, error);
    }

其它不变。

设置策略

public <T> Request<?> addModelRequest(Context context, int method, String url,
                                          RequestBody requestBody, Type type, final RequestBody requestBodyForHeader,
                                          RetryPolicy retryPolicy,
                                          Response.Listener<T> listener, @NonNull Response.ErrorListener error) {
        ModelRequest<T> request =
                new ModelRequest<T>(method, getAbsoluteUrl(url), requestBody, type, mDataHandler, listener,
                        error) {
                    @Override
                    public Map<String, String> getHeaders() throws AuthFailureError {
                        return generateHeader(requestBodyForHeader);
                    }
                };
        request.setRetryPolicy(retryPolicy);
        return add(context, request);
    }

    public Request<?> addMessageRequest(Context context, int method, String url,
                                        RequestBody requestBody, final RequestBody requestBodyForHeader,
                                        RetryPolicy retryPolicy,
                                        Response.Listener<String> listener, @NonNull Response.ErrorListener error) {
        MessageRequest request =
                new MessageRequest(method, getAbsoluteUrl(url), requestBody, mDataHandler, listener,
                        error) {
                    @Override
                    public Map<String, String> getHeaders() throws AuthFailureError {
                        return generateHeader(requestBodyForHeader);
                    }
                };
        request.setRetryPolicy(retryPolicy);
        return add(context, request);
    }

    public Request<?> addDataRequest(Context context, int method, String url, RequestBody requestBody,
                                     final RequestBody requestBodyForHeader,
                                     RetryPolicy retryPolicy,
                                     Response.Listener<JsonElement> listener,
                                     @NonNull Response.ErrorListener error) {
        DataRequest request =
                new DataRequest(method, getAbsoluteUrl(url), requestBody, mDataHandler, listener, error) {
                    @Override
                    public Map<String, String> getHeaders() throws AuthFailureError {
                        return generateHeader(requestBodyForHeader);
                    }
                };
        request.setRetryPolicy(retryPolicy);
        return add(context, request);
    }

    public Request<?> addGsonRequest(final Context context, int method, String url,
                                     RequestBody requestBody, final RequestBody requestBodyForHeader,
                                     RetryPolicy retryPolicy,
                                     Response.Listener<JsonElement> listener, @NonNull Response.ErrorListener error) {
        GsonRequest request =
                new GsonRequest(method, getAbsoluteUrl(url), requestBody, mDataHandler, listener,
                        error) {
                    @Override
                    public Map<String, String> getHeaders() throws AuthFailureError {
                        return generateHeader(requestBodyForHeader);
                    }
                };
        request.setRetryPolicy(retryPolicy);
        return add(context, request);
    }

    public Request<?> addGsonRequest(Context context, int method, String url, RequestBody requestBody, RequestBody requestBodyForHeader, Response.Listener<JsonElement> listener, Response.ErrorListener error) {
        return addGsonRequest(context, method, url, requestBody, requestBodyForHeader,
                new DefaultRetryPolicy(DEFAULT_SOCKET_TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                        DefaultRetryPolicy.DEFAULT_BACKOFF_MULT),
                listener, error);
    }

在定义接口时,这几个方法对应上面的说的request。
这里都时做了相同的几件事:

  • 定义头信息generateHeader方法
  • 设置策略
  • 把请求添加到队列
  • 获取到完整的url

完整的url是由下面组成的

private static final String mDebugApiHost = "http://mtest.baidu.cn/abcd/";
private String getBaseHost() {
        return mDebugApiHost;
    }
public static final String API_CLAIM_QUERY_CLAIM_PAGE = "/api/claim/query-claim-page";

public Request<?> queryClaimPage(Context context, String page_id, String report_id, Response.Listener<JsonElement> listener, Response.ErrorListener error) {
        String url = API_CLAIM_QUERY_CLAIM_PAGE;
        JsonBuilder builder = new JsonBuilder();
        builder.add("page_id", page_id);
        builder.add("report_id", report_id);
        RequestBody requestBody = builder.build();
        return addGsonRequest(context, POST, url, requestBody, requestBody, listener, error);
    }

最后调用VolleyClient的方法,得到完整的url

public String getAbsoluteUrl(String relativeUrl) {
        return !relativeUrl.startsWith("http://") && !relativeUrl.startsWith("https://") ? (relativeUrl.startsWith("/") ? this.getHost() + relativeUrl.substring(1) : this.getHost() + relativeUrl) : relativeUrl;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342