OkHttp使用详解

今天学习了一下OkHttp,在这里做个总结,希望可以帮助到有需要的人,好了,废话不多说,进入正题。

一、OkHttp介绍

OkHttp是一个优秀的网络请求框架,可能一说到网络请求框架,可能很多人都会想到volley,volley是一个Google提供的网络请求框架,我的博客里也有一篇专门介绍volley的博客,博客地址在此Android网络请求 ------ Volley的使用 那么既然Google提供了网络请求的框架,我们为什么还要使用OkHttp呢,原来是volley是要依靠HttpCient的,而Google在Android6.0的SDK中去掉了HttpCient,所以OkHttp就开始越来越受大家的欢迎.

今天我们主要介绍OkHttpGet请求、Post请求、上传下载文件上传下载图片等功能

当然在开始之前,我们还要先在项目中添加OkHttp的依赖库,至于怎么在AndroidStudio中给项目添加OkHTTP依赖,这里将不再赘述。另外,OkHttp中使用了建造者模式,如果对建造者模式不了解,可以看看这篇博客设计模式之建造者模式**

添加OkHttp的依赖

在对应的Module的gradle中添加
compile 'com.squareup.okhttp3:okhttp:3.5.0'   
然后同步一下项目即可

二、OkHttp进行Get请求

使用OkHttp进行Get请求只需要四步即可完成。

1 . 拿到OkHttpClient对象

OkHttpClient client = new OkHttpClient();

2 . 构造Request对象

Request request = new Request.Builder()
                .get()
                .url("https:www.baidu.com")
                .build();

这里我们采用建造者模式和链式调用指明是进行Get请求,并传入Get请求的地址

如果我们需要在get请求时传递参数,我们可以以下面的方式将参数拼接在url之后

https:www.baidu.com?username=admin&password=admin

3 . 将Request封装为Call

Call call = client.newCall(request);

4 . 根据需要调用同步或者异步请求方法

//同步调用,返回Response,会抛出IO异常
Response response = call.execute();

//异步调用,并设置回调函数
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Toast.makeText(OkHttpActivity.this, "get failed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onResponse(Call call, final Response response) throws IOException {
        final String res = response.body().string();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                contentTv.setText(res);
            }
        });
    }
});

第四步有一些需要注意的地方

  1. 同步调用会阻塞主线程,一般不适用
  2. 异步调用的回调函数是在子线程,我们不能在子线程更新UI,需要借助于runOnUiThread()方法或者Handler来处理

是不是以为上面就结束了,对的,OkHttp的Get请求步骤就这么4步,但是当你试图打开应用加载数据,可是发现并没有加载到数据,这是一个简单但是我们常犯的错误.
在AndroidManifest.xml中加入联网权限

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

三、OkHttp进行Post请求提交键值对

使用OkHttp进行Post请求和进行Get请求很类似,只需要五步即可完成。

1 . 拿到OkHttpClient对象

OkHttpClient client = new OkHttpClient();

2 . 构建FormBody,传入参数

FormBody formBody = new FormBody.Builder()
                .add("username", "admin")
                .add("password", "admin")
                .build();

3 . 构建Request,将FormBody作为Post方法的参数传入

final Request request = new Request.Builder()
                .url("http://www.jianshu.com/")
                .post(formBody)
                .build();

4 . 将Request封装为Call

Call call = client.newCall(request);

5 . 调用请求,重写回调方法

call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        Toast.makeText(OkHttpActivity.this, "Post Failed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        final String res = response.body().string();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                contentTv.setText(res);
            }
        });
    }
});

经过上面的步骤一个post请求就完成了,当然上面的url参数和需要传入的参数大家就要根据实际情况来传入,你会发现get和post请求的步骤非常像。

四、OkHttp进行Post请求提交字符串

如果你已经掌握了上面的两种基本的步骤,那下面的内容就比较简单了

上面我们的post的参数是通过构造一个FormBody通过键值对的方式来添加进去的,其实post方法需要传入的是一个RequestBody对象,FormBodyRequestBody的子类,但有时候我们常常会遇到要传入一个字符串的需求,比如客户端给服务器发送一个json字符串,那这种时候就需要用到另一种方式来构造一个RequestBody如下所示:

RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "{username:admin;password:admin}");

上面的MediaType我们指定传输的是纯文本,而且编码方式是utf-8,通过上面的方式我们就可以向服务端发送json字符串啦。

注:关于MidiaType的类型你可以百度搜索mime type查看相关的内容,这里不再赘述

五、OkHttp进行Post请求上传文件

