android-async-http 基于httpClient封装的网络库,android6.0后httpclient不是系统自带的,目前已不维护,尽管Google在大部分安卓版本推荐使用HttpURLConnection,但是这个类太难用了,而OKhttp是一个相对成熟的网络库,在android4.4的源码中HttpURLConnection已经替换成OKHttp实现了,很多大的第三方库都支持它(fresco、retrofit)
api调用方便
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
通过Request的Builder辅助类,创建请求对象,再传递给OkHttpClient执行,Response为返回的内容,这样get请求就完成了。
如果要是实现post请求,则创建一个RequestBody对象,赋值给Request,则完成了post请求
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
f (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
支持各种形式的post请求
post提交字符串
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
post提交流
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
post提交文件
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
post提交表单
RequestBody formBody = new FormEncodingBuilder()
.add("search", "Jurassic Park")
.build();
Request request = new Request.Builder()
.url("https://en.wikipedia.org/w/index.php")
.post(formBody)
.build();
OkHttpClient 的执行
调用execute方法是同步执行,调用enqueue时异步执行
响应头
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
post分块请求
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"title\""),
RequestBody.create(null, "Square Logo"))
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\""),
RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
.build();
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();
能够方便的设置和获取响应头。
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
支持同步和异步请求
enqueue发起异步请求,execute发起同步请求
请求拦截
Okhttp支持定义各种拦截器对整个网络请求流程进行拦截(监视、重写、重试调用)
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
可以定义多个拦截器,按顺序调用
缓存
Okhttp已经内置了缓存,使用DiskLruCache,使用缓存需要在创建OKhttpClient进行配置
int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheDirectory = new File("cache");
//出于安全性的考虑,在Android中我们推荐使用Context.getCacheDir()来作为缓存的存放路径
if (!cacheDirectory.exists()) {
cacheDirectory.mkdirs();
}
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient newClient = okHttpClient.newBuilder()
.Cache(cache)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
如果服务器支持缓存,请求返回的Response会带有Header:Cache-Control, max-age=xxx,Okhttp会自动执行缓存,如果服务器不支持,则要通过拦截Response,给其设置Cache-Control信息
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
Response response1 = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
//cache for 30 days
.header("Cache-Control", "max-age=" + 3600 * 24 * 30)
.build();
return response1;
}
}
OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient newClient = okHttpClient.newBuilder()
.addNetworkInterceptor(new CacheInterceptor())
.cache(cache)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
ssl支持
SSL位于TCP/IP和http协议之间,他的作用
1、认证用户和服务器,确保数据发送到正确的客户机和服务器;(验证证书)
2、加密数据以防止数据中途被窃取;(加密)
3、维护数据的完整性,确保数据在传输过程中不被改变。(摘要算法)
Okhttp默认是可以访问通过CA认证的https链接,如果是自签名的证书,则应用需要存放对应的证书,并添加到okhttp设置中。
mContext = context;
X509TrustManager trustManager;
SSLSocketFactory sslSocketFactory;
final InputStream inputStream;
try {
inputStream = mContext.getAssets().open("srca.cer"); // 得到证书的输入流
try {
trustManager = trustManagerForCertificates(inputStream);//以流的方式读入证书
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
sslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build();
} catch (IOException e) {
e.printStackTrace();
}
dns支持
HTTP DNS通过将域名查询请求放入http中的一种域名解析方式,而不用系统自带的libc库去查询运营商的DNS服务器,有更大的自由度,https下不会存在任何问题,证书校验依然使用域名进行校验。目前微信,qq邮箱、等业务均使用了HTTP DNS。
在OKhttp中,提供DNS接口,实现DNS类,配置到OKhttp中就行了
主要优点:
能够准确地将站点解析到离用户最近的CDN站点,方便进行流量调度
解决部分运营商DNS无法解析国外站点的问题
TCP在一定程度可以防止UDP无校验导致的DNS欺诈(比如墙,运营商广告,404导航站),当然基于HTTP的话本质还是不安全的。
static Dns HTTP_DNS = new Dns(){
@Override public List lookup(String hostname) throws UnknownHostException {
//防御代码
if (hostname == null) throw new UnknownHostException("hostname == null");
//dnspod提供的dns服务
HttpUrl httpUrl = new HttpUrl.Builder().scheme("http")
.host("119.29.29.29")
.addPathSegment("d")
.addQueryParameter("dn", hostname)
.build();
Request dnsRequest = new Request.Builder().url(httpUrl).get().build();
try {
String s = getHTTPDnsClient().newCall(dnsRequest).execute().body().string();
//避免服务器挂了却无法查询DNS
if (!s.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) {
return Dns.SYSTEM.lookup(hostname);
}
return Arrays.asList(InetAddress.getAllByName(s));
} catch (IOException e) {
return Dns.SYSTEM.lookup(hostname);
}
}
};
static public synchronized OkHttpClient getClient() {
if (client == null) {
final File cacheDir = GlobalContext.getInstance().getExternalCacheDir();
client = new OkHttpClient.Builder().addNetworkInterceptor(getLogger())
.cache(new Cache(new File(cacheDir, "okhttp"), 60 * 1024 * 1024))
.dispatcher(getDispatcher())
//配置DNS查询实现
.dns(HTTP_DNS)
.build();
}
return client;
}
支持连接池
Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间)。
socket连接每次需要3次握手、释放要2次或4次,当访问复杂网络时,延时将成为非常重要的因素,使用连接池的好处就是优化网络性能,对于延迟降低与速度提升的有非常重要的作用。
KeepAlive缺点则是,在提高了单个客户端性能的同时,复用却阻碍了其他客户端的链路速度。
相关资料
OkHttp使用教程
Okhttp使用指南与源码分析
Okhttp-wiki 之 Interceptors 拦截器
OkHttp 3.x 源码解析之Interceptor 拦截器
OkHttp拦截器的实现原理
使用okHttp支持https
Android使用OkHttp请求自签名的https网站
OkHttp3应用HTTP DNS的实现
Android OkHttp实现HttpDns的最佳实践(非拦截器)
Android网络编程(七)源码解析OkHttp前篇请求网络
OkHttp3源码分析复用连接池