每天学习一个Android中的常用框架——3.OkHttp

1.简介

在过去,Android上发送HTTP请求一般有两种方式:HttpURLConnection和HttpClient(Apache HttpClient)。不过由于HttpClient存在API数量过多、扩展困难等缺点,Android团队越来越不建议我们使用这种方式。终于在Android 6.0系统中,HttpClient的功能被完全移除了,标志着此功能被正式弃用。虽然官方推荐使用HttpURLConnection,但是在代码编写的过程中仍然会比较繁琐。此时,一个将要替代它们的网络层框架出现了:也就是Okhttp,该框架也是我在《Android 第一行代码》上学到的仅此于Litepal之后的第二个框架。今天重新学习了一下Okhttp,将学习历程记录到博客上,望对读者有帮助。
OkHttp是由鼎鼎大名的Square公司开发的,这个公司在开源事业上面贡献良多,除了 OkHttp 之外,还开发了像Picasso,Retrofit等著名的开源项目。OkHttp不仅在接口封装上面做得简单易用,就连在底层实现上也是自成一派,比起原生的HttpURLConnection,可以说是有过之而无不及,现在已经成了广大Android开发者首选的网络通信库。

2.特性

OkHttp是一个高效的HTTP客户端,它有以下默认特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
  • 连接池减少请求延时
  • 透明的GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

当网络出现问题的时候OkHttp依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP,OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接,当握手失败时会回退到TLS 1.0。

OkHttp 支持 Android 2.3 及以上版本Android平台, 以及 Java,JDK 1.7及以上.

OkHttp的使用是非常简单的. 它的请求/响应 API 使用构造器模式builders来设计,它支持阻塞式的同步请求和带回调的异步请求
为了更好地演示OkHttp,这里仅展示出同步/异步执行get/post请求的相关api,至于关于OkHttp的相关调用,建议参考OkHttp的官方文档:OkHttp官网,无需翻墙
关于使用post请求发送流/文件/表单等,这些功能则要配合另一个框架:OkIo,将在下一篇博客中专门展示出来。
话不多说,让我们马上开始使用OkHttp吧。

3.演示

3.1 集成

又到了喜闻乐见的集成环节,我们只需要修改module下的build.gradle,加入OkHttp的依赖,代码如下:

implementation("com.squareup.okhttp3:okhttp:4.6.0")

记得重新Sync一下,确保OkHttp集成到了你的项目中。作者在写下此篇博客时OkHttp3的稳定版本号以达到4.6.0,如果读者在OkHttp的官网上发现了更新的版本,记得及时更新,做到与时俱进。

3.2 配置

既然你集成了OkHttp,就不可不免地要进行与网络有关的操作。既然如此,就需要在清单文件下声明网络权限,即:

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

另外:如果你的Android版本为Android P(即targetSdkVersion 27),在运行该项目时可能会报有关网络的错误,如图所示:
在这里插入图片描述
原因:在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同理若应用内使用WebView加载网页 则加载网页也需要是https请求。
解决方法

  • APP整体网络请求改用https
  • 将targetSdkVersion 版本下调至27以下
  • 更改项目网络安全配置

三种方法亦可,这里主要介绍一下第三种解决方法,以拓宽解决思路

  1. 在res目录下新建xml文件夹 在xml文件夹内新建名为network_config(名字非固定)的xml,代码如下:
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. 然后,在AndroidManifest中增加android:networkSecurityConfig属性,代码如下:
 <application
    ...
     android:networkSecurityConfig="@xml/network_security_config"
    ...
        />
  1. 以上两个步骤就完成了网络安全配置,如果实在不行的话可以尝试另外两种方法或者百度

3.3 布局文件和URL封装

接下来,我们直接开始布局文件activity_main.xml的编写。该布局很简单,仅有四个按钮,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_get_syn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送get请求——同步"/>

    <Button
        android:id="@+id/btn_get_asy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送get请求——异步"/>

    <Button
        android:id="@+id/btn_post_syn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送post请求——异步"/>

    <Button
        android:id="@+id/btn_post_asy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送post请求——异步"/>

</LinearLayout>

之后,我们简单用字符串封装一下要请求的URL,即http://wwww.baidu.com。这里建议想要深入OkHttp的读者自己搭建一个Tomcat/Apache服务器,将资源文件上传到服务器上,然后访问一下本地路径,这样可以加深对于网络请求的理解。这里为了便于演示就直接网络请求百度,代码如下:

private static final String URL = "http://wwww.baidu.com";

3.4 同步GET请求

