OkHttp3入门介绍

版权所有,转载请注明出处:linzhiyong https://www.jianshu.com/p/af144d662bfd https://blog.csdn.net/u012527802/article/details/81013772

相关文章
1、OkHttp3入门介绍:https://www.jianshu.com/p/af144d662bfd
2、OkHttp3入门介绍之Cookie持久化:https://www.jianshu.com/p/23b35d403148

OkHttp是一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,本文主要介绍OkHttp3的基本使用方法。
官网:http://square.github.io/okhttp/
Github:https://github.com/square/okhttp
OkHttp3Demo传送门:https://github.com/linzhiyong/OkHttp3Demo
服务端Demo传送门:https://github.com/linzhiyong/SpringMVCDemo

结合自己的项目经验,主要从以下几方面介绍:

  1. OkHttpClient基本参数配置介绍
  2. 普通GET请求(同步/异步)
  3. 普通POST请求(同步/异步)
  4. 根据tag取消请求
  5. POST请求提交String
  6. POST请求提交流
  7. POST请求提交JSON(实体转JSON)
  8. POST请求提交普通Form表单
  9. POST请求提交混合Form表单(文本参数+文件)
  10. POST请求提交单/多文件(带进度条)
  11. GET请求下载文件(带进度条)

开发配置

使用AndroidStudio开发,在app的build.gradle文件中增加对okhttp3的依赖:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
}

网络请求需要申请网络权限,需要在AndroidManifest.xml配置:

<uses-permission android:name="android.permission.INTERNET" />

OkHttpClient基本参数介绍

OkHttpClient是通过OkHttpClient.Builder来配置参数,基础参数如下:

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .readTimeout(HTTP_TIME_OUT, TimeUnit.SECONDS)
                    .writeTimeout(HTTP_TIME_OUT, TimeUnit.SECONDS)
                    .connectTimeout(HTTP_TIME_OUT, TimeUnit.SECONDS)
                    );
OkHttpClient okHttpClient = okHttpClient = builder.build();

至于其他参数,如Cookie保持、Https证书设置、Interceptor拦截器设置等,会在后续章节中介绍。

注:
1、这里建议在项目中创造一个OkHttpClient实例并重复使用,这是因为每个实例都有它自己的连接池和线程池,重用连接池和线程池可以减少延迟同时节省内存。
2、本例中,我将OkHttpClient实例封装在LOkHttp3Utils中,使用LOkHttp3Utils.okHttpClient()获取。

普通GET请求(同步/异步)

// 创建请求体
Request request = new Request.Builder()
                .url(url) // 请求地址
                .get() // get请求
                .addHeader("name", "value") // 请求头参数
                .tag("getSync") // 为当前request请求增加tag,可在okHttpClient中使用tag获取到当前请求
                .build();
Call call = LOkHttp3Utils.okHttpClient().newCall(request);
// 同步请求
Response response = call.execute();

// 异步请求
call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i("", "");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                int code = response.code();
                if (code == 200) {
                    String result = response.body().string();
                    Log.i("r", result);
                }
                else {
                    Log.e("e", response.message());
                }
            }
});

注:
1、Call对象作为请求执行者,可以取消请求,同时Call请求只能执行一次;
2、Response作为响应体,获取返回的string使用response.body().string(),获取返回的字节使用response.body().bytes(),获取返回的字节流(如下载文件)使用response.body().byteStream();

普通POST请求(同步/异步)

// 创建请求body,MediaType请求包体类型
RequestBody requestBody = RequestBody.create(MediaType.parse("text/html; charset=utf-8"), content);
// 创建请求体
Request request = new Request.Builder()
        .url(url)
        .post(requestBody)
        .addHeader("name", "value")
        .tag("postSync")
        .build();
Call call = LOkHttp3Utils.okHttpClient().newCall(request);
// 同步请求
Response response = call.execute();

// 异步请求
call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i("", "");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                int code = response.code();
                if (code == 200) {
                    String result = response.body().string();
                    Log.i("r", result);
                }
                else {
                    Log.e("e", response.message());
                }
            }
});

注:RequestBody作为请求提的包体,有多种实现形式:FormBody、MultipartBody,作为提交string、file、stream、form的载体;

根据tag取消请求

在创建请求Request的时候,如果添加了tag属性,可以通过tag取消请求,下面是具体实现:

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

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

