欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android二次封装okhttp网络请求框架

程序员文章站 2024-03-07 14:45:21
...

网络请求框架现在是越来越多越来越成熟了,从最初的自己手敲HttpUrlConnection建立网络连接到后来的Volley,XUtils,再到后来的Okhttp,Retrofit等等,当然这些网络请求框架也能在一定程度上显现出安卓行业在不断完善。为什么我们需要二次封装?因为,虽然网络框架提供的是越来越稳定,性能越来越高的网络请求模块功能,但也仅仅是帮助处理协议、建立网络连接、获取数据等一系列过程,处理数据的过程并没有在框架中进行,因为对数据这块的处理,每个项目可能都或多或少的存在一定的差异,所以这部分就需要我们自己根据手中项目进行二次封装。

本文只是讲述okhttp这个框架的二次封装,当然,也不可能会所有人都实用,但是相对大多数项目来说,还是没问题的。希望对大家有一些帮助吧。

总体思路:封装可以传递Url,参数Map,TAG,解析得到实体类后的回调的方法。

添加依赖,混淆那些杂七杂八的东西我就不提了。直接切入正题。使用过okhttp的同学都知道发送一个基本的异步请求大概是这样:

String url = "http://wwww.baidu.com";
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
        .url(url)
        .get()
        .build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Log.d(TAG, "onFailure");
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Log.d(TAG, "onResponse: " + response.body().string());
    }
});

查看源码可以知道Request的Builder中可以设置的参数有5个

Android二次封装okhttp网络请求框架

当然,其中请求方式是有默认的为“GET”方式,所以如果构建Request的时候不设置Method的话,默认会发起get请求。Header的话可以给此次HTTP请求增加一个头部。

下面贴出我封装好的代码:OkHttpClientManager.java

public class OkHttpClientManager {

    private static OkHttpClientManager mInstance;
    private static OkHttpClient mOkHttpClient;
    private Handler mDelivery;
    private Gson mGson;

    public static OkHttpClientManager getInstance() {
        if (mInstance == null) {
            synchronized (OkHttpClientManager.class) {
                if (mInstance == null) {
                    mInstance = new OkHttpClientManager();
                }
            }
        }
        return mInstance;
    }

    //默认的请求回调类
    private final OkHttpCallback<String> DEFAULT_RESULT_CALLBACK = new OkHttpCallback<String>() {
        @Override
        public void onSuccess(String response) {
        }

        @Override
        public void onFailure(BaseEntity<String> entity, String message) {

        }
    };

    private OkHttpClientManager() {
        mOkHttpClient = new OkHttpClient().newBuilder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
//                .retryOnConnectionFailure(false)// 失败后是否重试
                .cookieJar(CookieJar.NO_COOKIES)
                .build();
        mDelivery = new Handler(Looper.getMainLooper());
        mGson = new Gson();
    }

    /** 取消所有请求请求 */
    public void cancelAll() {
        if (mOkHttpClient == null) return;
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            call.cancel();
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            call.cancel();
        }
    }

    /** 根据Tag取消请求 */
    public void cancelTag(Object tag) {
        if (tag == null || mOkHttpClient == null) return;
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }

    /**
     * 异步get请求
     * @param url
     * @param callback
     * @param tag
     */
    public void getAsyn(String url, final OkHttpCallback callback, Object tag) {
        Request request = new Request.Builder()
                .url(url)
                .get()
                .tag(tag)
                .build();
        deliveryResult(callback, request);
    }

    /**
     * 通用基础的异步的post请求
     *
     * @param url
     * @param callback
     * @param tag
     */
    public void postAsyn(String url, Map<String, String> params, final OkHttpCallback callback, Object tag) {
//        Map<String,Object> param = new HashMap<>();
//        param.putAll(params);
//        postAsynObject(url,param,callback,tag);
        Request request = buildPostRequest(url, params, null, null, tag);
        deliveryResult(callback, request);
    }

    /**
     * 请求参数表单创建(请求参数转为json字符串,字段名为data)
     * @param url
     * @param params
     * @param tag
     * @return
     */
    private Request buildPostRequest(String url, Map<String, String> params, String headKey, String headValue, Object tag) {
        FormBody.Builder builder = new FormBody.Builder();
        if (params != null) {
            Iterator<String> iterator = params.keySet().iterator();
            String key = "";
            while (iterator.hasNext()) {
                key = iterator.next();
                builder.add(key, params.get(key));
            }
            if (AppMgrUtils.getInstance().isDev()) {// 如果为开发版,则打印请求信息、
                String data = mGson.toJson(params);
                LogUtils.i("url:" + url + "\n requestParams:\n" + data);
            }
        }
        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(url)
                .post(builder.build())
                .tag(tag);
        // 添加token
        requestBuilder.addHeader("token","token_value");
        // 添加header
        if (!StringUtils.getInstance().isEmpty(headKey) && !StringUtils.getInstance().isEmpty(headValue)) {
            requestBuilder.addHeader(headKey,headValue);
        }
        return requestBuilder.build();
    }

