在过去,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开发者首选的网络通信库。
OkHttp是一个高效的HTTP客户端,它有以下默认特性:
当网络出现问题的时候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吧。
又到了喜闻乐见的集成环节,我们只需要修改module下的build.gradle,加入OkHttp的依赖,代码如下:
implementation("com.squareup.okhttp3:okhttp:4.6.0")
记得重新Sync一下,确保OkHttp集成到了你的项目中。作者在写下此篇博客时OkHttp3的稳定版本号以达到4.6.0
,如果读者在OkHttp的官网上发现了更新的版本,记得及时更新,做到与时俱进。
既然你集成了OkHttp,就不可不免地要进行与网络有关的操作。既然如此,就需要在清单文件下声明网络权限,即:
<uses-permission android:name="android.permission.INTERNET" />
另外:如果你的Android版本为Android P(即targetSdkVersion 27),在运行该项目时可能会报有关网络的错误,如图所示:
原因:在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同理若应用内使用WebView加载网页 则加载网页也需要是https请求。
解决方法:
三种方法亦可,这里主要介绍一下第三种解决方法,以拓宽解决思路
network_config(名字非固定)的xml
,代码如下:<network-security-config>
<base-config cleartextTrafficPermitted="true" />
network-security-config>
android:networkSecurityConfig
属性,代码如下: <application
...
android:networkSecurityConfig="@xml/network_security_config"
...
/>
接下来,我们直接开始布局文件activity_main.xml的编写。该布局很简单,仅有四个按钮,代码如下:
<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";
同步GET请求的步骤很简单,大抵分为:
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();
}
});
}
异步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());
}
});
}
});
}
同步POST请求的步骤类似于同步GET请求,大抵分为:
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();
}
});
}
异步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());
}
});
}
});
}
一般来说,虽说同步/异步调用各有优劣,在实际项目里,我们还是异步调用会相对用得多一些,搭配RxJava/RxAndroid以及EventBus,可以提升用户的体验。当然,如果是为了快速开发比较简单的小demo,同步调用也够用了。
AFL——Android框架学习