理解了上面一个,下面这个就更简单了,这里我们以上传一张图片为例,当然你也可以上传一个txt什么的文件,都是可以的

其实最主要的还是构架我们自己的RequestBody,如下图构建

File file = new File(Environment.getExternalStorageDirectory(), "1.png");
if (!file.exists()){
    Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
}else{
    RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file);
}

这里我们将手机SD卡根目录下的1.png图片进行上传。代码中的application/octet-stream表示我们的文件是任意二进制数据流,当然你也可以换成更具体的image/png

注:最后记得最重要的一点:添加存储卡写权限,在AndroidManifest.xml文件中添加如下代码:

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

六、OkHttp进行Post请求提交表单

我们在网页上经常会遇到用户注册的情况,需要你输入用户名,密码,还有上传头像,这其实就是一个表单,那么接下来我们看看如何利用OkHttp来进行表单提交。经过上面的学习,大家肯定也懂,主要的区别就在于构造不同的RequestBody传递给post方法即可.

由于我们使用的是OkHttp3所以我们还需要再导入一个包okio.jar才能继续下面的内容,我们需要在模块的Gradle文件中添加如下代码,然后同步一下项目即可

compile 'com.squareup.okio:okio:1.11.0'

这里我们会用到一个MuiltipartBody,这是RequestBody的一个子类,我们提交表单就是利用这个类来构建一个RequestBody,下面的代码我们会发送一个包含用户民、密码、头像的表单到服务端

File file = new File(Environment.getExternalStorageDirectory(), "1.png");
if (!file.exists()){
    Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
    return;
}
RequestBody muiltipartBody = new MultipartBody.Builder()
        //一定要设置这句
        .setType(MultipartBody.FORM)
        .addFormDataPart("username", "admin")//
        .addFormDataPart("password", "admin")//
        .addFormDataPart("myfile", "1.png", RequestBody.create(MediaType.parse("application/octet-stream"), file))
        .build();

上面添加用户民和密码的部分和我们上面学习的提交键值对的方法很像,我们关键要注意以下几点:

(1)如果提交的是表单,一定要设置setType(MultipartBody.FORM)这一句

(2)提交的文件addFormDataPart()的第一个参数,就上面代码中的myfile就是类似于键值对的键,是供服务端使用的,就类似于网页表单里面的name属性,例如下面:

<input type="file" name="myfile">

(3)提交的文件addFormDataPart()的第二个参数文件的本地的名字,第三个参数是RequestBody,里面包含了我们要上传的文件的路径以及MidiaType

(4)记得在AndroidManifest.xml文件中添加存储卡读写权限

七、OkHttp进行get请求下载文件

除了上面的功能,我们最常用的功能该有从网路上下载文件,我们下面的例子将演示下载一个文件存放在存储卡根目录,从网络下载一张图片并显示到ImageView中

1 . 从网络下载一个文件(此处我们以下载一张图片为例)

public void downloadImg(View view){
    OkHttpClient client = new OkHttpClient();
    final Request request = new Request.Builder()
            .get()
            .url("https://www.baidu.com/img/bd_logo1.png")
            .build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.e("moer", "onFailure: ");;
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //拿到字节流
            InputStream is = response.body().byteStream();

            int len = 0;
            File file  = new File(Environment.getExternalStorageDirectory(), "n.png");
            FileOutputStream fos = new FileOutputStream(file);
            byte[] buf = new byte[128];

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

            fos.flush();
            //关闭流
            fos.close();
            is.close();
        }
    });
}

你会发现步骤与进行一般的Get请求差别不大,唯一的区别在于我们在回调函数中所做的事,我们拿到了图片的字节流,然后保存为了本地的一张图片

2 . 从网络下载一张图片并设置到ImageView中

其实学会了上面的步骤你完全可以将图片下载到本地后再设置到ImageView中,当然下面是另一种方法
这里我们使用BitmapFactorydecodeStream将图片的输入流直接转换为Bitmap,然后设置到ImageView中,下面只给出onResponse()中的代码.

@Override
public void onResponse(Call call, Response response) throws IOException {
    InputStream is = response.body().byteStream();

    final Bitmap bitmap = BitmapFactory.decodeStream(is);
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            imageView.setImageBitmap(bitmap);
        }
    });

    is.close();
}

八、给文件的上传和下载加上进度条

我们一直都说,用户体验很重要,当我们下载的文件比较大,而网速又比较慢的时候,如果我们只是在后台下载或上传,没有给用户显示一个进度,那将是非常差的用户体验,下面我们就将简单做一下进度的显示,其实非常简单的

