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.lxweimin.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常用的總結,希望可以幫助到需要的人

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容

  • 參考okhttp官方wiki https://github.com/square/okhttp/wiki/Call...
    WangGavin閱讀 7,178評論 0 1
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,607評論 25 708
  • Cookie保存 Cookie的保存也提供了快捷方式,當然也可以通過攔截器自己實現 Websocket okhtt...
    WangGavin閱讀 1,973評論 0 1
  • 文/村草 女朋友最近天天加班,特忙。 晚上經常忙到9-10點,然后同事們一起出去吃飯,回家倒頭就睡。 今天從家里吃...
    村草視角閱讀 378評論 0 0