android网络请求组件(一)OkHttp3的封装使用

现在程序员的能力是越来越强了,新技术新想法不断的涌现,然后并应用于实战上,有时候自己会不经意的发现手头上用的技术好老,有点脱节的感觉。为了摆脱这种不爽的感觉(其实就是怕被同行歧视),咋办?唯一的出路就是不断的学习!学习!!学习!!!

一、概述

现在的手机APP,能在应用商店上看到的APP,差不多有95%以上都需要网络需求。进而得出网络组件在APP中的重要性是非常高的!既然如此重要,我们战斗强悍的程序员们为了本着服务大众的奉献精神,创造出了很多非常优秀的开源网络框架,例如android-async-http、volley、retrofit、OkHttp3等等。这其中现在最受开发者们欢迎且用的非常多的框架就是retrofit和OkHttp3了。今天我们先聊OkHttp3,等以后我有时间再来和大家聊聊retroft的封装使用。
为什么要聊OkHttp3?我的答案就4个字“简单好用”,我们开发者使用的框架的目的就是方便我们开发,工具越简单、用起来舒服,你说开发者能不爱吗?说了这么多废话,接下来就要开始进入开发正题了,我会已两种实现模式向大家介绍OkHttp3的使用,一种是“基本模式”(也可以叫Demo学习模式),另一种是”高级模式“(也可以叫开发者模式),至于两种模式的区别,请仔细看我接下来的介绍吧。

二、基本使用模式

基本使用模式(Demo学习模式)其实就是我对OkHttp3的一个最基本的使用介绍,没有对其进行封装优化,用起来很简单,但局限性很强,仅作为Demo学习用。
使用之前首先我们要配置环境,这一点相当简单,一句话就可以搞定。在Android Studio环境,找到自己项目下的build.gradle文件,找到dependencies{},然后在里面添加
compile 'com.squareup.okhttp3:okhttp:3.3.0' //okttp依赖
就可以用了。请大家注意,里面的3.3.0是目前我写这篇的文章的版本,大家以后在用的时候最好还是选择最新的版本,因为不同的版本里面的方法可能不同,我之前就遇到这个坑,2.0版有一些方法在3.0上就没了,或者说被其他方法整合优化掉了,这一点请各位一定要注意更新。
搭建好环境后,咱们开始看看OkHttp3的核心类,一共有四个核心类。因此使用起来也就是分别使用这四个
1、OkHttpClient类:这个类用于处理请求,因此第一步就是要创建OkHttpClient实例
2、Request类:这个类用来构建请求参数,如url、请求方式、请求参数、header等
3、Call类:生成一个请求实例,这里可以选择设置是同步执行还是异步执行
4、Response:请求结果响应,包含HTTP响应,返回数据等,这里面就可以拿到接口传来时数据,然后就可以对数据进行处理了。
文字看的不清晰,咱们对着上面的四句话看看代码具体是啥样的。
	// 1、拿到okhttpClient对象
 	OkHttpClient okHttpClient = new OkHttpClient();
	// 2、构造Request
        Request.Builder builder = new Request.Builder();
        Request request = builder
                .get()
                .url(mBaseUrl + "login?username=zbq&userpassword=123")
                .build();
        //  3、将Request请求封装为Call
 	Call call = okHttpClient.newCall(request);
        // 执行Call
	//  Response response = call.execute(); 同步执行
        call.enqueue(new Callback() {  // 异步执行
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtils.e("onFailure" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String res = response.body().toString();
                LogUtils.e(response.body().toString());
                // 4、response回调在子线程中而不是UI线程
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv.setText(res);
                    }
                });
            }
        });
虽然代码里加了注释相信很多人也应该能看的懂,以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。上面我用的Get请求,接下来看看Post请求是什么样子的吧
 	// 1、拿到okhttpClient对象
        OkHttpClient okHttpClient = new OkHttpClient();
        FormBody requestBody = new FormBody.Builder()
                .add("username","huanhuan").add("userpassword","12345").build();
        // 2、构造Request
        // 2.1 Post请求需要构造FormBody
        Request.Builder builder = new Request.Builder();
        Request request = builder.url(mBaseUrl +"login").post(requestBody).build();
        //  3、将Request请求封装为Call
	 Call call = okHttpClient.newCall(request);
        // 执行Call
	// Response response = call.execute(); 同步执行
        call.enqueue(new Callback() {  // 异步执行
            @Override
            public void onFailure(Call call, IOException e) {
                LogUtils.e("onFailure" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String res = response.body().toString();
                LogUtils.e(response.body().toString());
                // response回调在子线程中而不是UI线程
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tv.setText(res);
                    }
                });
            }
        });