//    public void postAsynObject(String url, Map<String, Object> params, final OkHttpCallback callback, Object tag) {
//        postAsynHeadObject(url,params,null,null,callback,tag);
//    }

//    /**
//     * 含header的请求
//     * @param url
//     * @param params
//     * @param headKey
//     * @param headValue
//     * @param callback
//     * @param tag
//     */
//    public void postAsynHead(String url, Map<String, String> params, String headKey, String headValue, final OkHttpCallback callback, Object tag) {
//        Map<String,Object> param = new HashMap<>();
//        param.putAll(params);
//        postAsynHeadObject(url,param,headKey,headValue,callback,tag);
//    }
//
//    public void postAsynHeadObject(String url, Map<String, Object> params, String headKey, String headValue, final OkHttpCallback callback, Object tag){
//        Request request = buildPostFormRequest(url, params, headKey, headValue, tag);
//        deliveryResult(callback, request);
//    }

    /**
     * 传参下载文件
     * @param url
     * @param params
     * @param fileDir
     * @param fileName
     * @param callback
     * @param tag
     */
    public void postAsynDownload(String url, Map<String, String> params, String fileDir, String fileName, final OkHttpDownLoadCallback callback, Object tag) {
//        Map<String,Object> param = new HashMap<>();
//        param.putAll(params);
//        Request request = buildPostFormRequest(url, param, null, null, tag);
        Request request = buildPostRequest(url, params, null, null, tag);
        downloadResult(fileDir, fileName, callback, request);
    }

    /**
     * 不传参get下载文件
     * @param url
     * @param fileDir
     * @param fileName
     * @param callback
     * @param tag
     */
    public void getAsynDownload(String url, String fileDir, String fileName, final OkHttpDownLoadCallback callback, Object tag) {
        Request request = new Request.Builder()
                        .url(url)
                        .build();
        if (AppMgrUtils.getInstance().isDev()) {
            LogUtils.i("url:" + url);
        }
        downloadResult(fileDir,fileName, callback, request);
    }

    /**
     * 请求参数表单创建(请求参数转为json字符串,字段名为data)
     * @param url
     * @param params
     * @param tag
     * @return
     */
    private Request buildPostFormRequest(String url, Map<String, Object> params, String headKey, String headValue, Object tag) {
        FormBody.Builder builder = new FormBody.Builder();
        if (params == null) {
            builder.add("data", "");
        } else {
            String data = mGson.toJson(params);
            builder.add("data", data);
            if (AppMgrUtils.getInstance().isDev()) {
                LogUtils.i("url:" + url + "\n requestParams:\n" + data);
            }
        }
        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(url)
                .post(builder.build())
                .tag(tag);
        // 添加token
        String token = AppMgrUtils.getInstance().getToken();
        if (!StringUtils.getInstance().isEmpty(token)) {
            requestBuilder.addHeader("token",token);
        }
        // 添加header
        if (!StringUtils.getInstance().isEmpty(headKey) && !StringUtils.getInstance().isEmpty(headValue)) {
            requestBuilder.addHeader(headKey,headValue);
        }
        return requestBuilder.build();
    }

    /**
     * 发送json请求
     * @param url
     * @param jsonParams
     * @param callback
     */
    public void postJsonAsyn(String url, String jsonParams, final OkHttpCallback callback) {
        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8")
                , jsonParams);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        deliveryResult(callback, request);
    }

    /**
     * 上传文件
     *
     * @param url
     * @param pathName
     * @param fileName
     * @param callback
     */
    public void uploadFile(String url, String pathName, String fileName, OkHttpCallback callback) {
        //判断文件类型
        MediaType MEDIA_TYPE = MediaType.parse(judgeType(pathName));
        //创建文件参数
        MultipartBody.Builder builder = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart(MEDIA_TYPE.type(), fileName, RequestBody.create(MEDIA_TYPE, new File(pathName)));
        //发出请求参数
        Request request = new Request.Builder()
//                .header("Authorization", "Client-ID " + "9199fdef135c122")
                .url(url)
                .post(builder.build())
                .build();
        deliveryResult(callback, request);
    }


    /**
     * 根据文件路径判断MediaType
     * @param path
     * @return
     */
    private static String judgeType(String path) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }

    /**
     * @param destFileDir  下载的文件储存目录
     * @param destFileName 下载文件名称
     * @param listener     下载监听
     */

    public void downloadResult(final String destFileDir, final String destFileName, final OkHttpDownLoadCallback listener, final Request request) {

        //异步请求
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // 下载失败监听回调
                listener.onDownloadFailed(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                InputStream is = null;
                byte[] buf = new byte[2048];
                int len = 0;
                FileOutputStream fos = null;

                //储存下载文件的目录
                File dir = new File(destFileDir);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File file = new File(dir, destFileName);

                try {

                    is = response.body().byteStream();
                    long total = response.body().contentLength();
                    if (AppMgrUtils.getInstance().isDev()) {
                        LogUtils.i("file size:" + (total * 1.0f) / 1024 + " KB");
                    }
                    fos = new FileOutputStream(file);
                    long sum = 0;
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                        sum += len;
                        int progress = (int) (sum * 1.0f / total * 100);
                        //下载中更新进度条
                        listener.onDownloading(progress);
                    }
                    fos.flush();
                    //下载完成
                    listener.onDownloadSuccess(file);
                } catch (Exception e) {
                    listener.onDownloadFailed(e);
                    e.printStackTrace();
                }finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                        if (fos != null) {
                            fos.close();
                        }
                    } catch (IOException e) {

                    }
                }
            }
        });
    }


    /**
     * 请求回调处理方法并传递返回值
     *
     * @param callback Map类型请求参数
     * @param request  Request请求
     */
    private void deliveryResult(OkHttpCallback callback, final Request request) {
        if (callback == null)
            callback = DEFAULT_RESULT_CALLBACK;
        final OkHttpCallback resCallBack = callback;
        //UI thread
        callback.onBefore(request);
        mOkHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                sendFailedStringCallback(request, BaseApplication.getContext().getString(R.string.error_network_connected), resCallBack);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    final String responseMessage = response.message();
                    final String responseBody = response.body().string();
                    if (AppMgrUtils.getInstance().isDev()) {// 开发版,打印结果信息
                        LogUtils.i("response :\n" + responseBody);
                    }
                    if (resCallBack.mType == String.class) {
                        sendSuccessResultCallback(responseBody, resCallBack);
                    } else {
                        Object o = mGson.fromJson(responseBody, resCallBack.mType);
                        sendSuccessResultCallback(o, resCallBack);
                    }
                } catch (IOException | com.google.gson.JsonParseException e) {
                    sendFailedStringCallback(response.request(), BaseApplication.getContext().getString(R.string.error_json_parse), resCallBack);
                }
            }
        });
    }

    /**
     * 处理请求成功的回调信息方法
     *
     * @param object   服务器响应信息
     * @param callback 回调类
     */
    private void sendSuccessResultCallback(final Object object, final OkHttpCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onSuccess(object);
                callback.onAfter();
            }
        });
    }

    private void sendFailedStringCallback(final Request request, final String message, final OkHttpCallback callback) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
