OkHttp使用詳解


今天學習了一下OkHttp,在這里做個總結,希望可以幫助到有需要的人,好了,廢話不多說,進入正題。

一、OkHttp介紹

OkHttp是一個優秀的網絡請求框架,可能一說到網絡請求框架,可能很多人都會想到volley,volley是一個Google提供的網絡請求框架,我的博客里也有一篇專門介紹volley的博客,博客地址在此Android網絡請求 ------ Volley的使用 那么既然Google提供了網絡請求的框架,我們為什么還要使用OkHttp呢,原來是volley是要依靠HttpCient的,而Google在Android6.0的SDK中去掉了HttpCient,所以OkHttp就開始越來越受大家的歡迎.

今天我們主要介紹OkHttpGet請求、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);
            }
        });
    }
});

第四步有一些需要注意的地方

  1. 同步調用會阻塞主線程,一般不適用
  2. 異步調用的回調函數是在子線程,我們不能在子線程更新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對象,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查看相關的內容,這里不再贅述

五、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中,當然下面是另一種方法 這里我們使用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();
}

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

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

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

九、后記

以上就是一些OkHttp常用的總結,希望可以幫助到需要的人


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM