OkHttp:一次不太高级的封装

背景:
最近翻看了一下OkHttp的源码,看完之后想自己重新封装一个OkHttp框架,同时检验一下自己的水平。
功能:
1.get请求:

// url:请求地址
//MyCallBack<T>:结果回调,目前只支持返回参数为Json和String,
//              此时传入的是WeatherEntity,那么返回的是通过Gson把数据解析成WeatherEntity的数据
OkHttpUtils.getInstance().get(url, new MyCallBack<WeatherEntity>() {
                    @Override
                    public void onSuccess(WeatherEntity weatherEntity) {
                        WeatherEntity mWeatherEntity = weatherEntity;
                        tv_result.setText(mWeatherEntity.getStatus());
                    }
                     @Override
                    public void onFail(String err) {
                        super.onFail(err);
                    }
                });

目前get请求分为onSuccess的回调接口和onFail的回调接口

2.文件下载:

//url:请求地址
//path:文件下载的目录位置
//MyCallBack<T>:结果回调,目前只支持返回参数为Json和String,
//              此时传入的是WeatherEntity,那么返回的是通过Gson把数据解析成WeatherEntity的数据

OkHttpUtils.getInstance().download(url, path, new MyCallBack<String>() {
            @Override
            public void onSuccess(String s) {
                super.onSuccess(s);
                Log.e("TAG","成功");
                tvResult.setText("成功");
            }
            @Override
            public void onFail(String err) {
                super.onFail(err);
            }
            @Override
            public void onDownloading(int progress) {
                super.onDownloading(progress);
                Log.e("onDownloading","下载进度"+progress);
                tvResult.setText(progress+"");
            }
        });

目前文件下载只有成功、失败和下载进度监听。

尴尬之处:因为没有后台人员配合,而网上的提供的开源的接口只有GET请求的接口,所以目前只封装了这两种请求。如果哪位大佬能提供好POST请求的接口或者知道哪里有开源的,还请告知,在此多谢。
好的,下面开始介绍:
首先大家知道OkHttp的一般请求格式为:

  //  第一步:创建 OkHttpClient 对象
        OkHttpClient okHttpClient = new OkHttpClient();
         //  第二步:创建 Request 对象
                    Request request = new Request.Builder()
                            .url(url)
                            .build();
            //  第三步:发起 HTTP 请求
                //没有回调接口
                    okHttpClient.newCall(request).execute();
                //有回调接口
                okHttpClient.newCall(request).enqueue(callBack);

构建一个网络请求分为三步:
第一步:创建 OkHttpClient 对象
第二步:创建 Request 对象
第三步:发起 HTTP 请求
注意: okHttpClient.newCall(request).enqueue(callBack);中的callBack的回调不是在主线程,这点需要注意。
开始封装:
首先:我们需要知道一点就是OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。 如果有需要,可以使用clone方法,再进行自定义。所以就必须要使用单例了:

/**
 * Created by Administrator on 2017/12/23.
 */

public class OkHttpUtils {
    private static OkHttpUtils okHttpUtils = null;
    private OkHttpClient httpClient;
    private Gson mGson;
    private Handler mDelivery;

    private OkHttpUtils() {
        //创建okHttpClient
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //添加拦截器
        builder.addInterceptor(new HttpParamInterceptor());
        httpClient = builder.build();
        //因为回调默认是在子线程,所以想在UI线程处理就要用到Handler
        mDelivery = new Handler(Looper.getMainLooper());
    }
    public static OkHttpUtils getInstance() {
        if (okHttpUtils == null) {
            synchronized (OkHttpUtils.class) {
                if (okHttpUtils == null) {
                    okHttpUtils = new OkHttpUtils();
                }
            }
        }
        return okHttpUtils;
    }
    //异步get请求
    public void get(String url, final MyCallBack myCallBack) {
        //创建请求
        Request request = new Request.Builder()
                .url(url)
                .build();
        httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                String err = "请求失败";
                sendFailResult(myCallBack, err);
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                sendSuccessResult(myCallBack, response.body().string());
            }
        });
    }

    //异步文件下载
    public void download(final String url, final String destFileDir, final MyCallBack myDownloadCallBack) {
        //创建请求
        Request request = new Request.Builder()
                .url(url)
                .build();
        httpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                String err = "请求失败";
                sendFailResult(myDownloadCallBack, err);
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //判断文件夹是否存在,如果不存在就创建文件夹
                File dirFolder = new File(destFileDir);
                if (!dirFolder.exists()) { //如果该文件夹不存在,则进行创建
                    dirFolder.mkdirs();//创建文件夹
                }
                InputStream is = null;
                byte[] buf = new byte[2048];
                int len = 0;
                FileOutputStream fos = null;

                is = response.body().byteStream();
                long total = response.body().contentLength();
                File file = new File(destFileDir, "QQ.apk");
                fos = new FileOutputStream(file);
                long sum = 0;
                while ((len = is.read(buf)) != -1) {
                    fos.write(buf, 0, len);

                    sum += len;
                    final int progress = (int) (sum * 1.0f / total * 100);
                    // 下载中
//                            myDownloadCallBack.onDownloading(progress);
                    sendDownloadProgress(myDownloadCallBack, progress);
                }
                fos.flush();
                //如果下载文件成功,第一个参数为文件的绝对路径

                sendSuccessResult(myDownloadCallBack, file.getAbsolutePath());
            }
        });
    }

    private void sendDownloadProgress(final MyCallBack myDownloadCallBack, final int progress) {
        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                myDownloadCallBack.onDownloading(progress);

            }
        });
    }

    /**
     * 根据请求地址判断下载文件的名称
     * @param path
     * @return
     */
    private String getFileName(String path) {
        int separatorIndex = path.lastIndexOf("/");
        return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
    }

    /**
     * 在主线程返回成功结果
     * @param myCallBack
     * @param response
     */
    private void sendSuccessResult(final MyCallBack myCallBack, final String response) {

        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                if (myCallBack.mType == String.class) {
                    myCallBack.onSuccess(response);

                } else {
                    mGson = new Gson();
                    Object o = mGson.fromJson(response, myCallBack.mType);
                    myCallBack.onSuccess(o);
                }
            }
        });
    }

    /**
     * 在主线程返回失败结果
     */
    private void sendFailResult(final MyCallBack myCallBack, final String err) {

        mDelivery.post(new Runnable() {
            @Override
            public void run() {

                myCallBack.onFail(err);
            }
        });
    }

}