注:为省去一些重复的代码量,下面列出的关于提交string、json、file...方法,本文只写到如何创建RequestBody,后续的创建Request、Call、同步异步调用不在重复罗列;

POST请求提交String

// 提交普通字符串
String content = "普通字符串";
RequestBody requestBody = RequestBody.create(MediaType.parse("text/html; charset=utf-8"), content);

POST请求提交流

RequestBody requestBody = new RequestBody() {
    @Override
    public MediaType contentType() {
        return MediaType.parse("application/octet-stream; charset=utf-8");
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("字符串作为流提交");
        // 重写此处可以实现文件上传进度检测
    }
};

POST请求提交JSON(实体转JSON)

// 提交JSON
String bodyStr = "json";
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), bodyStr);

POST请求提交普通Form表单

FormBody formBody = new FormBody.Builder()
        .add("key1", "value1")
        .add("key2", "value2")
        .add("key3", "value3")
        .build();

POST请求提交混合Form表单(文本参数+文件)

File file = new File("filePath");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream; charset=utf-8"), file);

// 方式一
RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("key1", "value1")
        .addFormDataPart("key2", "value2")
        .addFormDataPart("key3", "value3")
        .addFormDataPart("file1", "name1", fileBody)
        .build();

// 方式二
FormBody formBody = new FormBody.Builder()
        .add("key1", "value1")
        .add("key2", "value2")
        .add("key3", "value3")
        .build();
RequestBody requestBody1 = new MultipartBody.Builder()
        .addPart(Headers.of(
                "Content-Disposition",
                "form-data; name=\"params\""),
                formBody)
        .addPart(Headers.of(
                "Content-Disposition",
                "form-data; name=\"file\"; filename=\"plans.xml\""),
                fileBody)
        .build();

POST请求提交单/多文件(带进度条)

1. 提交单文件(不带进度条)
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test.txt");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);

// 方式一
RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("file1", file.getName(), fileBody)
        .build();

// 方式二
RequestBody requestBody1 = new MultipartBody.Builder()
        .addPart(Headers.of(
                "Content-Disposition",
                "form-data; name=\"file1\"; filename=\"" + file.getName() + "\""),
                fileBody)
        .build();
2. 提交多文件(不带进度条)
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test.txt");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody fileBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file);

// 方式一
RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("file1", file.getName(), fileBody)
        .addFormDataPart("file2", file.getName(), fileBody2)
        .build();

// 方式二
RequestBody requestBody1 = new MultipartBody.Builder()
        .addPart(Headers.of(
                "Content-Disposition",
                "form-data; name=\"file1\"; filename=\"" + file.getName() + "\""),
                fileBody)
        .addPart(Headers.of(
                "Content-Disposition",
                "form-data; name=\"file2\"; filename=\"" + file.getName() + "\""),
                fileBody2)
        .build();
3. 提交单文件(!!!带进度条!!!)

根据前面提交流的方式,实现RequestBody的writeTo(BufferedSink sink)方法监听文件传输进度:

// 使用方式
RequestBody fileBody = LOkHttp3Utils.createProgressRequestBody(MediaType.parse("application/octet-stream"), file, 
  new ProgressListener() {
    @Override
    public void onStart() {

    }

    @Override
    public void onProgress(long total, float progress) {
        // progress 显示当前进度
    }

    @Override
    public void onFinish(File file) {

    }

    @Override
    public void onError(Exception e) {

    }
});

/**
 * 创建文件requestbody,自定义进度监听器
 *
 * @param mediaType
 * @param file
 * @param listener
 * @return
 */
public static RequestBody createProgressRequestBody(final MediaType mediaType, final File file,
       final ProgressListener listener) {
    return new RequestBody() {
        @Override
        public MediaType contentType() {
            return mediaType;
        }

        @Override
        public long contentLength() throws IOException {
            return file.length();
        }

        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            listener.onStart();
            Source source;
            try {
                source = Okio.source(file);
                //sink.writeAll(source);
                Buffer buf = new Buffer();
                Long remaining = contentLength();
                for (long readCount; (readCount = source.read(buf, 2048)) != -1; ) {
                    sink.write(buf, readCount);
                    listener.onProgress(contentLength(), 1 - (float)(remaining -= readCount) / contentLength());
                }
                listener.onFinish(file);
            } catch (Exception e) {
                listener.onError(e);
                e.printStackTrace();
            }
        }
    };
}