//                callback.onFailure(null, e.getMessage());
                callback.onFailure(null, message);
            }
        });
    }

稍后会贴出其他相关代码,先讲一下这个类中

1.不仅封装了基础的get请求,同时也封装了传参的post请求,post请求对外的主要是postAsyn(String url, Map<String, String> params, final OkHttpCallback callback, Object tag)这个方法,参数列表一目了然,其实一开始我还封装了支持参数为Map<String, Object>的方法,这个根据大家的业务需求来定制吧,当然传递进来,也是需要使用Gson来转换成字符串进行传递的,只是我把需要在外面进行处理的步骤一并封装在里面了,这部分代码我注释掉了,有需要的同学可以自行查看一下,当然,我这里是简单的把所有的参数都直接使用Gson转换为字符串作为参数的value,而key为唯一的data,这里使用的时候需要大家根据自己的需求进行修改。

2.这个类的构造函数中包含了一些okhttp的配置,如:连接超时时间等,同时失败后重连的机制我没有设置。

Android二次封装okhttp网络请求框架

源码中的默认配置如上。需要修改默认配置的,大家可以进行相应的修改。

3.提供了取消所有网络请求和根据tag来取消制定网络请求的方法。

4.deliveryResult方法就是对请求的整个过程了,下面我先贴出回调类的封装

