OkHttp簡介
OkHttp是一個http協(xié)議網(wǎng)絡(luò)請求的框架,OkHttp是一個高效的HTTP客戶端,適用于Android和Java應(yīng)用程序。從Android 4.4開始google已經(jīng)開始將源碼中的HttpURLConnection替換為OkHttp,而在Android 6.0之后的SDK中g(shù)oogle更是移除了對于HttpClient的支持,而現(xiàn)在流行的Retrofit同樣是使用OkHttp進行再次封裝而來的。
其它文章
Retrofit2.0+RxJava2.0封裝使用
Android開發(fā) 多語言、指紋登錄、手勢登錄
Android使用IconFont阿里矢量圖標
Android Studio 使用SVN 主干和分支合并代碼
本文章主要講的:
1.Okhttp3簡單使用
2.Okhttp3封裝使用
效果圖

項目地址:https://github.com/pengjunshan/UseOkhttp3
拿到代碼后移到自己項目中根據(jù)自己項目需求修改即可使用。
OkHttp3特性
- 支持http2,使得對同一個主機發(fā)出的所有請求都可以共享相同的socket套接字連接;
- 使用連接池來復(fù)用連接以減少延遲、提高效率;
- 支持Gzip壓縮響應(yīng)體,降低傳輸內(nèi)容的大??;
- 支持Http緩存,避免重復(fù)請求;
- 請求失敗時會自動重試主機中的其他IP地址自動重定向;
- 使用Okio來簡化數(shù)據(jù)的訪問與存儲,提高性能;
- 使用了創(chuàng)建者設(shè)計模式;
Http簡介
HTTP是一個屬于應(yīng)用層的面向?qū)ο蟮膮f(xié)議,由于其簡捷、快速的方式,適用于分布式超媒體信息系統(tǒng)。它于1990年提出,經(jīng)過幾年的使用與發(fā)展,得到不斷地完善和擴展。目前在WWW中使用的是HTTP/1.1版本。2.0版本目前也有在使用,只是使用不廣泛。HTTP協(xié)議工作于客戶端-服務(wù)端架構(gòu)為上,我們把Http協(xié)議中通信的兩方稱作Client和Server,Client端向Server端通過http協(xié)議發(fā)送一個Request請求,Server端收到Client端發(fā)來的Request請求后經(jīng)過一系列的處理返回Client一個Response,過程如下圖。

