最初我们进行HTTP
请求时使用的是HttpURLConnection
或者HttpClient
,那么这两者都有什么优缺点呢?
HttpClient
是Apache
基金会的一个开源网络库,功能十分强大,API
数量众多,但正是由于庞大的API
数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以Android
团队在提升和优化HttpClient
方面的工作态度并不积极。官方在Android 2.3
以后就不建议用了,并且在Android 5.0
以后废弃了HttpClient
,在Android 6.0
更是删除了HttpClient
。
HttpURLConnection
是一种多用途、轻量极的HTTP
客户端,提供的API
比较简单,可以容易地去使用和扩展。不过在Android 2.2
版本之前,HttpURLConnection
一直存在着一些令人厌烦的bug
。比如说对一个可读的InputStream
调用close()
方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
private void disableConnectionReuseIfNecessary() {
// 这是一个2.2版本之前的bug
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
因此一般推荐是在2.2
之前使用HttpClient
,因为其bug
较少。在2.2
之后推荐使用HttpURLConnection
,因为API
简单、体积小、有压缩和缓存机制,并且Android
团队后续会继续优化HttpURLConnection
。
但是上面两个类库和OkHttp
比起来就显得有些不足了,因为OkHttp
不仅具有高效的请求效率,并且提供了很多开箱即用的网络疑难杂症解决方案。
从Android 4.4
开始google
已经开始将源码中的HttpURLConnection
替换为OkHttp
,而在Android 6.0
之后的SDK
中google
更是移除了对于HttpClient
的支持,而现在流行的Retrofit
同样是使用OkHttp
进行再次封装而来的。
OkHttp
是一个快速、高效的网络请求库,它的设计和实现的首要目标便是高效,有如下特性:
Android 2.3
及其以上版本;Java JDK 1.7
以上版本;dependencies {
compile 'com.squareup.okhttp3:okhttp:3.6.0'
compile 'com.squareup.okio:okio:1.11.0'
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
}
注:如果配置PersistentCookieJar依赖则同时也要在Project的Build.gradle中添加Maven库:
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
#okhttp
-dontwarn okhttp3.**
-keep class okhttp3.**{*;}
#okio
-dontwarn okio.**
-keep class okio.**{*;}
1.Application中初始化OkHttp
/**
* @Description 初始化OkHttp
*/
private void initOkHttp() {
File cache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(mContext));
Https.SSLParams sslParams = Https.getSslSocketFactory(null, null, null);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)//连接超时(单位:秒)
.writeTimeout(20, TimeUnit.SECONDS)//写入超时(单位:秒)
.readTimeout(20, TimeUnit.SECONDS)//读取超时(单位:秒)
.pingInterval(20, TimeUnit.SECONDS) //websocket轮训间隔(单位:秒)
.cache(new Cache(cache.getAbsoluteFile(), cacheSize))//设置缓存
.cookieJar(cookieJar)//Cookies持久化
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)//https配置
.build();
OkHttpUtils.initClient(okHttpClient);
}
2.同步数据请求
/**
* @param url 请求地址
* @param callback 请求回调
* @Description GET请求
*/
public static void getSync(String url, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doExecute(request, callback);
}
/**
* @param url 请求地址
* @param params 请求参数
* @param callback 请求回调
* @Description GET请求
*/
public static void getSync(String url, Map params, HttpCallback callback) {
if (params != null && !params.isEmpty()) {
url = OkHttpRequest.appendGetParams(url, params);
}
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doExecute(request, callback);
}
/**
* @param url 请求地址
* @param params 请求参数
* @param callback 请求回调
* @Description POST请求
*/
public static void postSync(String url, Map params, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
OkHttpRequest.doExecute(request, callback);
}
3.异步数据请求
使用enqueue
方法,将call
放入请求队列,然后OkHttp
会在线程池中进行网络访问;只需要在适当的时候(需要操作UI
的时候)发送一个消息给主线程的Handler
(取决于Looper
,使用 Looper.getMainLooper()
创建的Handler
就是主线程Handler
)就可以了。
/**
* @param url 请求地址
* @param callback 请求回调
* @Description GET请求
*/
public static void getAsyn(String url, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doEnqueue(request, callback);
}
/**
* @param url 请求地址
* @param params 请求参数
* @param callback 请求回调
* @Description GET请求
*/
public static void getAsyn(String url, Map params, HttpCallback callback) {
if (params != null && !params.isEmpty()) {
url = OkHttpRequest.appendGetParams(url, params);
}
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doEnqueue(request, callback);
}
/**
* @param url 请求地址
* @param params 请求参数
* @param callback 请求回调
* @Description POST请求
*/
public static void postAsyn(String url, Map params, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
OkHttpRequest.doEnqueue(request, callback);
}
/**
* @param url 请求地址
* @param json json数据格式
* @param callback 请求回调
* @Description POST提交JSON数据
*/
public static void postAync(String url, String json, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, null, json);
OkHttpRequest.doEnqueue(request, callback);
}
4.文件上传
/**
* @param url 请求地址
* @param file 上传文件
* @param callback 请求回调
* @Description 单文件上传
*/
public static void postAsynFile(String url, File file, HttpCallback callback) {
if (!file.exists()) {
ToastUtil.showText(UIUtils.getString(R.string.file_does_not_exist));
return;
}
Request request = OkHttpRequest.builderFileRequest(url, file, null, null, null, callback);
OkHttpRequest.doEnqueue(request, callback);
}
/**
* @param url 请求地址
* @param pic_key 上传图片关键字(约定pic_key如“upload”作为后台接受多张图片的key)
* @param files 上传文件集合
* @param params 请求参数
* @param callback 请求回调
* @Description 多文件上传
*/
public static void postAsynFiles(String url, String pic_key, List files, Map params, HttpCallback callback) {
Request request = OkHttpRequest.builderFileRequest(url, null, pic_key, files, params, callback);
OkHttpRequest.doEnqueue(request, callback);
}
5.文件下载
/**
* @param url 请求地址
* @param destFileDir 目标文件存储的文件夹路径,如:Environment.getExternalStorageDirectory().getAbsolutePath()
* @param destFileName 目标文件存储的文件名,如:gson-2.7.jar
* @param callback 请求回调
* @Description 文件下载
*/
public void downloadAsynFile(String url, String destFileDir, String destFileName, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doDownloadEnqueue(request, destFileDir, destFileName, callback);
}
/**
* @param url 请求地址
* @param destFileDir 目标文件存储的文件夹路径
* @param destFileName 目标文件存储的文件名
* @param params 请求参数
* @param callback 请求回调
* @Description 文件下载
*/
public void downloadAsynFile(String url, String destFileDir, String destFileName, Map params, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.POST, url, params, null);
OkHttpRequest.doDownloadEnqueue(request, destFileDir, destFileName, callback);
}
6.图片显示
/**
* @param url 请求地址
* @param callback 请求回调
* @Description 图片显示
*/
public static void displayAsynImage(String url, HttpCallback callback) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doDisplayEnqueue(request, callback);
}
7.流式提交
/**
* @param url 请求地址
* @param content 提交内容
* @param callback 请求回调
* @Description 使用流的方式提交POST请求
*/
public static void postAsynStream(String url, String content, HttpCallback callback) {
Request request = OkHttpRequest.builderStreamRequest(url, content);
OkHttpRequest.doEnqueue(request, callback);
}
8.Websocket
/**
* @param url 请求地址
* @Description WebSocket协议首先会发起http请求,握手成功后,转换协议保持长连接,类似心跳
*/
public static void websocket(String url) {
Request request = OkHttpRequest.builderRequest(HttpMethodType.GET, url, null, null);
OkHttpRequest.doNewWebSocket(request);
}
9.HTTP头部的设置和读取
HTTP
头部的数据结构是Map
类型,也就是说对于每个HTTP
头可能有多个值。但是大部分HTTP
头都只有一个值,只有少部分HTTP
头允许多个值。OkHttp
的处理方式是:
header(name,value)
来设置HTTP
头的唯一值(如果name
已经存在,将会移除该name
对应的value
,然后将新value
添加进来,即替换掉原来的value
值);addHeader(name,value)
来补充新值(即使当前已经存在值了,也只会添加新的value
值,并不会移除或替换原来的值);header(name)
读取唯一值或多个值的最后一个值;headers(name)
获取所有值;/**
* @param builder Request.Builder
* @param name 名称
* @param value 值
* @Description 添加单个头部信息
*/
public static Request.Builder appendHeader(Request.Builder builder, String name, String value) {
builder.header(name, value);
return builder;
}
/**
* @param builder Request.Builder
* @param headers 头部参数
* @Description 添加多个头部信息
*/
public static Request.Builder appendHeaders(Request.Builder builder, Map headers) {
Headers.Builder headerBuilder = new Headers.Builder();
if (headers == null || headers.isEmpty()) {
return builder;
}
for (String key : headers.keySet()) {
headerBuilder.add(key, headers.get(key));
}
builder.headers(headerBuilder.build());
return builder;
}
10.缓存控制
强制不缓存
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.noCache()
.build())
.url(url)
.build();
强制缓存
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.onlyIfCached()
.build())
.url(url)
.build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.code() != 504) {
// The resource was cached! Show it.
} else {
// The resource was not cached.
}
缓存策略由服务器指定
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxAge(0, TimeUnit.SECONDS)
.build())
.url(url)
.build();
允许使用旧的缓存
Request request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxStale(365, TimeUnit.DAYS)
.build())
.url(url)
.build();
11.Cookies缓存
OkHttpClient mOkHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() {
private final HashMap> cookieStore = new HashMap<>();
@Override
public void saveFromResponse(HttpUrl url, List cookies) {
//注:key是String类型且为url的host部分
cookieStore.put(url.host(), cookies);
}
@Override
public List loadForRequest(HttpUrl url) {
List cookies = cookieStore.get(url.host());
return cookies != null ? cookies : new ArrayList();
}
}).build();
12.关闭请求
/**
* @param tag 请求标签
* @Description 取消请求
*/
public static void cancelTag(Object tag) {
if (tag == null) {
return;
}
synchronized (mOkHttpClient.dispatcher().getClass()) {
for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
}
Android 4.0
之后要求网络请求必须在工作线程中运行,不允许在主线程中运行。因此如果使用OkHttp3
的同步方法,需要新起工作线程进行调用。Response
返回的字符串,可以通过response.body().string()
获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes()
;如果想获取到返回的InputStream
,则调用response.body().byteStream()
。enqueue
的回调是子线程,非主线程,所以是不能直接操作UI
界面的。string()
方法适用于获取小数据信息,如果返回的数据太大(超过1MB
),建议使用stream()
获取返回的数据,因为string()
方法会将整个文档加载到内存中。项目地址 ☞ 传送门