是不是Post请求的代码要比Get要多一些,没错,大家都清楚,post的时候,参数是包含在请求体中的;所以我们通过FormBody添加多个String键值对,然后去构造RequestBody,最后完成我们Request的构造。
上述就是OkHttp3的最基本用法,对比两部分代码,大家就能发现是不是有很多重复的地方。一般项目里有很多调用网络的地方,如果每一个功能模块的网络请求都这么写,那还不被项目老大给骂死,即罗嗦,而且可复用性低,所以上述代码只适合小的演示Demo。接下来咱们开始写点儿实际工作中用的例子。
先提前声明一下,这部分内容有点多,而且每个人的想法可能都不一样,我是学习了任志强老师以及张鸿洋的资料后做的总结吧,如果我写的不对或者不好,还请各位大神指正。谢谢。

三、高级模式

这里的高级模式其实就是开发者模式,和上面基本模式的不同之处就是“封装”和“复用”。将OkHttp3在封装一次,方便以后使用。

基本的用法上面我都提到了,接下来我们看看我为封装的OkHttp3网络框架画的思路图:



对着这张图咱们来拆解分析一下,看看要分装那些类

Request封装
首先看最左边的request,这里面包含有请求参数、url、创建对象。请求参数一般都是以键值对的形式,比如uername : haha,我们可以用HashMap<String, String>或HashMap<String, Object>进行存储,然后将其封装到RequestParams类,这样我们在request请求时直接创建一个对象传入参数即可。例如
    public ConcurrentHashMap<String, String> urlParams = new ConcurrentHashMap<>();
    public ConcurrentHashMap<String, Object> fileParams = new ConcurrentHashMap<>()
    public void put(String key, String value) {
        if (key != null && value != null) {
            urlParams.put(key, value);
        }
    }

    public void put(String key, Object object) throws FileNotFoundException {

        if (key != null) {
            fileParams.put(key, object);
        }
    }
请求参数封装好后,接下来开始封装Request核心逻辑。网络请求通常情况下我们都是用的Get和Post请求,所以我们要写两个方法去分别做两个操作,这两个方法可以封装到CommonRequest类中。这个类的作用就是接收请求参数,然后生成Request对象。例如Get请求
 /**
     * 创建一个Get请求
     * @param url
     * @param params
     * @return 返回一个创建好的Get请求对象
     */
    public static Request createGetRequest(String url,RequestParams params){
        StringBuilder stringBuilder = new StringBuilder(url).append("?");
        if(params !=null){
            for (Map.Entry<String,String> entry : params.urlParams.entrySet()){
               stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        return new Request.Builder().url(stringBuilder.substring(0,stringBuilder.length()-1))
                .get().build();
    }
这里我将方法声明为static,就是把它当工具以后方便调用,这里大家也可以用单例对象实现。参数里面的RequestParams就是我们刚刚封装的请求参数,中间那部分逻辑其实就是Get请求中拼接请求参数了。最后我们要构建一个Request对象将参数填充进去然后返回给我们的调用者OkHttpClient。接下来看看Post请求:
 /**
     * 创建一个Post请求
     * @param url
     * @param params
     * @return 返回一个创建好的Post请求对象
     */
    public static Request createPostRequest(String url,RequestParams params){
        FormBody.Builder mFormBodyBuild = new FormBody.Builder();
        if(params !=null){
            for (Map.Entry<String,String> entry : params.urlParams.entrySet()){
                // 将请求参数遍历添加到请求构建类中
                mFormBodyBuild.add(entry.getKey(),entry.getValue());
            }
        }
        // 通过请求构建类的build方法获取到真正的请求体对象
        FormBody formBody = mFormBodyBuild.build();
        return new Request.Builder().url(url).post(formBody).build();
    }
这里大家请注意以下之前有些人可能看到网上用是FormEncodingBuilder,这里为什么用FormBody?因为OkHttp3.x,FormEncodingBuilder已被FormBody取代,所以请大家在用的时候一定要引用最新的网络框架到工程中!!!FormBody的作用就是方便添加键值对表单数据,这里用到了构建者模式,他继承了RequestBody,给大家看看里面的部分源码就明白原理是什么 了
public static final class Builder {
  private final List<String> names = new ArrayList<>();
  private final List<String> values = new ArrayList<>();

  public Builder add(String name, String value) {
    names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true));
    values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true));
    return this;
  }

  public Builder addEncoded(String name, String value) {
    names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true));
    values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true));
    return this;
  }

  public FormBody build() {
    return new FormBody(names, values);
  }
}
这样最左边的Request请求部分咱们就封装完毕,接下来开始封装OkHttp的核心部分OkHttpClient。