HTTP請求報文格式
HTTP請求報文主要由請求行、請求頭部、請求正文3部分組成。
-
請求行:由請求方法,URL,協(xié)議版本三部分構(gòu)成。
- URL是請求服務(wù)器地址。
- 協(xié)議版本有HTTP1.0、HTTP1.1,目前HTTP2.0也有使用。
- HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。
- HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
- 請求方法
請求頭部為請求報文添加了一些附加信息,由“名/值”對組成,每行一對,名和值之間使用冒號分隔。
Host ----接受請求的服務(wù)器地址,可以是IP:端口號,也可以是域名
User-Agent ----發(fā)送請求的應(yīng)用程序名稱
Connection ---- 指定與連接相關(guān)的屬性,如Connection:Keep-Alive
Accept-Charset ---- 通知服務(wù)端可以發(fā)送的編碼格式
Accept-Encoding ---- 通知服務(wù)端可以發(fā)送的數(shù)據(jù)壓縮格式
Accept-Language ---- 通知服務(wù)端可以發(fā)送的語言請求正文,可選部分,GET請求就沒有請求正文。
HTTP響應(yīng)報文格式
- HTTP響應(yīng)報文主要由狀態(tài)碼、響應(yīng)頭部、響應(yīng)正文3部分組成。
常用狀態(tài)碼
200:響應(yīng)成功
302:重定向跳轉(zhuǎn),跳轉(zhuǎn)地址通過響應(yīng)頭中的Location屬性指定
400:客戶端請求有語法錯誤,參數(shù)錯誤,不能被服務(wù)器識別
403:服務(wù)器接收到請求,但是拒絕提供服務(wù)(認證失?。?br> 404:請求資源不存在
500:服務(wù)器內(nèi)部錯誤響應(yīng)頭部,與請求頭部類似,為響應(yīng)報文添加了一些附加信息
Server - 服務(wù)器應(yīng)用程序軟件的名稱和版本
Content-Type - 響應(yīng)正文的類型(是圖片還是二進制字符串)
Content-Length - 響應(yīng)正文長度
Content-Charset - 響應(yīng)正文使用的編碼
Content-Encoding - 響應(yīng)正文使用的數(shù)據(jù)壓縮格式
Content-Language - 響應(yīng)正文使用的語言響應(yīng)正文,是請求響應(yīng)的最終結(jié)果,都在響應(yīng)體里??梢允亲址梢允亲址?。
配置
- maven方式:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.12.0</version>
</dependency>
- gradle方式:
compile 'com.squareup.okhttp3:okhttp:3.12.0'
-
自動下載okio資源
Libraries資源 - 聯(lián)網(wǎng)權(quán)限
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
1.簡單使用
HTTP工作中常用方式:
- get請求
- post請求
- 文件上傳
- 文件下載
- 圖文混合上傳
Android3.0 以后已經(jīng)不允許在主線程訪問網(wǎng)絡(luò)。需要注意的是這個onResponse回調(diào)方法不是在主線程回調(diào),可以使用runOnUIThread(new Runnable(){})更新UI,或者使用Handler在主線程中更新UI。
GET請求
1.第一步創(chuàng)建OkHttpClient對象
2.如果需要拼接參數(shù) (一般有參數(shù)的都會用Post請求,除非參數(shù)不重要)
3.第二步創(chuàng)建request對象
4.新建一個Call對象
5.同步請求網(wǎng)絡(luò)execute()
6.異步請求網(wǎng)絡(luò)enqueue(Callback)
/**
* 獲取輪播圖接口
* GET請求
*/
private void requestBannerApi(){
//1.第一步創(chuàng)建OkHttpClient對象
final OkHttpClient okHttpClient = new OkHttpClient();
String url ="http://www.wanandroid.com/banner/json";
//2. 如果需要拼接參數(shù) (一般有參數(shù)的都會用Post請求,除非參數(shù)不重要)
// Map<String, String> params = new HashMap<>();
// params.put("movieid", "246363");
// params.put("limit", "3");
// params.put("offset", "5");
// url = appendParams(url,params);
//3.第二步創(chuàng)建request
Request.Builder builder = new Request.Builder();
final Request request = builder.url(url)
.get()
.build();
//4.新建一個Call對象
final Call call = okHttpClient.newCall(request);
//5.同步請求網(wǎng)絡(luò)execute()
new Thread(new Runnable() {
@Override
public void run() {
try {
Response response = call.execute();
if(response.isSuccessful()){
Log.e("Benner請求成功同步=",response.body().string());
}else{
throw new IOException("Unexpected code " + response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//6.異步請求網(wǎng)絡(luò)enqueue(Callback)
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "Benner請求失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
Log.e("TAG", "Benner請求成功異步="+json);
}
});
}
/**
* 拼接參數(shù)
* @param url
* @param params
* @return
*/
private String appendParams(String url, Map<String, String> params) {
StringBuilder sb = new StringBuilder();
sb.append(url + "?");
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
sb.append(key).append("=").append(params.get(key)).append("&");
}
}
sb = sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
注意execute()同步方式會阻塞調(diào)用線程,所以在Android中應(yīng)放在子線程中執(zhí)行,否則有可能引起ANR異常。一般都會使用enqueue()異步請求服務(wù)器。
POST請求(鍵值對 key-value)
1.拿到okhttpClient對象
2.創(chuàng)建 FormBody 添加需要的鍵值對
3.構(gòu)造Request
4.創(chuàng)建一個Call對象
5.異步請求enqueue(Callback)
/**
* 登錄接口
* POST請求
* @param account 用戶名
* @param pwd 密碼
*/
private void requestLoginApi(String account, String pwd) {
// 1.拿到okhttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.創(chuàng)建 FormBody 添加需要的鍵值對
FormBody formBody = new FormBody.Builder()
.add("username",account)
.add("password",pwd)
.build();
// 3.構(gòu)造Request
Request.Builder builder = new Request.Builder();
Request request = builder.url("http://www.wanandroid.com/user/login")
.post(formBody)//鍵值對
.build();
//4.創(chuàng)建一個Call對象
Call call = okHttpClient.newCall(request);
//5.異步請求enqueue(Callback)
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "登錄失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
UserInfo userInfo = new Gson().fromJson(json,UserInfo.class);
if(userInfo!=null) {
if(userInfo.getErrorCode()!=0) {
Log.e("TAG", userInfo.getErrorMsg());
}else {
Log.e("TAG", "登錄成功="+json);
}
}
}
});
}
如果Post提交的數(shù)據(jù)是鍵值對就構(gòu)造一個FormBody對象,可以添加N個鍵值對。
POST請求(json字符串)
1.拿到okhttpClient對象
2.設(shè)置提交類型MediaType+json字符串
3.構(gòu)造Request
4.創(chuàng)建一個Call對象
5.異步請求enqueue(Callback)
private void requestLoginApi(String account, String pwd) {
// 1.拿到okhttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//需要提交的json字符串
String jsonStr = "hahaha";
//2.創(chuàng)建 RequestBody 設(shè)置提交類型MediaType+json字符串
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"),jsonStr);
// 3.構(gòu)造Request
Request.Builder builder = new Request.Builder();
Request request = builder.url("http://www.wanandroid.com/user/login")
.post(requestBody)//字符串
.build();
//4.創(chuàng)建一個Call對象
Call call = okHttpClient.newCall(request);
//5.異步請求enqueue(Callback)
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "登錄失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
UserInfo userInfo = new Gson().fromJson(json,UserInfo.class);
if(userInfo!=null) {
if(userInfo.getErrorCode()!=0) {
Log.e("TAG", userInfo.getErrorMsg());
}else {
Log.e("TAG", "登錄成功="+json);
}
}
}
});
}
如果提交json字符串需要構(gòu)造一個RequestBody對象,用它來攜帶我們要提交的json字符串數(shù)據(jù)。在構(gòu)造 RequestBody 需要指定MediaType,用于描述請求/響應(yīng) body 的內(nèi)容類型。

POST上傳(文件)
1.創(chuàng)建OkHttpClient對象
2.獲取文件地址,設(shè)置上傳文件類型,構(gòu)造RequestBody對象
3.構(gòu)造Requst對象
4.構(gòu)造Call對象進行 異步請求enqueue(Callback)
/**
* 提交txt文件
* POST請求
*/
private void requestPostFileTxt(){
//1.創(chuàng)建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.獲取文件地址,設(shè)置上傳文件類型,構(gòu)造RequestBody對象
File fileAdress = new File("/sdcard/wangshu.txt");
MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
RequestBody requestBody = RequestBody.create(mediaType,fileAdress);
//3.構(gòu)造Requst對象
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
//4.構(gòu)造Call對象進行 異步請求enqueue(Callback)
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "post"+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
Log.e("TAG", "Benner請求成功異步="+json);
}
});
}
上傳文件本身也是一個post請求,向服務(wù)器發(fā)送文件時需要備注文件類型Content-Type,可以用MultipartBody上傳多個文件。如果沒有添加charset也沒關(guān)系,RequestBody 中已經(jīng)幫我們寫好了。
charset
常用的Content-Type:
text/plain :純文本格式 .txt
text/xml : XML格式 .xml
image/gif :gif圖片格式 .gif
image/jpeg :jpg圖片格式 .jpg
image/png:png圖片格式 .png
audio/mp3 : 音頻mp3格式 .mp3
audio/rn-mpeg :音頻mpga格式 .mpga
video/mpeg4 : 視頻mp4格式 .mp4
video/x-mpg : 視頻mpa格式 .mpg
video/x-mpeg :視頻mpeg格式 .mpeg
video/mpg : 視頻mpg格式 .mpg
以application開頭的媒體格式類型:
application/xhtml+xml :XHTML格式
application/xml : XML數(shù)據(jù)格式
application/atom+xml :Atom XML聚合格式
application/json : JSON數(shù)據(jù)格式
application/pdf :pdf格式
application/msword : Word文檔格式
application/octet-stream : 二進制流數(shù)據(jù)(如常見的文件下載)
POST上傳圖片
1.創(chuàng)建OkHttpClient對象
2.設(shè)置文件類型
3.構(gòu)造RequestBody 指定文件類型和文件
4.創(chuàng)建Request對象
5.異步請求newCall(Callback)
/**
* 上傳圖片
* @param file
*/
private void requestPostImg( File file) {
//1.創(chuàng)建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.設(shè)置文件類型
MediaType mediaType = MediaType.parse("image/png");
if (file != null && file.exists()) {
//3.構(gòu)造RequestBody 指定文件類型和文件
RequestBody image = RequestBody.create(mediaType, file);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("img", file.getName(), image)
.build();
//4.創(chuàng)建Request對象
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("www.baidu.login")
.post(multipartBody)
.build();
//5.異步請求newCall(Callback)
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "圖片上傳失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body().string();
Log.e("TAG", "成功上傳圖片=" + result);
}
});
}
}
MultipartBody繼承RequestBody,具有自己的contentType+BufferedSink,是POST請求的最外層封裝,需要添加多個Part
Part對象組成:Headers+RequestBody。是MultipartBody的成員變量,需要寫入MultipartBody的BufferedSink中。
GET下載圖片
1.創(chuàng)建OkHttpClient對象
2.創(chuàng)建Request對象
3.異步請求newCall(Callback)
4.用文件流下載在本地文件夾下
/**
* 上傳圖片
* 沒有測試服務(wù)器地址
*/
public void PostImgRequet(View view) {
//1.創(chuàng)建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.設(shè)置文件類型
MediaType mediaType = MediaType.parse("image/png");
if (file != null && file.exists()) {
//3.構(gòu)造RequestBody 指定文件類型和文件
RequestBody image = RequestBody.create(mediaType, file);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("img", file.getName(), image)
.build();
//4.創(chuàng)建Request對象
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("www.baidu.login")
.post(requestBody)
.build();
//5.異步請求newCall(Callback)
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
mHandler.sendEmptyMessage(0);
Log.e("TAG", "圖片上傳失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body().string();
mHandler.sendEmptyMessage(1);
Log.e("TAG", "成功上傳圖片=" + result);
}
});
}
}
/**
* 下載圖片
*/
public void GetImgRequetSimpleness(View view) {
//1.創(chuàng)建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
String url = "http://p0.meituan.net/165.220/movie/7f32684e28253f39fe2002868a1f3c95373851.jpg";
//2.創(chuàng)建Request對象
Request request = new Request.Builder()
.url(url)
.build();
//3.異步請求newCall(Callback)
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "下載失敗");
mHandler.sendEmptyMessage(0);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(SimplenessActivity.this, "下載圖片成功", Toast.LENGTH_SHORT).show();
}
});
/**
* 用java文件輸入流下載圖片
*/
/* InputStream inputStream = response.body().byteStream();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File("/sdcard/okhttp.jpg"));
byte[] buffer = new byte[2048];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.flush();
} catch (IOException e) {
Log.i("TAG", "IOException");
e.printStackTrace();
}*/
//方法一,獲取byte數(shù)組,然后轉(zhuǎn)換成圖片
byte[] bytes = response.body().bytes();
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//方法二,可以獲取字節(jié)流,然后轉(zhuǎn)換成圖片
// InputStream inputStream = response.body().byteStream();
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
/**
* 保存圖片
*/
/* File file=new File("/sdcard/okhttp.jpg");
file.createNewFile();
//創(chuàng)建文件輸出流對象用來向文件中寫入數(shù)據(jù)
FileOutputStream out=new FileOutputStream(file);
//將bitmap存儲為jpg格式的圖片
bitmap.compress(Bitmap.CompressFormat.JPEG,100,out);
//刷新文件流
out.flush();
out.close();*/
if(bitmap!=null) {
Log.e("TAG", "圖片下載成功");
}
}
});
}
下載圖片可以用java文件輸入流下載圖片、BitmapFactory.decodeByteArray、 BitmapFactory.decodeStream,根據(jù)自己的需求來使用。
圖文混合上傳
1.創(chuàng)建OkHttpClient對象
2.構(gòu)建多部件builder
3.創(chuàng)建 Map 添加需要的鍵值對
4.獲取要上傳的圖片集合
5.獲取參數(shù)并放到請求體中
6.添加圖片集合到請求體中
7.構(gòu)造Request
8.異步請求enqueue(Callback)
/**
* 圖文混合上傳
* @param view
*/
public void PostImgKeyValueRequet(View view) {
//1.創(chuàng)建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.構(gòu)建多部件builder
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
//3.創(chuàng)建 Map 添加需要的鍵值對
Map<String, String> params = new HashMap<>();
params.put("username","15294792877");
params.put("password","15294792877pp");
//4.獲取要上傳的圖片集合
List<File> fileList = new ArrayList<>();
//5.獲取參數(shù)并放到請求體中
try {
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
//將請求參數(shù)逐一遍歷添加到我們的請求構(gòu)建類中
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
//6.添加圖片集合到請求體中
if (fileList != null) {
for (File f : fileList) {
bodyBuilder.addFormDataPart("files", f.getName(),
RequestBody.create(MediaType.parse("image/png"), f));
}
}
//7.構(gòu)造Request
Request request = new Request.Builder()
.url("https://www.wanandroid.com/user/login")
.post(bodyBuilder.build())
.build();
//8.異步請求enqueue(Callback)
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
mHandler.sendEmptyMessage(0);
Log.e("TAG", "失敗="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
Log.e("TAG", "成功="+json);
}
});
}
要構(gòu)建一個多部件MultipartBody.Builder,設(shè)置其類型為FORM("multipart/form-data")。然后把需要上傳的key-value鍵值對和圖片都通過addFormDataPart()方法添加進去。添加圖片時name要和后端接口指定name相同,還要添加RequestBody指定類型("image/png")。
封裝使用
如果不封裝使用起來還是很繁瑣的,比如:寫重復(fù)的代碼、增加類的代碼量、不易維護、回調(diào)函數(shù)不在主線程... 接下來我們就來封裝一個。(加泛型使用)
先看下封裝后請求代碼
一個get請求,一個post請求,和上面沒有封裝時相比是不是代碼很簡潔清晰。下面就簡單的講一下封裝的過程,想詳細的看封裝過程請下載demo查看,每個類都有注解。
/**
* GET請求
* 返回類型要Json字符串
*/
public void GetRequet(View view) {
HttpRequest.getBannerApi(null, new ResponseCallback<String>() {
@Override
public void onSuccess(String s) {
Toast.makeText(EncapsulationActivity.this, "請求成功" + s.toString(), Toast.LENGTH_SHORT)
.show();
}
@Override
public void onFailure(OkHttpException failuer) {
Toast.makeText(EncapsulationActivity.this, "請求失敗=" + failuer.getEmsg(), Toast.LENGTH_SHORT)
.show();
}
});
}
/**
* POST請求
* 返回類型我要實體類
*/
public void PostKeyValueRequet(View view) {
RequestParams params = new RequestParams();
params.put("username", "15294792877");
params.put("password", "15294792877pp");
HttpRequest.postLoginApi(params, new ResponseCallback<BaseBean<Info>>() {
@Override
public void onSuccess(BaseBean<Info> infoBaseBean) {
Toast.makeText(EncapsulationActivity.this, "成功=" + infoBaseBean.toString(),
Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(OkHttpException failuer) {
Toast.makeText(EncapsulationActivity.this, "失敗=" + failuer.getEmsg(), Toast.LENGTH_SHORT)
.show();
}
});
}
/**
* 下載圖片
* 可以用GET方式||POST方式,一般是用POST方式 除非你們公司不注重隱式,
* 本案例用的是GET方式,因為沒有找到免費的POST請求api。
*
* @param view
*/
public void GetImgRequet(View view) {
HttpRequest.getImgApi(null, String.valueOf(System.currentTimeMillis()) + ".png",
new ResponseByteCallback() {
@Override
public void onSuccess(File file) {
Toast.makeText(EncapsulationActivity.this, "圖片下載成功="+file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
Log.e("TAG", "圖片下載成功="+file.getAbsolutePath());
}
@Override
public void onFailure(String failureMsg) {
Toast.makeText(EncapsulationActivity.this, "圖片下載失敗="+failureMsg, Toast.LENGTH_SHORT).show();
Log.e("TAG", "圖片下載失敗="+failureMsg);
}
});
}
/**
* 圖文混合
* @param view
*/
public void PostImgKeyValueRequet(View view) {
RequestParams params = new RequestParams();
params.put("name", "aaaaaaa");
//添加圖片
List<File> fileList = new ArrayList<>();
// HttpRequest.postMultipartApi(params, fileList, new ResponseCallback() {
// @Override
// public void onSuccess(Object responseObj) {
//
// }
//
// @Override
// public void onFailure(OkHttpException failuer) {
//
// }
// });
}

OkHttpClient對象
初始化全局OkHttpClient對象,為我們的Client配置參數(shù),使用靜態(tài)語句塊來配置,只執(zhí)行一次,運行一開始就開辟了內(nèi)存,內(nèi)存放在全局。主要設(shè)置有緩存、超時時間、重定向、攔截器、Https支持,根據(jù)自己項目需求類配置就行了。
/**
* @author:PengJunShan.
* 時間:On 2019-05-05.
* 描述:OkHttpClient對象
*/
public class CommonOkHttpClient {
/**
* 超時時間
*/
private static final int TIME_OUT = 30;
private static OkHttpClient mOkHttpClient;
/**
* 為我們的Client配置參數(shù),使用靜態(tài)語句塊來配置
* 只執(zhí)行一次,運行一開始就開辟了內(nèi)存,內(nèi)存放在全局
*/
static {
//獲取緩存路徑
File cacheDir = MyApplication.context.getExternalCacheDir();
//設(shè)置緩存的大小
int cacheSize = 10 * 1024 *1024 ;
//創(chuàng)建我們Client對象的構(gòu)建者
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder
//為構(gòu)建者設(shè)置超時時間
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
////websocket輪訓(xùn)間隔(單位:秒)
.pingInterval(20, TimeUnit.SECONDS)
//設(shè)置緩存
.cache(new Cache(cacheDir.getAbsoluteFile(), cacheSize))
//允許重定向
.followRedirects(true)
//設(shè)置攔截器
.addInterceptor(new RequetInterceptor())
//添加https支持
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
})
.sslSocketFactory(HttpsUtils.initSSLSocketFactory(), HttpsUtils.initTrustManager());
mOkHttpClient = okHttpBuilder.build();
}
/**
* 發(fā)送具體的HTTP以及Https請求
*/
public static Call sendRequest(Request request, CommonJsonCallback commonCallback) {
Call call = mOkHttpClient.newCall(request);
call.enqueue(commonCallback);
return call;
}
/**
* GET請求
*/
public static Call get(Request request, ResposeDataHandle handle) {
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallback(handle));
return call;
}
/**
* POST請求
*/
public static Call post(Request request, ResposeDataHandle handle) {
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallback(handle));
return call;
}
/**
* POST請求圖片
*/
public static Call downLadImg(Request request, final String imgPath,
final ResponseByteCallback callback) {
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("TAG", "下載圖片失敗=" + e.getMessage());
new Handler().post(new Runnable() {
@Override
public void run() {
callback.onFailure(e.getMessage());
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("TAG", "下載圖片成功=" + response);
File file = null;
try {
InputStream is = response.body().byteStream();
int len = 0;
// 文件夾路徑
String pathUrl =
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
+ "/sgcc/";
File filepath = new File(pathUrl);
if (!filepath.exists()) {
filepath.mkdirs();// 創(chuàng)建文件夾
}
file = new File(pathUrl, imgPath);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[2048];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
is.close();
File finalFile = file;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onSuccess(finalFile);
}
});
} catch (final Exception e) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onFailure(e.getMessage());
}
});
}
}
});
return call;
}
}
公共入?yún)?CommonRequest)
我們每次請求都會創(chuàng)建Request對象,寫著重復(fù)的代碼,那我們就寫一個類專門處理入?yún)⑷缓蠓祷豏equest對象。
/**
* 創(chuàng)建: PengJunShan
* 描述: 公共入?yún)? */
public class CommonRequest {
/**
* 創(chuàng)建Get請求的Request
*/
public static Request createGetRequest(String url, RequestParams params) {
StringBuilder urlBuilder = new StringBuilder(url).append("?");
if (params != null) {
for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
urlBuilder
.append(entry.getKey())
.append("=")
.append(entry.getValue())
.append("&");
}
}
return new Request.Builder().url(urlBuilder.substring(0, urlBuilder.length() - 1))
.get().build();
}
/**
* 創(chuàng)建Post請求的Request
*
* @return 返回一個創(chuàng)建好的Request對象
*/
public static Request createPostRequest(String url, RequestParams params) {
FormBody.Builder mFromBodyBuilder = new FormBody.Builder();
//將請求參數(shù)逐一遍歷添加到我們的請求構(gòu)建類中
for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
mFromBodyBuilder.add(entry.getKey(), entry.getValue());
}
//通過請求構(gòu)建類的build方法獲取到真正的請求體對象
FormBody mFormBody = mFromBodyBuilder.build();
Request request = new Request.Builder()
.url(url)
.post(mFormBody)
.build();
return request;
}
/**
* 混合form和圖片
* @return 返回一個創(chuàng)建好的Request對象
*/
public static Request createMultipartRequest(String url, RequestParams params, List<File> files) {
//構(gòu)建多部件builder
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
//獲取參數(shù)并放到請求體中
try {
if (params != null) {
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> entry : params.urlParams.entrySet()) {
//將請求參數(shù)逐一遍歷添加到我們的請求構(gòu)建類中
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
jsonObject.put(entry.getKey(), entry.getValue());
}
Log.e("TAG", "入?yún)? " + jsonObject.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
//添加圖片集合放到請求體中
if (files != null) {
for (File f : files) {
bodyBuilder.addFormDataPart("files", f.getName(),
RequestBody.create(MediaType.parse("image/png"), f));
}
}
Request request = new Request.Builder()
.url(url)
.post(bodyBuilder.build())
.build();
return request;
}
}
請求模式(RequestMode)
實際工作中常用的請求模式有:get(無參)、post(key-value)、圖文混合、圖片下載。
/**
* 創(chuàng)建: PengJunShan
* 描述:請求模式
*/
public class RequestMode {
/**
* GET請求
* @param url URL請求地址
* @param params 入?yún)? * @param callback 回調(diào)接口
* @param clazz 需要解析的實體類
*/
public static void getRequest(String url, RequestParams params,
ResponseCallback callback, Class<?> clazz) {
CommonOkHttpClient.get(CommonRequest.createGetRequest(url, params),
new ResposeDataHandle(callback, clazz));
}
/**
* POST請求
* @param url URL請求地址
* @param params 入?yún)? * @param callback 回調(diào)接口
* @param clazz 需要解析的實體類
*/
public static void postRequest(String url, RequestParams params,
ResponseCallback callback, Class<?> clazz) {
CommonOkHttpClient.post(CommonRequest.createPostRequest(url, params),
new ResposeDataHandle(callback, clazz));
}
/**
* 下載圖片 Get方式
*/
public static void getLoadImg(String url,RequestParams params,String imgPath, ResponseByteCallback callback){
CommonOkHttpClient.downLadImg(CommonRequest.createGetRequest(url, params),imgPath,callback);
}
/**
* 下載圖片 Post方式
*/
public static void postLoadImg(String url,RequestParams params,String imgPath, ResponseByteCallback callback){
CommonOkHttpClient.downLadImg(CommonRequest.createPostRequest(url, params),imgPath,callback);
}
/**
* 表單和媒體 圖文混合
*/
public static void postMultipart(String url, RequestParams params,
List<File> files, ResponseCallback callback, Class<?> clazz) {
CommonOkHttpClient.post(CommonRequest.createMultipartRequest(url, params, files),
new ResposeDataHandle(callback, clazz));
}
}
HttpRequest
HttpRequest存放所有的請求接口,我們在activity中請求接口最先就是調(diào)用的這個類中的方法。
/**
* 作者:PengJunShan.
* 時間:On 2019-05-05.
* 描述:所有的請求接口
*/
public class HttpRequest {
/**
* @param params 入?yún)? * @param callback 回調(diào)接口
*/
public static void getBannerApi(RequestParams params, ResponseCallback<String> callback) {
RequestMode.getRequest("https://www.wanandroid.com/banner/json", params, callback);
}
/**
* @param params 入?yún)? * @param callback 回調(diào)接口
*/
public static void postLoginApi(RequestParams params, ResponseCallback<BaseBean<Info>> callback) {
RequestMode.postRequest("https://www.wanandroid.com/user/login", params, callback);
}
/**
* 下載圖片 Get方式
* @param params 入?yún)? * @param imgPath 存儲地址
* @param callback 回調(diào)接口
*/
public static void getImgApi(RequestParams params,String imgPath, ResponseByteCallback callback) {
RequestMode.getLoadImg("http://p0.meituan.net/165.220/movie/7f32684e28253f39fe2002868a1f3c95373851.jpg",params,imgPath,callback);
}
/**
* 下載圖片 Post方式
* @param params 入?yún)? * @param imgPath 存儲地址
* @param callback 回調(diào)接口
*/
public static void postImgApi(RequestParams params,String imgPath, ResponseByteCallback callback) {
RequestMode.postLoadImg("url地址",params,imgPath,callback);
}
/**
* 圖文混合上傳服務(wù)器
* @param params
* @param files
* @param callback
*/
public static void postMultipartApi(RequestParams params, List<File> files, ResponseCallback callback) {
RequestMode.postMultipart("url地址", params, files, callback, null);
}
}
回調(diào)ResponseCallback<T>
每次請求api都創(chuàng)建這個抽象類并實現(xiàn)其抽象方法,通過回傳把數(shù)據(jù)回調(diào)。
/**
* 創(chuàng)建: PengJunShan 描述:回調(diào) 使用泛型
*/
public abstract class ResponseCallback<T> {
Type mType;
public ResponseCallback() {
//Type是 Java 編程語言中所有類型的公共高級接口。它們包括原始類型、參數(shù)化類型、數(shù)組類型、類型變量和基本類型。
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
// throw new RuntimeException("請傳入實體類");
mType = null;
} else {
//ParameterizedType參數(shù)化類型,即泛型
ParameterizedType parameterized = (ParameterizedType) superclass;
//getActualTypeArguments獲取參數(shù)化類型的數(shù)組,泛型可能有多個
//將Java 中的Type實現(xiàn),轉(zhuǎn)化為自己內(nèi)部的數(shù)據(jù)實現(xiàn),得到gson解析需要的泛型
mType = $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
}
//請求成功回調(diào)事件處理
public abstract void onSuccess(T t);
//請求失敗回調(diào)事件處理
public abstract void onFailure(OkHttpException failuer);
}
處理JSON數(shù)據(jù)(CommonJsonCallback)
當我們請求到Json數(shù)據(jù)后不是直接返給最前端的接口,而是先進行解析處理。讓這個類繼承Callback接口,實現(xiàn)onFailure()、onResponse()方法。如果走了onFailure()失敗中,通過Exception類型判斷失敗原因。如果走了onResponse()中,首先是獲取errorMsg值來判斷是否成功然后解析數(shù)據(jù)。大家都知道CallBck的回調(diào)是在子線程中不能操作UI,那該怎么辦呢?我們在創(chuàng)建Handler時通過Looper.getMainLooper(),獲得主線程的Looper,將其綁定到此Handler對象上,這種情況下,Runnable對象是運行在主線程中可以更新UI操作。
/**
* 創(chuàng)建: PengJunShan
* 描述:專門處理JSON數(shù)據(jù)的回調(diào)響應(yīng)
*/
public class CommonJsonCallback<T> implements Callback {
/**
* errorCode是根據(jù)接口返回的標識 實際根據(jù)自己接口返回為準
*/
protected final String RESULT_CODE = "errorCode";
protected final int RESULT_CODE_VALUE = 0;
/**
* errorMsg字段提示信息,實際根據(jù)自己接口返回為準
*/
protected final String ERROR_MSG = "errorMsg";
protected final String NETWORK_MSG = "請求失敗";
protected final String JSON_MSG = "解析失敗";
/**
* 自定義異常類型
*/
protected final int NETWORK_ERROR = -1; //網(wǎng)絡(luò)失敗
protected final int JSON_ERROR = -2; //解析失敗
protected final int OTHER_ERROR = -3; //未知錯誤
protected final int TIMEOUT_ERROR = -4; //請求超時
private Handler mDeliveryHandler; //進行消息的轉(zhuǎn)發(fā)
private ResponseCallback<T> mListener;
public CommonJsonCallback(ResposeDataHandle handle) {
this.mListener = handle.mListener;
this.mDeliveryHandler = new Handler(Looper.getMainLooper());
}
/**
* 請求失敗的處理
*/
@Override
public void onFailure(@NonNull Call call, @NonNull final IOException e) {
Log.e("TAG", "請求失敗=" + e.getMessage());
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
if (!Utils.isConnected(MyApplication.context)) {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, "請檢查網(wǎng)絡(luò)"));
} else if (e instanceof SocketTimeoutException) {
//判斷超時異常
mListener.onFailure(new OkHttpException(TIMEOUT_ERROR, "請求超時"));
} else if (e instanceof ConnectException) {
//判斷超時異常
mListener.onFailure(new OkHttpException(OTHER_ERROR, "請求服務(wù)器失敗"));
} else {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, e.getMessage()));
}
}
});
}
/**
* 請求成功的處理 回調(diào)在主線程
*/
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
final String result = response.body().string();
mDeliveryHandler.post(new Runnable() {
@Override
public void run() {
handleResponse(result);
}
});
}
/**
* 處理Http成功的響應(yīng)
*/
private void handleResponse(Object responseObj) {
if (responseObj == null && responseObj.toString().trim().equals("")) {
mListener.onFailure(new OkHttpException(NETWORK_ERROR, NETWORK_MSG));
return;
}
try {
JSONObject result = new JSONObject(responseObj.toString());
if (result.has(RESULT_CODE)) {
//從JSON對象中取出我們的響應(yīng)碼,如果為0,則是正確的響應(yīng) (實際情況按你們接口文檔)
if (result.getInt(RESULT_CODE) == RESULT_CODE_VALUE) {
/**
* 判斷是否需要解析成實體類還是json字符串
* class com.google.gson.internal.$Gson$Types$ParameterizedTypeImpl
*/
Gson gson = new GsonBuilder().serializeNulls().create();
T obj = null;
if (!mListener.mType.getClass().equals("java.lang.Class")) {
obj = gson.fromJson((String) responseObj, mListener.mType);
} else {
obj = (T) responseObj;
}
if (obj != null) {
mListener.onSuccess(obj);
} else {
mListener.onFailure(new OkHttpException(JSON_ERROR, JSON_MSG));
}
} else { //將服務(wù)端返回的異?;卣{(diào)到應(yīng)用層去處理
mListener.onFailure(new OkHttpException(OTHER_ERROR, result.get(ERROR_MSG) + ""));
Log.e("TAG", "onResponse處理失敗");
}
}
} catch (Exception e) {
e.printStackTrace();
mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
Log.e("TAG", "onResponse處理失敗" + e.getMessage());
}
}
}
日志攔截器(RequetInterceptor)
攔截器的作用還是很大的,一般我們工作中請求頭部都會傳入token、用戶id標識認證參數(shù)。連接器中可以攔截到Request對象,然后添加頭部公共參數(shù)。通過獲取FormBody可以打印出入?yún)?shù),通過獲取Response可以打印出出參參數(shù)。不能直接使用response.body().string()的方式輸出日志,因為response.body().string()之后,response中的流會被關(guān)閉,我們需要創(chuàng)建出一個新的response給應(yīng)用層處理。
/**
* @author:PengJunShan.
* 時間:On 2019-05-05.
* 描述:日志攔截器
*/
public class RequetInterceptor implements Interceptor {
/**
* 這個chain里面包含了request和response,所以你要什么都可以從這里拿
*/
@Override
public Response intercept(Chain chain) throws IOException {
/**
* 可以添加公共頭部參數(shù)如token
*/
Request request = chain.request()
.newBuilder()
// .header("TOKEN", token)
// .header("ID", id)
.build();
/**
* 開始時間
*/
long startTime = System.currentTimeMillis();
Log.e("TAG","\n"+"requestUrl=" + request.url());
String method = request.method();
if ("POST".equals(method)) {
try {
JSONObject jsonObject = new JSONObject();
if (request.body() instanceof FormBody) {
FormBody body = (FormBody) request.body();
for (int i = 0; i < body.size(); i++) {
jsonObject.put(body.encodedName(i), body.encodedValue(i));
}
Log.e("TAG","入?yún)SON= " + jsonObject.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
Response response = chain.proceed(request);
/**
* 這里不能直接使用response.body().string()的方式輸出日志
* 因為response.body().string()之后,response中的流會被關(guān)閉,程序會報錯,我們需要創(chuàng)建出一個新的response給應(yīng)用層處理
*/
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.e("TAG","出參JSON=" + responseBody.string());
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
Log.e("TAG","----------" + "耗時:" + duration + "毫秒----------");
return response;
}
}
結(jié)束
網(wǎng)上有很多別人封裝好的庫,但是符不符合自己的項目使用就是另外一回事了。還不如自己花點時間封裝一個既簡單又符合自己項目使用的代碼,文章中貼出來的代碼是核心代碼并不是所有的代碼,下載代碼根據(jù)自己項目需求修改一下就可以用的。下篇我要寫一個以retrofit2+rxjava2進行網(wǎng)絡(luò)請求封裝。
項目地址:https://github.com/pengjunshan/UseOkhttp3
拿到代碼后移到自己項目中根據(jù)自己項目需求修改即可使用。