1 . 显示文件下载进度

这里只是演示,我只是把进度显示在一个TextView中,至于进度的获取当然是在我们的回调函数onResponse()中去获取

(1)使用response.body().contentLength()拿到文件总大小

(2)在while循环中每次递增我们读取的buf的长度

@Override
public void onResponse(Call call, Response response) throws IOException {
    InputStream is = response.body().byteStream();
    long sum = 0L;
    //文件总大小
    final long total = response.body().contentLength();
    int len = 0;
    File file  = new File(Environment.getExternalStorageDirectory(), "n.png");
    FileOutputStream fos = new FileOutputStream(file);
    byte[] buf = new byte[128];

    while ((len = is.read(buf)) != -1){
        fos.write(buf, 0, len);
        //每次递增
        sum += len;

        final long finalSum = sum;
        Log.d("pyh1", "onResponse: " + finalSum + "/" + total);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //将进度设置到TextView中
                contentTv.setText(finalSum + "/" + total);
            }
        });
    }
    fos.flush();
    fos.close();
    is.close();
}

2 . 显示文件上传进度

对于上传的进度的处理会比较麻烦,因为具体的上传过程是在RequestBody中由OkHttp帮我们处理上传,而且OkHttp并没有给我们提供上传进度的接口,这里我们的做法是自定义类继承RequestBody,然后重写其中的方法,将其中的上传进度通过接口回调暴露出来供我们使用。

public class CountingRequestBody extends RequestBody {
    //实际起作用的RequestBody
    private RequestBody delegate;
    //回调监听
    private Listener listener;

    private CountingSink countingSink;

    /**
     * 构造函数初始化成员变量
     * @param delegate
     * @param listener
     */
    public CountingRequestBody(RequestBody delegate, Listener listener){
        this.delegate = delegate;
        this.listener = listener;
    }
    @Override
    public MediaType contentType() {
        return delegate.contentType();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        countingSink = new CountingSink(sink);
        //将CountingSink转化为BufferedSink供writeTo()使用
        BufferedSink bufferedSink = Okio.buffer(countingSink);
        delegate.writeTo(bufferedSink);
        bufferedSink.flush();
    }

    protected final class CountingSink extends ForwardingSink{
        private long byteWritten;
        public CountingSink(Sink delegate) {
            super(delegate);
        }

        /**
         * 上传时调用该方法,在其中调用回调函数将上传进度暴露出去,该方法提供了缓冲区的自己大小
         * @param source
         * @param byteCount
         * @throws IOException
         */
        @Override
        public void write(Buffer source, long byteCount) throws IOException {
            super.write(source, byteCount);
            byteWritten += byteCount;
            listener.onRequestProgress(byteWritten, contentLength());
        }
    }

    /**
     * 返回文件总的字节大小
     * 如果文件大小获取失败则返回-1
     * @return
     */
    @Override
    public long contentLength(){
        try {
            return delegate.contentLength();
        } catch (IOException e) {
            return -1;
        }
    }

    /**
     * 回调监听接口
     */
    public static interface Listener{
        /**
         * 暴露出上传进度
         * @param byteWritted  已经上传的字节大小
         * @param contentLength 文件的总字节大小
         */
        void onRequestProgress(long byteWritted, long contentLength);
    }
}

上面的代码注释非常详细,这里不再解释,然后我们在写具体的请求时还需要做如下变化

File file = new File(Environment.getExternalStorageDirectory(), "1.png");
if (!file.exists()){
    Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
}else{
    RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file);
}

//使用我们自己封装的类
CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody2, new CountingRequestBody.Listener() {
    @Override
    public void onRequestProgress(long byteWritted, long contentLength) {
        //打印进度
        Log.d("pyh", "进度 :" + byteWritted + "/" + contentLength);
    }
});

上面其实就是在原有的RequestBody上包装了一层,最后在我们的使用中在post()方法中传入我们的CountingRequestBody对象即可。

九、后记

以上就是一些OkHttp常用的总结,希望可以帮助到需要的人

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

推荐阅读更多精彩内容

  • 参考okhttp官方wiki https://github.com/square/okhttp/wiki/Call...
    WangGavin阅读 7,141评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • Cookie保存 Cookie的保存也提供了快捷方式,当然也可以通过拦截器自己实现 Websocket okhtt...
    WangGavin阅读 1,943评论 0 1
  • 文/村草 女朋友最近天天加班,特忙。 晚上经常忙到9-10点,然后同事们一起出去吃饭,回家倒头就睡。 今天从家里吃...
    村草视角阅读 363评论 0 0