OkHttpCallback.java
public abstract class  OkHttpCallback<T> {
    //这是请求数据的返回类型,包含常见的(Bean,List等)
    Type mType;

    public OkHttpCallback() {
        mType = getSuperclassTypeParameter(getClass());
    }

    /**
     * 通过反射想要的返回类型
     *
     * @param subclass
     * @return
     */
    static Type getSuperclassTypeParameter(Class<?> subclass) {
        Type superclass = subclass.getGenericSuperclass();
        if (superclass instanceof Class) {
            throw new RuntimeException("Missing type parameter.");
        }
        ParameterizedType parameterized = (ParameterizedType) superclass;
        return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
    }

    /**
     * 在请求之前的方法,一般用于加载框展示
     *
     * @param request
     */
    public void onBefore(Request request) {
    }

    /**
     * 在请求之后的方法,一般用于加载框隐藏
     */
    public void onAfter() {
    }

    public abstract void onSuccess(T response);
    public abstract void onFailure(BaseEntity<T> entity, String message);
}

里面也封装好了通过反射获取返回类型的方法,在处理结果的时候回用到。

接下来贴我自己的基础实体类,一般需要两种,返回类型为对象的实体,和返回类型为列表的实体类:

BaseEntity.java

public class BaseEntity<T> {

    private String msg;
    private int code;
    private T data;

    public BaseEntity() {
    }

    public BaseEntity(String message) {
        this.msg = message;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
BaseListEntity.java
public class BaseListEntity<T> {
    private String msg;
    private int code;
    private List<T> data;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }
}

接着讲发起请求以及对返回结果的处理那块

首先,callback的onBefore方法我没有做更多的动作,但是预留出来,因为这一步的时候,其实可以做一些弹框啊之类的操作。相应的在onAfter方法的时候就应该dissmiss掉弹框了。

其次,okhttp本身的回调只有两个

Android二次封装okhttp网络请求框架

至于失败的原因其实有很多,比如,设备本身没有联网,连接超时等等,okhttp本身也对这部分错误进行了分类并且将错误信息写入了IOException中返回,我只是简单处理为“网络异常”错误信息。

成功回调方法中包含有很多信息

Android二次封装okhttp网络请求框架

而我们最关注的其实是ResponseBody的内容,当我们拿到返回内容之后,通过反射获取返回类型,并且使用Gson进行json解析为我们想要得到的实体类,此时需要捕获一下解析异常。由于发起请求是在主线程,但是请求本身的回调是在子线程,所以我们需要使用handler进行处理。这样返回的数据,才是可以UI可以直接使用的数据。当然,token失效的返回码,大家根据自己的业务需求进行处理发出广播跳转到登录界面还是怎么样的操作,就需要大家自己去定制修改了。

5.文件下载和上传的功能,封装有传参和不传参两种方法,下面只是贴一下下载文件的回调接口

OkHttpDownLoadCallback.java
public interface OkHttpDownLoadCallback {
    /**
     * 下载成功之后的文件
     */
    void onDownloadSuccess(File file);

    /**
     * 下载进度
     */
    void onDownloading(int progress);

    /**
     * 下载异常信息
     */

    void onDownloadFailed(Exception e);
}

OK

至此,okhttp的二次封装已经完成,有问题的同学可以留言,如果存在有错误,望大家指出,非常感谢!