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中,而该类的的注释中也有详细说明:


方法说明.png

有兴趣的可以去看一下这个类:okhttp3包下的HttpUrl。

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

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

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

推荐阅读更多精彩内容