OkHttpClient
首先咱们先来描述一下这个类的作用:请求的发送、请求参数的配置以及现在必要的https的支持。创建一个OkHttpClientCnter类。创建好后我们要做一些最基本的网络配置。
    // 为我们的client去配置参数
    static {
        // 创建我们client对象的构建者
        OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
        // 是为构建者填充超时时间
        okHttpBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
        okHttpBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
        okHttpBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
        // 运行请求重定向
        okHttpBuilder.followRedirects(true);
        // https支持
        okHttpBuilder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        okHttpBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());
        // 生成Client对象
        mOkHttpClient = okHttpBuilder.build();
    }
这里我要提一下okHttpBuilder.sslSocketFactory(),这个方法设置用于保护HTTPS连接的套接字工厂和信任管理器,里面可以设置对Https的一个支持,说实话这一块涉及到网络的SSL这一块原理我也不是很清楚(好尴尬)也是参考别人的方法实现,我这里把代码贴出来大家直接用就好了。
    public static SSLSocketFactory initSSLSocketFactory() {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("SSL");
            X509TrustManager[] xTrustArray = new X509TrustManager[]
                    {initTrustManager()};
            sslContext.init(null,
                    xTrustArray, new SecureRandom());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sslContext.getSocketFactory();
    }

    public static X509TrustManager initTrustManager() {
        X509TrustManager mTrustManager = new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[]{};
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }
        };
        return mTrustManager;
    }
好了配置好基本的网络参数后,就可以发送请求了,这里是调用Call的异步处理。
   /**
     * 通过构造好的Get Request,Callback去发送请求
     * @param request
     * @param commCallback
     * @return Call
     */
    public static Call get(Request request, Callback commCallback){
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new commCallback);
        return call;
    }
这样写看起来好像OK了,不过仔细一看感觉还是少了些什么,是不是没有对Callback回调做一个统一的处理啊。的确,接下来我就对最右边的部分Callback进行封装,然后回来优化这块逻辑。

Callback封装

这一块的封装要做的事情有点多,要处理回调函数,处理数据异常,转发消息到UI线程最后还要将消息转化成对应的实体,看起来好麻烦,的确。但是我想说如果这一层封装的很好的话,以后调用时能够省略好多好多代码。何乐而不为呢?接下来我们一个一个的拆解实现。

1、处理回调函数
首先我们需要自定义回调事件监听,或许有人会问,Callback里面不是已经有回调事件,为什么还要自定义?你看源码不是有吗
public interface Callback {
  /**
   * Called when the request could not be executed due to cancellation, a connectivity problem or
   * timeout. Because networks can fail during an exchange, it is possible that the remote server
   * accepted the request before the failure.
   */
  void onFailure(Call call, IOException e);

  /**
   * Called when the HTTP response was successfully returned by the remote server. The callback may
   * proceed to read the response body with {@link Response#body}. The response is still live until
   * its response body is {@linkplain ResponseBody closed}. The recipient of the callback may
   * consume the response body on another thread.
   *
   * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
   * not necessarily indicate application-layer success: {@code response} may still indicate an
   * unhappy HTTP response code like 404 or 500.
   */
  void onResponse(Call call, Response response) throws IOException;
}
的确,源码里是有。但是!这不代表以后还是这些方法,我刚才也提到了,由于OkHttp3的项目负责人太敬业了,总想让自己的代码更加完美好用,于是版本一升级,难免有的方法就过时消失或者被整合到其他方法中了,这个坑我在刚刚在讲Request里的FormBody就提到过了。所以,保险起见就是我们自己重新自定义一个回调事件接口,同时自定还有一个好处就是方便以后好扩展,大家看源码里面就只提供了 两个方法,如果以后我想要看看当前下载进度如何,源码里没有这个监听下载进度的回调方法,那岂不是呵呵了。为此我们要定义实现为好。OK说完了该实现了
/**
 * 自定义事件监听
 * Created by Administrator on 2017/7/23.
 */

