一 OKHttp簡介
OKHttp是一個處理網絡請求的開源項目,Android 當前最火熱網絡框架,由移動支付Square公司貢獻,用於替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient)。
OKHttpGitHub地址
OKHttp優點
- 支持HTTP2/SPDY(SPDY是Google開發的基於TCP的傳輸層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。)
- socket自動選擇最好路線,並支持自動重連,擁有自動維護的socket連接池,減少握手次數,減少了請求延遲,共享Socket,減少對服務器的請求次數。
- 基於Headers的緩存策略減少重復的網絡請求。
- 擁有Interceptors輕松處理請求與響應(自動處理GZip壓縮)。
OKHttp的功能
- PUT,DELETE,POST,GET等請求
- 文件的上傳下載
- 加載圖片(內部會圖片大小自動壓縮)
- 支持請求回調,直接返回對象、對象集合
- 支持session的保持
二 OkHttp3使用
主要介紹 OkHttp3 的 Get 請求、 Post 請求、 上傳下載文件 、 上傳下載圖片等功能 。
添加OkHttp3的依賴
-
compile 'com.squareup.okhttp3:okhttp:3.7.0'
-
compile 'com.squareup.okio:okio:1.12.0'
添加網絡權限
<uses-permission android:name="android.permission.INTERNET"/>
添加請求頭
private Request.Builder addHeaders() {
Request.Builder builder = new Request.Builder()
//addHeader,可添加多個請求頭 header,唯一,會覆蓋
.addHeader("Connection", "keep-alive")
.addHeader("platform", "2")
.addHeader("phoneModel", Build.MODEL)
.addHeader("systemVersion", Build.VERSION.RELEASE)
.addHeader("appVersion", "3.2.0")
.header("sid", "eyJhZGRDaGFubmVsIjoiYXBwIiwiYWRkUHJvZHVjdCI6InFia3BsdXMiLCJhZGRUaW1lIjoxNTAzOTk1NDQxOTEzLCJyb2xlIjoiUk9MRV9VU0VSIiwidXBkYXRlVGltZSI6MTUwMzk5NTQ0MTkxMywidXNlcklkIjoxNjQxMTQ3fQ==.b0e5fd6266ab475919ee810a82028c0ddce3f5a0e1faf5b5e423fb2aaf05ffbf");
return builder;
}
1.異步GET請求
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.創建Request對象,設置一個url地址(百度地址),設置請求方式。
Request request = new Request.Builder().url("http://www.baidu.com").method("GET",null).build();
//3.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//4.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
//請求失敗執行的方法
@Override
public void onFailure(Call call, IOException e) {
}
//請求成功執行的方法
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
上面就是發送一個異步GET請求的4個步驟:
- 創建OkHttpClient對象
- 通過Builder模式創建Request對象,參數必須有個url參數,可以通過Request.Builder設置更多的參數比如:header、method等
- 通過request的對象去構造得到一個Call對象,Call對象有execute()和cancel()等方法。
- 以異步的方式去執行請求,調用的是call.enqueue,將call加入調度隊列,任務執行完成會在Callback中得到結果。
注意事項:
- 異步調用的回調函數是在子線程,我們不能在子線程更新UI,需要借助於 runOnUiThread() 方法或者 Handler 來處理。
- onResponse回調有一個參數是response,如果我們想獲得返回的是字符串,可以通過response.body().string()獲取;如果希望獲得返回的二進制字節數組,則調用response.body().bytes();如果你想拿到返回的inputStream,則調response.body().byteStream(),有inputStream我們就可以通過IO的方式寫文件(后面會有例子)。
2.同步GET請求
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.創建Request對象,設置一個url地址(百度地址),設置請求方式。
Request request = new Request.Builder().url("http://www.baidu.com").method("GET",null).build();
//3.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//4.同步調用會阻塞主線程,這邊在子線程進行
new Thread(new Runnable() {
@Override
public void run() {
try {
//同步調用,返回Response,會拋出IO異常
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
同步GET請求和異步GET請求基本一樣,不同地方是同步請求調用Call的execute()方法,而異步請求調用call.enqueue()方法(具體2個方法的不同點我下一遍具體源碼詳解再說)。
3.POST請求提交鍵值對
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.通過new FormBody()調用build方法,創建一個RequestBody,可以用add添加鍵值對
RequestBody requestBody = new FormBody.Builder().add("name","zhangqilu").add("age","25").build();
//3.創建Request對象,設置URL地址,將RequestBody作為post方法的參數傳入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//5.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
上面就是一個異步POST請求提交鍵值對的5個步驟:
- 創建OkHttpClient對象。
- 通過new FormBody()調用build方法,創建一個RequestBody,可以用add添加鍵值對 ,FormBody 是 RequestBody 的子類。
- 創建Request對象,設置URL地址,將RequestBody作為post方法的參數傳入。
- 創建一個call對象,參數就是Request請求對象。
- 請求加入調度,重寫回調方法。
通過對比我們發現異步的POST請求和GET請求步驟很相似。
4.異步POST請求提交字符串
POST請求提交字符串和POST請求提交鍵值對非常相似,不同地方主要是RequestBody,下面我們來具體看一下。
在有些情況下客戶端需要向服務端傳送字符串,我們該怎么做?
我們需要用到另一種方式來構造一個 RequestBody 如下所示:
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");//"類型,字節碼"
//字符串
String value = "{username:admin;password:admin}";
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.通過RequestBody.create 創建requestBody對象
RequestBody requestBody =RequestBody.create(mediaType, value);
//3.創建Request對象,設置URL地址,將RequestBody作為post方法的參數傳入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//5.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
5.異步POST請求上傳文件
我們這里舉一個上傳圖片的例子,也可以是其他文件如,TXT文檔等,不同地方主要是RequestBody,首先我們要添加存儲卡讀寫權限,在 AndroidManifest.xml 文件中添加如下代碼:
-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
下面我們具體看一下上傳文件代碼。
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//上傳的圖片
File file = new File(Environment.getExternalStorageDirectory(), "zhuangqilu.png");
//2.通過RequestBody.create 創建requestBody對象,application/octet-stream 表示文件是任意二進制數據流
RequestBody requestBody =RequestBody.create(MediaType.parse("application/octet-stream"), file);
//3.創建Request對象,設置URL地址,將RequestBody作為post方法的參數傳入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//5.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
6.異步GET請求下載文件
下載文件也是我們經常用到的功能,我們就舉個下載圖片的例子吧
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.創建Request對象,設置一個url地址(百度地址),設置請求方式。
Request request = new Request.Builder().url("https://www.baidu.com/img/bd_logo1.png").get().build();
//3.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//4.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "onFailure: "+call.toString() );
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//拿到字節流
InputStream is = response.body().byteStream();
int len = 0;
//設置下載圖片存儲路徑和名稱
File file = new File(Environment.getExternalStorageDirectory(),"baidu.png");
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[128];
while((len = is.read(buf))!= -1){
fos.write(buf,0,len);
Log.e(TAG, "onResponse: "+len );
}
fos.flush();
fos.close();
is.close();
}
});
Get請求下載文件還是比較簡單,設置下載地址,在回調函數中拿到了圖片的字節流,然后保存為了本地的一張圖片。
從網絡下載一張圖片並直接設置到ImageView中。
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = response.body().byteStream();
//使用 BitmapFactory 的 decodeStream 將圖片的輸入流直接轉換為 Bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
//在主線程中操作UI
runOnUiThread(new Runnable() {
@Override
public void run() {
//然后將Bitmap設置到 ImageView 中
imageView.setImageBitmap(bitmap);
}
});
is.close();
主要注釋已在代碼中了。
7.異步POST請求上傳Multipart文件
我們在有些情況下既要上傳文件還要上傳其他類型字段。比如在個人中心我們可以修改名字,年齡,修改圖像,這其實就是一個表單。這里我們用到MuiltipartBody ,它 是RequestBody 的一個子類,我們提交表單就是利用這個類來構建一個 RequestBody,我們來看一下具體代碼。
//1.創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//上傳的圖片
File file = new File(Environment.getExternalStorageDirectory(), "zhuangqilu.png");
//2.通過new MultipartBody build() 創建requestBody對象,
RequestBody requestBody = new MultipartBody.Builder()
//設置類型是表單
.setType(MultipartBody.FORM)
//添加數據
.addFormDataPart("username","zhangqilu")
.addFormDataPart("age","25")
.addFormDataPart("image","zhangqilu.png",
RequestBody.create(MediaType.parse("image/png"),file))
.build();
//3.創建Request對象,設置URL地址,將RequestBody作為post方法的參數傳入
Request request = new Request.Builder().url("url").post(requestBody).build();
//4.創建一個call對象,參數就是Request請求對象
Call call = okHttpClient.newCall(request);
//5.請求加入調度,重寫回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
Post 表單
public void postForm(View view) {
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "葉應是葉")
.addFormDataPart("password", "葉應是葉")
.build();
final Request request = new Request.Builder()
.url("http://www.jianshu.com/")
.post(requestBody)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtil.showToast(PostFormActivity.this, "Post Form 失敗");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseStr = response.body().string();
ToastUtil.showToast(PostFormActivity.this, "Code:" + String.valueOf(response.code()));
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_result.setText(responseStr);
}
});
}
});
}
Post 流
public void postStreaming(View view) {
final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
File file = new File("README.md");
final FileInputStream fileInputStream1=new FileInputStream(file);
RequestBody requestBody1=new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
OutputStream outputStream=sink.outputStream();
int length;
byte[] buffer = new byte[1024];
while ((length = fileInputStream1.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
};
RequestBody requestBody2=new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
int length;
byte[] buffer = new byte[1024];
while ((length = fileInputStream1.read(buffer)) != -1) {
sink.write(buffer, 0, length);
}
}
};
Request request = new Request.Builder()
.url(url)
.post(requestBody1)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
ToastUtil.showToast(PostStreamingActivity.this, "Post Streaming 失敗");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String responseStr = response.body().string();
ToastUtil.showToast(PostStreamingActivity.this, "Code:" + String.valueOf(response.code()));
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_result.setText(responseStr);
}
});
}
});
}
解析Json
這里來通過Gson將response的內容解析為Java Bean
首先需要先去將Gson.jar文件導入工程
這里來通過OkHttp訪問接口“http://news-at.zhihu.com/api/4/themes”,獲取Json數據然后將之解析為JavaBean實體
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()){
User user = new Gson().fromJson(response.body().charStream(), User.class);
}
設置超時時間和緩存
和OkHttp2.x有區別的是不能通過OkHttpClient直接設置超時時間和緩存了,而是通過OkHttpClient.Builder來設置,通過builder配置好OkHttpClient后用builder.build()來返回OkHttpClient,所以我們通常不會調用new OkHttpClient()來得到OkHttpClient,而是通過builder.build():
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
OkHttpClient mOkHttpClient=builder.build();
關於取消請求和封裝
取消請求仍舊可以調用call.cancel(),這個沒有變化,不明白的可以查看上一篇文章Android網絡編程(五)OkHttp2.x用法全解析,這里就不贅述了,封裝上一篇也講過仍舊推薦OkHttpFinal,它目前是基於OkHttp3來進行封裝的。
call.cancel();//取消請求,不能取消已經准備完成的請求 okHttpClient.dispatcher().cancelAll();//取消所有請求
有時候網絡條件不好的情況下,用戶會主動關閉頁面,這時候需要取消正在請求的http request, OkHttp提供了cancel方法,但是實際在使用過程中發現,如果調用cancel()方法,會回調到CallBack里面的 onFailure方法中,
/** * Called when the request could not be executed due to cancellation, a connectivity problem or * timeout. Because networks can fail during an exchange, it is possible that the remote server * accepted the request before the failure. */ void onFailure(Call call, IOException e);
可以看到注釋,當取消一個請求,網絡連接錯誤,或者超時都會回調到這個方法中來,但是我想對取消請求做一下單獨處理,這個時候就需要區分不同的失敗類型了
解決思路
測試發現不同的失敗類型返回的IOException e 不一樣,所以可以通過e.toString 中的關鍵字來區分不同的錯誤類型
自己主動取消的錯誤的 java.net.SocketException: Socket closed 超時的錯誤是 java.net.SocketTimeoutException 網絡出錯的錯誤是java.net.ConnectException: Failed to connect to xxxxx
代碼
直貼了部分代碼
call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if(e.toString().contains("closed")) { //如果是主動取消的情況下 }else{ //其他情況下 }
攔截器
添加Interceptor
// 配置一些信息進入OkHttpClient
mOkHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.readTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.writeTimeout(REQUEST_TIME, TimeUnit.SECONDS)
.addInterceptor(new LoggerInterceptor())
.build();
只要利用addInterceptor方法就可以添加攔截器,而自定義的攔截器只需要實現 Interceptor 接口就行了,可以使用攔截器方便的打印網絡請求時,需要查看的日志。如下所示:
public class LoggerInterceptor implements Interceptor {
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
// 攔截請求,獲取到該次請求的request
Request request = chain.request();
// 執行本次網絡請求操作,返回response信息
Response response = chain.proceed(request);
if (Configuration.DEBUG) {
for (String key : request.headers().toMultimap().keySet()) {
LogUtil.e("zp_test", "header: {" + key + " : " + request.headers().toMultimap().get(key) + "}");
}
LogUtil.e("zp_test", "url: " + request.url().uri().toString());
ResponseBody responseBody = response.body();
if (HttpHeaders.hasBody(response) && responseBody != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseBody.byteStream(), "utf-8"));
String result;
while ((result = bufferedReader.readLine()) != null) {
LogUtil.e("zp_test", "response: " + result);
}
// 測試代碼
responseBody.string();
}
}
// 注意,這樣寫,等於重新創建Request,獲取新的Response,避免在執行以上代碼時,
// 調用了responseBody.string()而不能在返回體中再次調用。
return response.newBuilder().build();
}
}
注意事項
- 如果提交的是表單,一定要設置表單類型,
setType(MultipartBody.FORM) - 提交文件 addFormDataPart() 方法的第一個參數就是類似於鍵值對的鍵,是供服務端使用的,第二個參數是文件的本地的名字,第三個參數是 RequestBody,里面包含了我們要上傳的文件的路徑以及 MidiaType。
from:https://www.cnblogs.com/chenxibobo/p/9585760.html

