原文:http://www.open-open.com/lib/view/open1482115993812.html
一、OkHttp介紹
OkHttp是一個優秀的網絡請求框架,可能一說到網絡請求框架,可能很多人都會想到 volley , volley 是一個Google提供的網絡請求框架,我的博客里也有一篇專門介紹 volley 的博客,博客地址在此 Android網絡請求 ------ Volley的使用 那么既然Google提供了網絡請求的框架,我們為什么還要使用 OkHttp 呢,原來是 volley 是要依靠 HttpCient 的,而Google在 Android6.0 的SDK中去掉了 HttpCient ,所以 OkHttp 就開始越來越受大家的歡迎.
今天我們主要介紹 OkHttp 的 Get 請求、 Post 請求、 上傳下載文件 、 上傳下載圖片等功能 。
當然在開始之前,我們還要先在項目中添加 OkHttp 的依賴庫,至於怎么在AndroidStudio中給項目添加OkHTTP依賴,這里將不再贅述。另外,OkHttp中使用了建造者模式
添加OkHttp的依賴
在對應的Module的gradle中添加
compile 'com.squareup.okhttp3:okhttp:3.5.0' 然后同步一下項目即可
二、OkHttp進行Get請求
使用OkHttp進行 Get 請求只需要四步即可完成。
1 . 拿到OkHttpClient對象
OkHttpClient client = new OkHttpClient();
2 . 構造Request對象
Request request = new Request.Builder() .get() .url("https:www.baidu.com") .build();
這里我們采用建造者模式和鏈式調用指明是進行Get請求,並傳入Get請求的地址
如果我們需要在get請求時傳遞參數,我們可以以下面的方式將參數拼接在url之后
https:www.baidu.com?username=admin&password=admin
3 . 將Request封裝為Call
Call call = client.newCall(request);
4 . 根據需要調用同步或者異步請求方法
//同步調用,返回Response,會拋出IO異常 Response response = call.execute(); //異步調用,並設置回調函數 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Toast.makeText(OkHttpActivity.this, "get failed", Toast.LENGTH_SHORT).show(); } @Override public void onResponse(Call call, final Response response) throws IOException { final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { contentTv.setText(res); } }); } });
第四步有一些需要注意的地方
- 同步調用會阻塞主線程,一般不適用
- 異步調用的回調函數是在子線程,我們不能在子線程更新UI,需要借助於 runOnUiThread() 方法或者 Handler 來處理
是不是以為上面就結束了,對的,OkHttp的Get請求步驟就這么4步,但是當你試圖打開應用加載數據,可是發現並沒有加載到數據,這是一個簡單但是我們常犯的錯誤.
在AndroidManifest.xml中加入聯網權限
<uses-permission android:name="android.permission.INTERNET" />
三、OkHttp進行Post請求提交鍵值對
使用OkHttp進行 Post 請求和進行 Get 請求很類似,只需要五步即可完成。
1 . 拿到OkHttpClient對象
OkHttpClient client = new OkHttpClient();
2 . 構建FormBody,傳入參數
FormBody formBody = new FormBody.Builder() .add("username", "admin") .add("password", "admin") .build();
3 . 構建Request,將FormBody作為Post方法的參數傳入
final Request request = new Request.Builder() .url("http://www.jianshu.com/") .post(formBody) .build();
4 . 將Request封裝為Call
Call call = client.newCall(request);
5 . 調用請求,重寫回調方法
call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Toast.makeText(OkHttpActivity.this, "Post Failed", Toast.LENGTH_SHORT).show(); } @Override public void onResponse(Call call, Response response) throws IOException { final String res = response.body().string(); runOnUiThread(new Runnable() { @Override public void run() { contentTv.setText(res); } }); } });
經過上面的步驟一個post請求就完成了,當然上面的 url 參數和需要傳入的參數大家就要根據實際情況來傳入,你會發現get和post請求的步驟非常像。
四、OkHttp進行Post請求提交字符串
如果你已經掌握了上面的兩種基本的步驟,那下面的內容就比較簡單了
上面我們的post的參數是通過構造一個 FormBody 通過鍵值對的方式來添加進去的,其實post方法需要傳入的是一個 RequestBody 對象, FormBody 是 RequestBody 的子類,但有時候我們常常會遇到要傳入一個字符串的需求,比如客戶端給服務器發送一個json字符串,那這種時候就需要用到另一種方式來構造一個 RequestBody 如下所示:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "{username:admin;password:admin}");
上面的MediaType我們指定傳輸的是純文本,而且編碼方式是utf-8,通過上面的方式我們就可以向服務端發送json字符串啦。
注:關於MidiaType的類型你可以百度搜索 mime type 查看相關的內容,這里不再贅述
五、OkHttp進行Post請求上傳文件
理解了上面一個,下面這個就更簡單了,這里我們以上傳一張圖片為例,當然你也可以上傳一個txt什么的文件,都是可以的
其實最主要的還是構架我們自己的 RequestBody ,如下圖構建
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); }else{ RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file); }
這里我們將手機SD卡根目錄下的 1.png 圖片進行上傳。代碼中的 application/octet-stream 表示我們的文件是 任意二進制數據流 ,當然你也可以換成更具體的 image/png
注:最后記得最重要的一點:添加存儲卡寫權限,在 AndroidManifest.xml 文件中添加如下代碼:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
六、OkHttp進行Post請求提交表單
我們在網頁上經常會遇到用戶注冊的情況,需要你輸入用戶名,密碼,還有上傳頭像,這其實就是一個表單,那么接下來我們看看如何利用OkHttp來進行表單提交。經過上面的學習,大家肯定也懂,主要的區別就在於構造不同的 RequestBody 傳遞給 post 方法即可.
由於我們使用的是OkHttp3所以我們還需要再導入一個包 okio.jar 才能繼續下面的內容,我們需要在模塊的Gradle文件中添加如下代碼,然后同步一下項目即可
compile 'com.squareup.okio:okio:1.11.0'
這里我們會用到一個 MuiltipartBody ,這是 RequestBody 的一個子類,我們提交表單就是利用這個類來構建一個 RequestBody ,下面的代碼我們會發送一個包含用戶民、密碼、頭像的表單到服務端
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); return; } RequestBody muiltipartBody = new MultipartBody.Builder() //一定要設置這句 .setType(MultipartBody.FORM) .addFormDataPart("username", "admin")// .addFormDataPart("password", "admin")// .addFormDataPart("myfile", "1.png", RequestBody.create(MediaType.parse("application/octet-stream"), file)) .build();
上面添加用戶民和密碼的部分和我們上面學習的提交鍵值對的方法很像,我們關鍵要注意以下幾點:
(1)如果提交的是表單,一定要設置 setType(MultipartBody.FORM) 這一句
(2)提交的文件 addFormDataPart() 的第一個參數,就上面代碼中的 myfile 就是類似於鍵值對的鍵,是供服務端使用的,就類似於網頁表單里面的 name 屬性,例如下面:
<input type="file" name="myfile">
(3)提交的文件 addFormDataPart() 的第二個參數文件的本地的名字,第三個參數是 RequestBody ,里面包含了我們要上傳的文件的路徑以及 MidiaType
(4)記得在 AndroidManifest.xml 文件中添加存儲卡讀寫權限
七、OkHttp進行get請求下載文件
除了上面的功能,我們最常用的功能該有從網路上下載文件,我們下面的例子將演示下載一個文件存放在存儲卡根目錄,從網絡下載一張圖片並顯示到ImageView中
1 . 從網絡下載一個文件(此處我們以下載一張圖片為例)
public void downloadImg(View view){ OkHttpClient client = new OkHttpClient(); final Request request = new Request.Builder() .get() .url("https://www.baidu.com/img/bd_logo1.png") .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e("moer", "onFailure: ");; } @Override public void onResponse(Call call, Response response) throws IOException { //拿到字節流 InputStream is = response.body().byteStream(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "n.png"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[128]; while ((len = is.read(buf)) != -1){ fos.write(buf, 0, len); } fos.flush(); //關閉流 fos.close(); is.close(); } }); }
你會發現步驟與進行一般的 Get 請求差別不大,唯一的區別在於我們在回調函數中所做的事,我們拿到了圖片的字節流,然后保存為了本地的一張圖片
2 . 從網絡下載一張圖片並設置到ImageView中
其實學會了上面的步驟你完全可以將圖片下載到本地后再設置到ImageView中,當然下面是另一種方法
這里我們使用 BitmapFactory 的 decodeStream 將圖片的輸入流直接轉換為 Bitmap ,然后設置到 ImageView 中,下面只給出 onResponse() 中的代碼.
@Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); final Bitmap bitmap = BitmapFactory.decodeStream(is); runOnUiThread(new Runnable() { @Override public void run() { imageView.setImageBitmap(bitmap); } }); is.close(); }
八、給文件的上傳和下載加上進度條
我們一直都說,用戶體驗很重要,當我們下載的文件比較大,而網速又比較慢的時候,如果我們只是在后台下載或上傳,沒有給用戶顯示一個進度,那將是非常差的用戶體驗,下面我們就將簡單做一下進度的顯示,其實非常簡單的
1 . 顯示文件下載進度
這里只是演示,我只是把進度顯示在一個TextView中,至於進度的獲取當然是在我們的回調函數 onResponse() 中去獲取
(1)使用 response.body().contentLength() 拿到文件總大小
(2)在 while 循環中每次遞增我們讀取的buf的長度
@Override public void onResponse(Call call, Response response) throws IOException { InputStream is = response.body().byteStream(); long sum = 0L; //文件總大小 final long total = response.body().contentLength(); int len = 0; File file = new File(Environment.getExternalStorageDirectory(), "n.png"); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[128]; while ((len = is.read(buf)) != -1){ fos.write(buf, 0, len); //每次遞增 sum += len; final long finalSum = sum; Log.d("pyh1", "onResponse: " + finalSum + "/" + total); runOnUiThread(new Runnable() { @Override public void run() { //將進度設置到TextView中 contentTv.setText(finalSum + "/" + total); } }); } fos.flush(); fos.close(); is.close(); }
2 . 顯示文件上傳進度
對於上傳的進度的處理會比較麻煩,因為具體的上傳過程是在 RequestBody 中由 OkHttp 幫我們處理上傳,而且 OkHttp 並沒有給我們提供上傳進度的接口,這里我們的做法是自定義類繼承 RequestBody ,然后重寫其中的方法,將其中的上傳進度通過接口回調暴露出來供我們使用。
public class CountingRequestBody extends RequestBody { //實際起作用的RequestBody private RequestBody delegate; //回調監聽 private Listener listener; private CountingSink countingSink; /** * 構造函數初始化成員變量 * @param delegate * @param listener */ public CountingRequestBody(RequestBody delegate, Listener listener){ this.delegate = delegate; this.listener = listener; } @Override public MediaType contentType() { return delegate.contentType(); } @Override public void writeTo(BufferedSink sink) throws IOException { countingSink = new CountingSink(sink); //將CountingSink轉化為BufferedSink供writeTo()使用 BufferedSink bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); } protected final class CountingSink extends ForwardingSink{ private long byteWritten; public CountingSink(Sink delegate) { super(delegate); } /** * 上傳時調用該方法,在其中調用回調函數將上傳進度暴露出去,該方法提供了緩沖區的自己大小 * @param source * @param byteCount * @throws IOException */ @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); byteWritten += byteCount; listener.onRequestProgress(byteWritten, contentLength()); } } /** * 返回文件總的字節大小 * 如果文件大小獲取失敗則返回-1 * @return */ @Override public long contentLength(){ try { return delegate.contentLength(); } catch (IOException e) { return -1; } } /** * 回調監聽接口 */ public static interface Listener{ /** * 暴露出上傳進度 * @param byteWritted 已經上傳的字節大小 * @param contentLength 文件的總字節大小 */ void onRequestProgress(long byteWritted, long contentLength); } }
上面的代碼注釋非常詳細,這里不再解釋,然后我們在寫具體的請求時還需要做如下變化
File file = new File(Environment.getExternalStorageDirectory(), "1.png"); if (!file.exists()){ Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show(); }else{ RequestBody requestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file); } //使用我們自己封裝的類 CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody2, new CountingRequestBody.Listener() { @Override public void onRequestProgress(long byteWritted, long contentLength) { //打印進度 Log.d("pyh", "進度 :" + byteWritted + "/" + contentLength); } });
上面其實就是在原有的 RequestBody 上包裝了一層,最后在我們的使用中在 post() 方法中傳入我們的 CountingRequestBody 對象即可。