Android開發之OkHttp的使用
阿新 • • 發佈:2019-01-08
下面介紹OkHttp庫的用法,本篇會給出OkHttp的使用demo,demo中包含了常用的get請求、post請求、檔案的上傳和下載,demo執行的效果如下圖所示:
下面上程式碼一一說明:
要使用OkHttp,必須在專案中先匯入OkHttp,在app模組的build.gradle檔案中,加入下面的程式碼:
這樣就將OkHttp匯入到專案中了。dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.squareup.okhttp3:okhttp:3.2.0' }
(1)GET請求
最簡單的GET請求用法如下:
如果請求中要新增Header頭和引數,可以用下面的方式://簡單的Get請求,不帶引數 public void simpleGetClick(View view) { okHttpClient = new OkHttpClient(); Request request = new Request.Builder() .url("http://192.168.1.170:8088/okhttp/test_simple_get.php") .build(); okHttpClient.newCall(request).enqueue(callback); }
//帶引數的Get請求 public void addParamGetClick(View view) { okHttpClient = new OkHttpClient(); Request request = new Request.Builder() .addHeader("token", "asdlfjkasdljfaskdjfalsjkljalk") //請求頭中加入引數 .url("http://192.168.1.170:8088/okhttp/test_param_get.php?username=zhangsan&phone=13888888888") //攜帶引數 .build(); okHttpClient.newCall(request).enqueue(callback); }
需要注意的是,上面的程式碼中,callback是請求後的回撥介面,程式碼如下:
//請求後的回撥介面
private Callback callback = new Callback() {
@Override
public void onFailure(Call call, IOException e) {
setResult(e.getMessage(), false);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
setResult(response.body().string(), true);
}
};
這個回撥介面需要注意的是,onResponse和onFailure都不是在UI執行緒中執行的,所以如果我們要在onResponse或onFailure中進行UI相關的操作,需要在UI執行緒中進行。(2)POST請求
比較簡單的POST請求,用法如下:
//簡單的帶引數和Header的post請求
public void simplePostClick(View view) {
okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("username", "wangwu")
.add("password", "hello12345")
.add("gender", "female")
.build();
Request request = new Request.Builder()
.url("http://192.168.1.170:8088/okhttp/test_simple_post.php")
.post(requestBody)
.addHeader("token", "helloworldhelloworldhelloworld")
.build();
okHttpClient.newCall(request).enqueue(callback);
}
這裡我們需要先構造一個RequestBody,然後把需要攜帶的引數放到RequestBody中,然後使用這個RequestBody構建一個Request請求,最後將這個請求放入佇列中執行
如果我們的POST請求稍微複雜點,比如攜帶的引數既有文字型別的,又有檔案型別的,那麼可以用下面的方式來請求:
//帶文字引數和檔案引數的post請求
public void filePostClick(View view) {
RequestBody fileBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), tempFile);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "wangwu")
.addFormDataPart("password", "hello12345")
.addFormDataPart("gender", "female")
.addFormDataPart("file", "info.txt", fileBody)
.build();
Request request = new Request.Builder()
.url("http://192.168.1.170:8088/okhttp/test_param_post.php")
.post(requestBody)
.addHeader("token", "helloworldhelloworldhelloworld")
.build();
okHttpClient.newCall(request).enqueue(callback);
}
上面的程式碼中,tempFile是一個文字檔案,為了POST提交檔案和一些其他的引數,我們使用MultipartBody來構建一個請求體,需要注意的是,因為POST的內容含有檔案,所以我們必須為這個請求體設定setType(MultipartBody.FORM)
(3)檔案的上傳
檔案上傳並顯示進度,這個程式碼稍微有些複雜,下面直接上程式碼:
package com.test.testokhttp;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.ForwardingSink;
import okio.ForwardingSource;
import okio.Okio;
import okio.Sink;
import okio.Source;
public class UploadActivity extends AppCompatActivity {
private OkHttpClient okHttpClient;
private TextView resultTextView;
private ProgressBar progressBar;
private File tempFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);
setTitle("上傳檔案並顯示進度");
resultTextView = (TextView) findViewById(R.id.result_textview);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
progressBar.setMax(100);
okHttpClient = new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
}
//點選按鈕開始上傳檔案
public void startUploadClick(View view) {
tempFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test.pdf");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "test.pdf", RequestBody.create(MediaType.parse("application/pdf; charset=utf-8"), tempFile))
.build();
ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestBody, progressListener);
Request request = new Request.Builder()
.url("http://192.168.1.170:8088/okhttp/test_upload_file.php")
.post(progressRequestBody)
.build();
okHttpClient.newCall(request).enqueue(callback);
}
//通過實現進度回撥介面中的方法,來顯示進度
private ProgressListener progressListener = new ProgressListener() {
@Override
public void update(long bytesRead, long contentLength, boolean done) {
int progress = (int) (100.0 * bytesRead / contentLength);
progressBar.setProgress(progress);
}
};
//請求後的回撥方法
private Callback callback = new Callback() {
@Override
public void onFailure(Call call, IOException e) {
setResult(e.getMessage(), false);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
setResult(response.body().string(), true);
}
};
//顯示請求返回的結果
private void setResult(final String msg, final boolean success) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (success) {
Toast.makeText(UploadActivity.this, "請求成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(UploadActivity.this, "請求失敗", Toast.LENGTH_SHORT).show();
}
resultTextView.setText(msg);
}
});
}
//自定義的RequestBody,能夠顯示進度
public class ProgressRequestBody extends RequestBody {
//實際的待包裝請求體
private final RequestBody requestBody;
//進度回撥介面
private final ProgressListener progressListener;
//包裝完成的BufferedSink
private BufferedSink bufferedSink;
/**
* 建構函式,賦值
*
* @param requestBody 待包裝的請求體
* @param progressListener 回撥介面
*/
public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
/**
* 重寫呼叫實際的響應體的contentType
*
* @return MediaType
*/
@Override
public MediaType contentType() {
return requestBody.contentType();
}
/**
* 重寫呼叫實際的響應體的contentLength
*
* @return contentLength
* @throws IOException 異常
*/
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
/**
* 重寫進行寫入
*
* @param sink BufferedSink
* @throws IOException 異常
*/
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (bufferedSink == null) {
//包裝
bufferedSink = Okio.buffer(sink(sink));
}
//寫入
requestBody.writeTo(bufferedSink);
//必須呼叫flush,否則最後一部分資料可能不會被寫入
bufferedSink.flush();
}
/**
* 寫入,回撥進度介面
*
* @param sink Sink
* @return Sink
*/
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
//當前寫入位元組數
long bytesWritten = 0L;
//總位元組長度,避免多次呼叫contentLength()方法
long contentLength = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (contentLength == 0) {
//獲得contentLength的值,後續不再呼叫
contentLength = contentLength();
}
//增加當前寫入的位元組數
bytesWritten += byteCount;
//回撥
progressListener.update(bytesWritten, contentLength, bytesWritten == contentLength);
}
};
}
}
//進度回撥介面
interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
}
上面需要注意的是,上傳檔案需要實現自定義的RequestBody,也就是上面的ProgressRequestBody,在ProgressRequestBody中獲取上傳的進度。
(4)檔案的下載
下載和上傳類似,區別在於,需要我們實習自定義的ResponseBody而不是RequestBody了,下面上程式碼:
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class DownloadActivity extends AppCompatActivity {
private OkHttpClient okHttpClient;
private TextView resultTextView;
private ProgressBar progressBar;
private File tempFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
setTitle("下載檔案並顯示進度");
okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), progressListener))
.build();
}
})
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
resultTextView = (TextView) findViewById(R.id.result_textview);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
tempFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + System.currentTimeMillis() + ".pdf");
}
//下載檔案
public void startDownloadClick(View view) {
Request request = new Request.Builder()
.url("http://192.168.1.170:8088/okhttp/test.pdf")
.build();
okHttpClient.newCall(request).enqueue(callback);
}
private ProgressListener progressListener = new ProgressListener() {
@Override
public void update(long bytesRead, long contentLength, boolean done) {
int progress = (int) (100.0 * bytesRead / contentLength);
progressBar.setProgress(progress);
}
};
//請求後的回撥方法
private Callback callback = new Callback() {
@Override
public void onFailure(Call call, IOException e) {
setResult(e.getMessage(), false);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if(response != null) {
//下載完成,儲存資料到檔案
InputStream is = response.body().byteStream();
FileOutputStream fos = new FileOutputStream(tempFile);
byte[] buf = new byte[1024];
int hasRead = 0;
while((hasRead = is.read(buf)) > 0) {
fos.write(buf, 0, hasRead);
}
fos.close();
is.close();
setResult("下載成功", true);
}
}
};
//顯示請求返回的結果
private void setResult(final String msg, final boolean success) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (success) {
Toast.makeText(DownloadActivity.this, "請求成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(DownloadActivity.this, "請求失敗", Toast.LENGTH_SHORT).show();
}
resultTextView.setText(msg);
}
});
}
//自定義的ResponseBody,在其中處理進度
private static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override public MediaType contentType() {
return responseBody.contentType();
}
@Override public long contentLength() {
return responseBody.contentLength();
}
@Override public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
//進度回撥介面
interface ProgressListener {
void update(long bytesRead, long contentLength, boolean done);
}
}
如果我們在專案中直接使用上面的程式碼來進行http請求的話,勢必會比較麻煩,所以這裡我們需要封裝上面的程式碼,儘量在專案中能用簡短的程式碼完成網路請求。另外,一個專案中肯定會有很多個網路請求,我們沒必要在每次網路請求中都建立一個OkHttpClient物件,所有的請求公用一個OkHttpClient就可以了。
參考:http://blog.csdn.net/lmj623565791/article/details/47911083