public interface DisposeDataListener {
    /**
     * 请求成功回调事件处理
     */
    public void onSuccess(Object responseObj);

    /**
     * 请求失败回调事件处理
     */
    public void onFailure(Object reasonObj);
}
写完回调接口后,我们可以对回调做一下预处理操作,因为我们在处理Json回调时,需要实现将Json对象转化成实体对象,所以我们就需要一个转化成对象的字节码对象,因此我们可以将响应回调Listener和要转换的字节码对象进行封装。例如
/**
 * 相当于对callBack的预处理
 * Created by Administrator on 2017/7/23.
 */

public class DisposeDataHandle {
    public DisposeDataListener mListener = null;
    public Class<?> mClass = null; // 字节码
    public String mSource = null;

    public DisposeDataHandle(DisposeDataListener listener)
    {
        this.mListener = listener;
    }

    public DisposeDataHandle(DisposeDataListener listener, Class<?> clazz)
    {
        this.mListener = listener;
        this.mClass = clazz;
    }

    public DisposeDataHandle(DisposeDataListener listener, String source)
    {
        this.mListener = listener;
        this.mSource = source;
    }
}

2、异常处理

异常处理相比上面的处理回调函数就简单多了,我只需要获取异常的状态码和状态信息就好了。实现如下:
/**
 * 自定义异常
 * Created by Administrator on 2017/7/23.
 */

public class OkHttpException extends Exception {
    private static final long serialVersionUID = 1L;

    /**
     * the server return code
     */
    private int ecode;

    /**
     * the server return error message
     */
    private Object emsg;

    public OkHttpException(int ecode, Object emsg) {
        this.ecode = ecode;
        this.emsg = emsg;
    }

    public int getEcode() {
        return ecode;
    }

    public Object getEmsg() {
        return emsg;
    }
}
好了,接下来就是重头戏转发消息到UI线程以及将Json转化成对应的实体。我将这两个一起封装到JsonCallbackCenter类中,一起实现。

3、4:转发消息到UI线程&将Json转化成对应的实体
首先我们来创建一个JsonCallbackCenter类,然后再来配置一些和服务交互的参数以及基本消息传递的对象,例如:
    // 与服务器返回的字段的一个对应关系
    protected final String RESULT_CODE = "ecode";       // 服务器返回的状态
    protected final int RESULT_CODE_VALUE = 0;          // 服务返回的状态值
    protected final String ERROR_MSG = "emsg";          // 服务器返回的消息
    protected final String EMPTY_MSG = "";              // 空消息
    protected final String COOKIE_STORE = "Set-Cookie"; // decide the server it

    // 自定义异常类型
    protected final int NETWORK_ERROR = -1;             // 网络相关错误
    protected final int JSON_ERROR = -2;                // Json处理相关错误
    protected final int OTHER_ERROR = -3;               // 未知异常错误
    /**
     * 将其它线程的数据转发到UI线程
     */
    private Handler mDeliveryHandler; 			// 进行消息转发
    private DisposeDataListener mListener;		// 自定义事件回调接口
    private Class<?> mClass; 				// 要转换的字节码
接着实现Callback接口,然后就会实现onFailure()、onResponse()两个方法。onFailure()就是我们请求失败时调用的方法,例如:
   @Override
    public void onFailure(final Call call, final IOException ioexception) {
        /**
         * 此时还在非UI线程,因此要转发
         */
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                mListener.onFailure(new OkHttpException(NETWORK_ERROR, ioexception));
            }
        });
    }
由于要将消息转发到Ui线程中去更新界面,所以更新是会抛异常的。这里我们使用handler去通知更新,同时这里我们还用到了刚刚自定义的回到方法onFailure()方法,将异常信息传入即可。接下来就该OnResponse()方法了
 @Override
    public void onResponse(final Call call, final Response response) throws IOException {
        final String result = response.body().string();
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                handleResponse(result); // 处理从接口返回的数据
            }
        });
    }
