android-async-http 基于httpClient封裝的網(wǎng)絡(luò)庫箫踩,android6.0后httpclient不是系統(tǒng)自帶的爱态,目前已不維護(hù),盡管Google在大部分安卓版本推薦使用HttpURLConnection境钟,但是這個(gè)類太難用了锦担,而OKhttp是一個(gè)相對(duì)成熟的網(wǎng)絡(luò)庫,在android4.4的源碼中HttpURLConnection已經(jīng)替換成OKHttp實(shí)現(xiàn)了慨削,很多大的第三方庫都支持它(fresco洞渔、retrofit)
api調(diào)用方便
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輔助類,創(chuàng)建請(qǐng)求對(duì)象缚态,再傳遞給OkHttpClient執(zhí)行磁椒,Response為返回的內(nèi)容,這樣get請(qǐng)求就完成了玫芦。
如果要是實(shí)現(xiàn)post請(qǐng)求浆熔,則創(chuàng)建一個(gè)RequestBody對(duì)象,賦值給Request桥帆,則完成了post請(qǐng)求
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請(qǐng)求
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 的執(zhí)行
調(diào)用execute方法是同步執(zhí)行医增,調(diào)用enqueue時(shí)異步執(zhí)行
響應(yīng)頭
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分塊請(qǐng)求
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();
能夠方便的設(shè)置和獲取響應(yīng)頭。
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"));
支持同步和異步請(qǐng)求
enqueue發(fā)起異步請(qǐng)求老虫,execute發(fā)起同步請(qǐng)求
請(qǐng)求攔截
Okhttp支持定義各種攔截器對(duì)整個(gè)網(wǎng)絡(luò)請(qǐng)求流程進(jìn)行攔截(監(jiān)視调窍、重寫、重試調(diào)用)
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();
可以定義多個(gè)攔截器张遭,按順序調(diào)用
緩存
Okhttp已經(jīng)內(nèi)置了緩存邓萨,使用DiskLruCache,使用緩存需要在創(chuàng)建OKhttpClient進(jìn)行配置
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();
如果服務(wù)器支持緩存缔恳,請(qǐng)求返回的Response會(huì)帶有Header:Cache-Control, max-age=xxx,Okhttp會(huì)自動(dòng)執(zhí)行緩存洁闰,如果服務(wù)器不支持歉甚,則要通過攔截Response,給其設(shè)置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協(xié)議之間扑眉,他的作用
1纸泄、認(rèn)證用戶和服務(wù)器赖钞,確保數(shù)據(jù)發(fā)送到正確的客戶機(jī)和服務(wù)器;(驗(yàn)證證書)
2聘裁、加密數(shù)據(jù)以防止數(shù)據(jù)中途被竊妊┯;(加密)
3衡便、維護(hù)數(shù)據(jù)的完整性献起,確保數(shù)據(jù)在傳輸過程中不被改變。(摘要算法)
Okhttp默認(rèn)是可以訪問通過CA認(rèn)證的https鏈接镣陕,如果是自簽名的證書谴餐,則應(yīng)用需要存放對(duì)應(yīng)的證書,并添加到okhttp設(shè)置中呆抑。
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通過將域名查詢請(qǐng)求放入http中的一種域名解析方式岂嗓,而不用系統(tǒng)自帶的libc庫去查詢運(yùn)營(yíng)商的DNS服務(wù)器,有更大的自由度鹊碍,https下不會(huì)存在任何問題摄闸,證書校驗(yàn)依然使用域名進(jìn)行校驗(yàn)。目前微信妹萨,qq郵箱、等業(yè)務(wù)均使用了HTTP DNS炫欺。
在OKhttp中乎完,提供DNS接口,實(shí)現(xiàn)DNS類品洛,配置到OKhttp中就行了
主要優(yōu)點(diǎn):
能夠準(zhǔn)確地將站點(diǎn)解析到離用戶最近的CDN站點(diǎn)树姨,方便進(jìn)行流量調(diào)度
解決部分運(yùn)營(yíng)商DNS無法解析國(guó)外站點(diǎn)的問題
TCP在一定程度可以防止UDP無校驗(yàn)導(dǎo)致的DNS欺詐(比如墻,運(yùn)營(yíng)商廣告桥状,404導(dǎo)航站)帽揪,當(dāng)然基于HTTP的話本質(zhì)還是不安全的。
static Dns HTTP_DNS = new Dns(){
@Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
//防御代碼
if (hostname == null) throw new UnknownHostException("hostname == null");
//dnspod提供的dns服務(wù)
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();
//避免服務(wù)器掛了卻無法查詢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查詢實(shí)現(xiàn)
.dns(HTTP_DNS)
.build();
}
return client;
}
支持連接池
Okhttp支持5個(gè)并發(fā)KeepAlive辅斟,默認(rèn)鏈路生命為5分鐘(鏈路空閑后转晰,保持存活的時(shí)間)。
socket連接每次需要3次握手士飒、釋放要2次或4次查邢,當(dāng)訪問復(fù)雜網(wǎng)絡(luò)時(shí),延時(shí)將成為非常重要的因素酵幕,使用連接池的好處就是優(yōu)化網(wǎng)絡(luò)性能扰藕,對(duì)于延遲降低與速度提升的有非常重要的作用。
KeepAlive缺點(diǎn)則是芳撒,在提高了單個(gè)客戶端性能的同時(shí)邓深,復(fù)用卻阻礙了其他客戶端的鏈路速度未桥。
相關(guān)資料
OkHttp使用教程
Okhttp使用指南與源碼分析
Okhttp-wiki 之 Interceptors 攔截器
OkHttp 3.x 源碼解析之Interceptor 攔截器
OkHttp攔截器的實(shí)現(xiàn)原理
使用okHttp支持https
Android使用OkHttp請(qǐng)求自簽名的https網(wǎng)站
OkHttp3應(yīng)用HTTP DNS的實(shí)現(xiàn)
Android OkHttp實(shí)現(xiàn)HttpDns的最佳實(shí)踐(非攔截器)
Android網(wǎng)絡(luò)編程(七)源碼解析OkHttp前篇請(qǐng)求網(wǎng)絡(luò)
OkHttp3源碼分析復(fù)用連接池