这里有一点需要说明:
如果你的后台返回的格式是统一的:

{
    "status": "0",
    "mag": "1",
    "data": ""
}

那么你可以在sendSuccessResult对数据进行第一次处理,将处理后的数据在返回给调用方。

其实通过上述代码,就能很好的看出来所封装的东西了,不过大家应该看到了,我在构建OkHttpClient添加了一个HttpParamInterceptor拦截器,因为OkHttp最核心的东西就是Interceptor(拦截器)。不要误以为它只是负责拦截请求的一些额外的处理(例如cookie),实际上他把实际的网路请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个Intercepter,他们再连接成一个Intercepter.Chain。最终圆满完成了一次网络请求。这里不在过多介绍,想了解的可以查阅相关文章。

/**
 * Created by RF
 * on 2017/11/28.
 * 请求时数据处理拦截器
 */

public class HttpParamInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl.Builder httpUrl = request.url().newBuilder()
        //添加同一参数 如手机唯一标识符,token等
        .addQueryParameter("city","CHSH000000");
        Request newrequest = new Request.Builder()
        //添加公共的头部
        .addHeader("User-Agent", "test")
                .method(request.method(),request.body())
                .url(httpUrl.build())
                .build();
//        printReponseMessage(chain.proceed(newrequest),httpUrl.build().toString());
        return chain.proceed(newrequest);
    }
    /**
     * 打印网络请求日志
     * @param response
     */
    private void printReponseMessage(Response response, String url)  throws IOException{
        ResponseBody body = response.body();
        BufferedSource source = body.source();
        source.request(Long.MAX_VALUE);
        Buffer buffer = source.buffer();
        Charset charset = Charset.defaultCharset();
        MediaType contentType = body.contentType();
        if (contentType != null) {
            charset = contentType.charset(charset);
        }
        String bodyString = buffer.clone().readString(charset);
        Log.e("OKHttp",url+ bodyString);
    }

}

这里有一点需要说明:之前在网上见过,有人说添加公共参数的时候需要先判断GET请求和POST请求,然后在对应的添加公共参数,意思是GET请求和POST请求添加公共参数的方式不一样,必须通过判断分别对应添加,这里我们看一下addQueryParameter的源码:

 /** Encodes the query parameter using UTF-8 and adds it to this URL's query string.
     使用UTF-8编码的查询参数并将其添加到这个URL的查询字符串。 
 */
    public Builder addQueryParameter(String name, @Nullable String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (encodedQueryNamesAndValues == null) encodedQueryNamesAndValues = new ArrayList<>();
      encodedQueryNamesAndValues.add(
          canonicalize(name, QUERY_COMPONENT_ENCODE_SET, false, false, true, true));
      encodedQueryNamesAndValues.add(value != null
          ? canonicalize(value, QUERY_COMPONENT_ENCODE_SET, false, false, true, true)
          : null);
      return this;
    }

其实不需要过多的了解方法内的代码,只需要通过注释就会发现,该方法是把传入的key-value解析之后拼在url中,而该类的的注释中也有详细说明:
方法说明
有兴趣的可以去看一下这个类:okhttp3包下的HttpUrl。

不完善之处:没有加入网络缓存,起初是打算使用第三方的Hawk数据库,不过OkHttp本身有负责读取缓存直接返回、更新缓存的CacheInterceptor;所以缓存处理还没有加上。

不足之处请多多指教。
项目地址为:https://github.com/fengxiaobing/MyOKHttp
(觉得还不错的希望给一个star。 QAQ)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值