这里是处理网络请求成功之后的操作,说先就是要拿到服务器返回的数据。然后将这些数据转发到主线程中。这里我又单独写了一个处理数据的方法,就是将Json数据转化成实体对象。
    /**
     * 处理服务器返回的响应数据
     * @param responseObj
     */
    private void handleResponse(Object responseObj) {
        // 为了保证代码的健壮性
        if (responseObj == null || responseObj.toString().trim().equals("")) {
            mListener.onFailure(new OkHttpException(NETWORK_ERROR, EMPTY_MSG));
            return;
        }
        try {
            /**
             * 协议确定后看这里如何修改
             */
            JSONObject result = new JSONObject(responseObj.toString());
            // 开始尝试解析json
            if(result.has(RESULT_CODE)){
                // 从json对象中取出我们的响应码,若为0,则是正确的响应(根据服务状态码来定)
                if (mClass == null) { // 不需要解析,直接返回数据到应用层
                    mListener.onSuccess(result);
                } else {
                    Gson gson = new Gson();
                    Object obj = gson.fromJson(responseObj.toString(),mClass);
                    // 表明正确的转化成实体对象
                    if (obj != null) {
                        mListener.onSuccess(obj);
                    } else {
                        // 返回的不是合法的json
                        mListener.onFailure(new OkHttpException(JSON_ERROR, EMPTY_MSG));
                    }
                }
            }else {
                // 将服务器返回我们的异常回调到应用层去处理
                mListener.onFailure(new OkHttpException(OTHER_ERROR,result.get(RESULT_CODE)));
            }
        } catch (Exception e) {
            mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
            e.printStackTrace();
        }
    }



大致梳理一下上面的代码,首先就是要判断服务器返回的数据是否有效,无效的话直接调用咱们封装的onFailure()。然后根据服务器返回的状态码是否是成功的。接着就是要处理Json数据,就是将Json数据转化成实体对象.这里我们可以使用第三方的Gson库,直接在项目的build.gradle中引入就好了
compile 'com.google.code.gson:gson:2.4'
转化完后再进一步验证自己的转化后的结果,保证把正确的结果传递到UI线程中
好了,现在最后一个CallBack模块也封装好了,还记得刚刚我们在前面实现的Get请求可以优化吗,这里我将封装好的CallBack放进去看看。优化后如下
    /**
     * 通过构造好的Get Request,Callback去发送请求
     * @param request
     * @param handle
     * @return Call
     */
    public static Call get(Request request, DisposeDataHandle handle){
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonJsonCallback(handle));
        return call;
    }
优化后接受到数据后可以预处理好多数据,大大简化我们后面我们调用时的操作。好了封装思路图里的东西我们都已经实现,接下来我们要做最后一层封装。由于我们的请求操作很多,例如注册、登录、详情页、支付等,为了简化我们的请求操作,我们还可以对上述已经封装好的框架在封装一次,当然这里的封装的目的就是简化操作作为API用,以后好统一管理。这里我建立一个叫RequestCenter的类,我以登录请求为例,在RequestCenter的类中写下如下方法:
    //根据参数发送所有post请求
    public static void postRequest(String url, RequestParams params,
                                   DisposeDataListener listener, Class<?> clazz) {
        CommonOkHttpClient.get(CommonRequest.
                createGetRequest(url, params), new DisposeDataHandle(listener, clazz));
    }

    /**
     * 用户登陆请求
     *
     * @param listener
     * @param userName
     * @param passwd
     */
    public static void login(String userName, String passwd, DisposeDataListener listener) {
        RequestParams params = new RequestParams();
        params.put("mb", userName);
        params.put("pwd", passwd);
        RequestCenter.postRequest(HttpConstants.LOGIN, params, listener, User.class);
    }

这个类还可以扩展,以后需要注册请求、下载文件等方法都可以放到这个类中。最后咱们实际调用时是什么样子的
	RequestCenter.login(userName, password, new DisposeDataListener() {
            @Override
            public void onSuccess(Object responseObj) {
                DialogManager.getInstnce().dismissProgressDialog();
                /**
                 * 这部分可以封装起来,封装为到一个登陆流程类中
                 */
                User user = (User) responseObj;
                UserManager.getInstance().setUser(user);//保存当前用户单例对象
                ........
            }

            @Override
            public void onFailure(Object reasonObj) {
                DialogManager.getInstnce().dismissProgressDialog();
            }
        });
    }
现在看看这个网络请求的逻辑是不是相当干净简洁。这就是封装的好处。

花了两天的时间断断续续的总算写好了,这期间也查阅了很多资料,学习了很多知识。由于本人水平有限,若有大神发现错误,还请帮忙指正,非常感谢。










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章