同步GET请求的步骤很简单,大抵分为:

  • 构造OkHttpClient对象;
  • 构造Request对象;
  • 通过前两步中的对象构建Call对象;
  • 通过call.execute()方法来提交同步请求。注意,由于是同步请求,所以该api要放在子线程中,避免阻塞主线程,造成ANR异常。另外,Android3.0 以后已经不允许在主线程访问网络。

代码如下:

private void getBySynchronized() {
        btn_get_syn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.构建Client对象
                OkHttpClient client = new OkHttpClient();
                // 2.采用建造者模式和链式调用构建Request对象
                final Request request = new Request.Builder()
                        .url(URL) // 请求URL
                        .get() // 默认就是get请求,可以不写
                        .build();
                // 3.通过1和2产生的Client和Request对象生成Call对象
                final Call call = client.newCall(request);
                // 4.同步发送get请求需要使用execute()方法,并且为了防止主线程阻塞需要放在子线程中自行
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 6.构建response对象
                            Response response = call.execute();
                            Log.d(TAG, "同步发送get请求成功!请求到的信息为:" + response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }

在这里插入图片描述

3.5 异步GET请求

异步GET请求的前面几个步骤和同步方式一样,只是最后一部是通过call.enqueue(Callback()) 来提交请求,代码如下:

private void getByAsynchronized() {
        btn_get_asy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.构建Client对象
                OkHttpClient client = new OkHttpClient();
                // 2.采用建造者模式和链式调用构建Request对象
                final Request request = new Request.Builder()
                        .url(URL) // 请求URL
                        .get() // 默认就是get请求,可以不写
                        .build();
                // 3.通过1和2产生的Client和Request对象生成Call对象
                Call call = client.newCall(request);
                // 4.调用Call对象的enqueue()方法,并且实现一个回调实现类
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
                        Log.d(TAG, "异步发送get请求失败!");
                        e.printStackTrace();
                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        Log.d(TAG, "异步发送get请求成功!请求到的信息为:" + response.body().string());
                    }
                });
            }
        });
    }

在这里插入图片描述

3.6 同步POST请求

同步POST请求的步骤类似于同步GET请求,大抵分为:

  • 构造OkHttpClient对象;
  • 构造FormBody对象(键值对)
  • 构造Request对象;
  • 通过前两步中的对象构建Call对象;
  • 通过call.execute()方法来提交同步请求。

代码如下:

private void postBySynchronized() {
        btn_post_syn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.构建Client对象
                OkHttpClient client = new OkHttpClient();
                // 2.采用建造者模式和链式调用构建键值对对象
                FormBody formBody = new FormBody.Builder()
                        .add("username", "admin")
                        .add("password", "123456")
                        .build();
                // 3.采用建造者模式和链式调用构建Request对象
                final Request request = new Request.Builder()
                        .url(URL) // 请求URL
                        .post(formBody) // 默认就是get请求,可以不写
                        .build();
                // 4.通过1和3产生的Client和Request对象生成Call对象
                final Call call = client.newCall(request);
                // 5.同步发送post请求需要使用execute()方法,并且为了防止主线程阻塞需要放在子线程中自行
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 6.构建response对象
                            Response response = call.execute();
                            Log.d(TAG, "同步发送post请求成功!请求到的信息为:" + response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }

在这里插入图片描述

3.7 异步POST请求

异步POST请求的前面几个步骤和同步方式一样,只是最后一部是通过call.enqueue(Callback()) 来提交请求,代码如下:

private void postByAsynchronized() {
        btn_post_asy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.构建Client对象
                OkHttpClient client = new OkHttpClient();
                // 2.采用建造者模式和链式调用构建键值对对象
                FormBody formBody = new FormBody.Builder()
                        .add("username", "admin")
                        .add("password", "123456")
                        .build();
                // 3.采用建造者模式和链式调用构建Request对象
                final Request request = new Request.Builder()
                        .url(URL) // 请求URL
                        .post(formBody) // 默认就是get请求,可以不写
                        .build();
                // 4.通过1和3产生的Client和Request对象生成Call对象
                Call call = client.newCall(request);
                // 5.调用Call对象的enqueue()方法,并且实现一个回调实现类
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
                        Log.d(TAG, "异步发送post请求失败!");
                        e.printStackTrace();
                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        Log.d(TAG, "异步发送post请求成功!请求到的信息为:" + response.body().string());
                    }
                });
            }
        });
    }

在这里插入图片描述

4 总结

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

一般来说,虽说同步/异步调用各有优劣,在实际项目里,我们还是异步调用会相对用得多一些,搭配RxJava/RxAndroid以及EventBus,可以提升用户的体验。当然,如果是为了快速开发比较简单的小demo,同步调用也够用了。

5.源码地址

AFL——Android框架学习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值