定义ProgressListener接口作为上传下载进度监听器:

public interface ProgressListener {

    void onStart();

    void onProgress(long total, float progress);

    void onFinish(File file);

    void onError(Exception e);

}

GET请求下载文件(带进度条)

此处需要重新实现Callback接口,获取到Response对象中的文件流,进行文件保存操作,如需监听进度,则在文件流读取的时候进行处理;

1. 下载文件(不带进度条)
// 先看使用效果
Request request = new Request.Builder()
        .url(url) // 文件地址
        .get()
        .addHeader("name", "value")
        .tag("getFileAsync")
        .build();

final Call call = LOkHttp3Utils.okHttpClient().newCall(request);
call.enqueue(new FileNoProgressCallback(Environment.getExternalStorageDirectory().getAbsolutePath(), "test.png") {
    @Override
    public void onFinish(File file) {

    }

    @Override
    public void onError(Exception e) {

    }
});

// 实现Callback接口,进行文件保存操作
public abstract class FileNoProgressCallback implements Callback {

    private String destFileDir;

    private String destFileName;

    public FileNoProgressCallback(String destFileDir, String destFileName) {
        this.destFileDir = destFileDir;
        this.destFileName = destFileName;
    }

    @Override
    public void onFailure(Call call, IOException e) {
        onError(e);
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        this.saveFile(response);
    }

    private void saveFile(Response response) throws IOException {
        InputStream is = null;
        byte[] buf = new byte[2048];
        FileOutputStream fos = null;

        try {
            is = response.body().byteStream();
            final long total = response.body().contentLength();
            long sum = 0L;
            File dir = new File(this.destFileDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }

            File file = new File(dir, this.destFileName);
            fos = new FileOutputStream(file);

            int len = 0;
            while ((len = is.read(buf)) != -1) {
                sum += (long) len;
                fos.write(buf, 0, len);
            }

            fos.flush();
            onFinish(file);
        } catch (Exception e) {
            onError(e);
        } finally {
            try {
                response.body().close();
                if (is != null) {
                    is.close();
                }
            } catch (IOException var23) {
            }

            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException var22) {
            }
        }
    }

    public abstract void onFinish(File file);

    public abstract void onError(Exception e);

}
2. 下载文件(!!!带进度条!!!)
// 使用方式
Request request = new Request.Builder()
        .url(url) // 文件地址
        .get()
        .addHeader("name", "value")
        .tag("getFileProgressAsync")
        .build();

final Call call = LOkHttp3Utils.okHttpClient().newCall(request);
call.enqueue(new FileCallback(Environment.getExternalStorageDirectory().getAbsolutePath(), "test.png") {
    @Override
    public void onStart() {

    }

    @Override
    public void onProgress(long total, float progress) {

    }

    @Override
    public void onFinish(File file) {

    }

    @Override
    public void onError(Exception e) {

    }
});

// FileCallback同样实现了Callback,但是多了对下载进度的监听
public abstract class FileCallback implements Callback, ProgressListener {

    private String destFileDir;

    private String destFileName;

    public FileCallback(String destFileDir, String destFileName) {
        this.destFileDir = destFileDir;
        this.destFileName = destFileName;
    }

    @Override
    public void onFailure(Call call, IOException e) {
        onError(e);
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        this.saveFile(response);
    }

    private void saveFile(Response response) throws IOException {
        onStart();
        InputStream is = null;
        byte[] buf = new byte[2048];
        FileOutputStream fos = null;

        try {
            is = response.body().byteStream();
            final long total = response.body().contentLength();
            long sum = 0L;
            File dir = new File(this.destFileDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }

            File file = new File(dir, this.destFileName);
            fos = new FileOutputStream(file);

            int len = 0;
            while ((len = is.read(buf)) != -1) {
                sum += (long) len;
                fos.write(buf, 0, len);
                onProgress(total, (float) sum * 1.0F / (float) total);
            }

            fos.flush();
            onFinish(file);
        } catch (Exception e) {
            onError(e);
        } finally {
            try {
                response.body().close();
                if (is != null) {
                    is.close();
                }
            } catch (IOException var23) {
            }

            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException var22) {
            }
        }
    }

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

推荐阅读更多精彩内容