OkHttp使用詳解

今天學習了一下 OkHttp,在這裏作個總結,但願能夠幫助到有須要的人,好了,廢話很少說,進入正題。

1、OkHttp介紹

OkHttp是一個優秀的網絡請求框架,可能一說到網絡請求框架,可能不少人都會想到 volley, volley是一個Google提供的網絡請求框架,個人博客裏也有一篇專門介紹 volley的博客,博客地址在此 Android網絡請求 ------ Volley的使用 那麼既然Google提供了網絡請求的框架,咱們爲何還要使用 OkHttp呢,原來是 volley是要依靠 HttpCient的,而Google在 Android6.0的SDK中去掉了 HttpCient,因此 OkHttp就開始愈來愈受你們的歡迎.

今天咱們主要介紹OkHttpGet請求、Post請求、上傳下載文件上傳下載圖片等功能html

固然在開始以前,咱們還要先在項目中添加OkHttp的依賴庫,至於怎麼在AndroidStudio中給項目添加OkHTTP依賴,這裏將再也不贅述。另外,OkHttp中使用了建造者模式,若是對建造者模式不瞭解,能夠看看這篇博客設計模式之建造者模式**java

添加OkHttp的依賴
在對應的Module的gradle中添加
compile 'com.squareup.okhttp3:okhttp:3.5.0'   
而後同步一下項目便可

2、OkHttp進行Get請求

使用OkHttp進行 Get請求只須要四步便可完成。

1 . 拿到OkHttpClient對象android

OkHttpClient client = new OkHttpClient();

2 . 構造Request對象json

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);
            }
        });
    }
});
第四步有一些須要注意的地方
  1. 同步調用會阻塞主線程,通常不適用
  2. 異步調用的回調函數是在子線程,咱們不能在子線程更新UI,須要藉助於runOnUiThread()方法或者Handler來處理

是否是覺得上面就結束了,對的,OkHttp的Get請求步驟就這麼4步,可是當你試圖打開應用加載數據,但是發現並無加載到數據,這是一個簡單可是咱們常犯的錯誤.在AndroidManifest.xml中加入聯網權限app

<uses-permission android:name="android.permission.INTERNET" />

3、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請求的步驟很是像。

4、OkHttp進行Post請求提交字符串

若是你已經掌握了上面的兩種基本的步驟,那下面的內容就比較簡單了

上面咱們的post的參數是經過構造一個 FormBody經過鍵值對的方式來添加進去的,其實post方法須要傳入的是一個 RequestBody對象, FormBodyRequestBody的子類,但有時候咱們經常會遇到要傳入一個字符串的需求,好比客戶端給服務器發送一個json字符串,那這種時候就須要用到另外一種方式來構造一個 RequestBody以下所示:
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "{username:admin;password:admin}");
上面的MediaType咱們指定傳輸的是純文本,並且編碼方式是utf-8,經過上面的方式咱們就能夠向服務端發送json字符串啦。

注:關於MidiaType的類型你能夠百度搜索mime type查看相關的內容,這裏再也不贅述

5、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"/>

6、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文件中添加存儲卡讀寫權限

7、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中,固然下面是另外一種方法 這裏咱們使用 BitmapFactorydecodeStream將圖片的輸入流直接轉換爲 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();
}

8、給文件的上傳和下載加上進度條

咱們一直都說,用戶體驗很重要,當咱們下載的文件比較大,而網速又比較慢的時候,若是咱們只是在後臺下載或上傳,沒有給用戶顯示一個進度,那將是很是差的用戶體驗,下面咱們就將簡單作一下進度的顯示,其實很是簡單的

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對象便可。

9、後記

以上就是一些 OkHttp經常使用的總結,但願能夠幫助到須要的人
相關文章
相關標籤/搜索