每天學習一個Android中的常用框架——3.OkHttp

1.簡介

在過去,Android上發送HTTP請求一般有兩種方式:HttpURLConnection和HttpClient(Apache HttpClient)。不過由於HttpClient存在API數量過多、擴展困難等缺點,Android團隊越來越不建議我們使用這種方式。終於在Android 6.0系統中,HttpClient的功能被完全移除了,標誌着此功能被正式棄用。雖然官方推薦使用HttpURLConnection,但是在代碼編寫的過程中仍然會比較繁瑣。此時,一個將要替代它們的網絡層框架出現了:也就是Okhttp,該框架也是我在《Android 第一行代碼》上學到的僅此於Litepal之後的第二個框架。今天重新學習了一下Okhttp,將學習歷程記錄到博客上,望對讀者有幫助。
OkHttp是由鼎鼎大名的Square公司開發的,這個公司在開源事業上面貢獻良多,除了 OkHttp 之外,還開發了像Picasso,Retrofit等著名的開源項目。OkHttp不僅在接口封裝上面做得簡單易用,就連在底層實現上也是自成一派,比起原生的HttpURLConnection,可以說是有過之而無不及,現在已經成了廣大Android開發者首選的網絡通信庫。

2.特性

OkHttp是一個高效的HTTP客戶端,它有以下默認特性:

  • 支持HTTP/2,允許所有同一個主機地址的請求共享同一個socket連接
  • 連接池減少請求延時
  • 透明的GZIP壓縮減少響應數據的大小
  • 緩存響應內容,避免一些完全重複的請求

當網絡出現問題的時候OkHttp依然堅守自己的職責,它會自動恢復一般的連接問題,如果你的服務有多個IP地址,當第一個IP請求失敗時,OkHttp會交替嘗試你配置的其他IP,OkHttp使用現代TLS技術(SNI, ALPN)初始化新的連接,當握手失敗時會回退到TLS 1.0。

OkHttp 支持 Android 2.3 及以上版本Android平臺, 以及 Java,JDK 1.7及以上.

OkHttp的使用是非常簡單的. 它的請求/響應 API 使用構造器模式builders來設計,它支持阻塞式的同步請求和帶回調的異步請求
爲了更好地演示OkHttp,這裏僅展示出同步/異步執行get/post請求的相關api,至於關於OkHttp的相關調用,建議參考OkHttp的官方文檔:OkHttp官網,無需翻牆
關於使用post請求發送流/文件/表單等,這些功能則要配合另一個框架:OkIo,將在下一篇博客中專門展示出來。
話不多說,讓我們馬上開始使用OkHttp吧。

3.演示

3.1 集成

又到了喜聞樂見的集成環節,我們只需要修改module下的build.gradle,加入OkHttp的依賴,代碼如下:

implementation("com.squareup.okhttp3:okhttp:4.6.0")

記得重新Sync一下,確保OkHttp集成到了你的項目中。作者在寫下此篇博客時OkHttp3的穩定版本號以達到4.6.0,如果讀者在OkHttp的官網上發現了更新的版本,記得及時更新,做到與時俱進。

3.2 配置

既然你集成了OkHttp,就不可不免地要進行與網絡有關的操作。既然如此,就需要在清單文件下聲明網絡權限,即:

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

另外:如果你的Android版本爲Android P(即targetSdkVersion 27),在運行該項目時可能會報有關網絡的錯誤,如圖所示:
在這裏插入圖片描述
原因:在Android P系統的設備上,如果應用使用的是非加密的明文流量的http網絡請求,則會導致該應用無法進行網絡請求,https則不會受影響,同理若應用內使用WebView加載網頁 則加載網頁也需要是https請求。
解決方法

  • APP整體網絡請求改用https
  • 將targetSdkVersion 版本下調至27以下
  • 更改項目網絡安全配置

三種方法亦可,這裏主要介紹一下第三種解決方法,以拓寬解決思路

  1. 在res目錄下新建xml文件夾 在xml文件夾內新建名爲network_config(名字非固定)的xml,代碼如下:
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. 然後,在AndroidManifest中增加android:networkSecurityConfig屬性,代碼如下:
 <application
    ...
     android:networkSecurityConfig="@xml/network_security_config"
    ...
        />
  1. 以上兩個步驟就完成了網絡安全配置,如果實在不行的話可以嘗試另外兩種方法或者百度

3.3 佈局文件和URL封裝

接下來,我們直接開始佈局文件activity_main.xml的編寫。該佈局很簡單,僅有四個按鈕,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_get_syn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="發送get請求——同步"/>

    <Button
        android:id="@+id/btn_get_asy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="發送get請求——異步"/>

    <Button
        android:id="@+id/btn_post_syn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="發送post請求——異步"/>

    <Button
        android:id="@+id/btn_post_asy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="發送post請求——異步"/>

</LinearLayout>

之後,我們簡單用字符串封裝一下要請求的URL,即http://wwww.baidu.com。這裏建議想要深入OkHttp的讀者自己搭建一個Tomcat/Apache服務器,將資源文件上傳到服務器上,然後訪問一下本地路徑,這樣可以加深對於網絡請求的理解。這裏爲了便於演示就直接網絡請求百度,代碼如下:

private static final String URL = "http://wwww.baidu.com";

3.4 同步GET請求

同步GET請求的步驟很簡單,大抵分爲:

  • 構造OkHttpClient對象;
  • 構造Request對象;
  • 通過前兩步中的對象構建Call對象;
  • 通過call.execute()方法來提交同步請求。注意,由於是同步請求,所以該api要放在子線程中,避免阻塞主線程,造成ANR異常。另外,Android3.0 以後已經不允許在主線程訪問網絡。

代碼如下:

private void getBySynchronized() {
        btn_get_syn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.構建Client對象
                OkHttpClient client = new OkHttpClient();
                // 2.採用建造者模式和鏈式調用構建Request對象
                final Request request = new Request.Builder()
                        .url(URL) // 請求URL
                        .get() // 默認就是get請求,可以不寫
                        .build();
                // 3.通過1和2產生的Client和Request對象生成Call對象
                final Call call = client.newCall(request);
                // 4.同步發送get請求需要使用execute()方法,並且爲了防止主線程阻塞需要放在子線程中自行
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 6.構建response對象
                            Response response = call.execute();
                            Log.d(TAG, "同步發送get請求成功!請求到的信息爲:" + response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }

在這裏插入圖片描述

3.5 異步GET請求

異步GET請求的前面幾個步驟和同步方式一樣,只是最後一部是通過call.enqueue(Callback()) 來提交請求,代碼如下:

private void getByAsynchronized() {
        btn_get_asy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.構建Client對象
                OkHttpClient client = new OkHttpClient();
                // 2.採用建造者模式和鏈式調用構建Request對象
                final Request request = new Request.Builder()
                        .url(URL) // 請求URL
                        .get() // 默認就是get請求,可以不寫
                        .build();
                // 3.通過1和2產生的Client和Request對象生成Call對象
                Call call = client.newCall(request);
                // 4.調用Call對象的enqueue()方法,並且實現一個回調實現類
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
                        Log.d(TAG, "異步發送get請求失敗!");
                        e.printStackTrace();
                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        Log.d(TAG, "異步發送get請求成功!請求到的信息爲:" + response.body().string());
                    }
                });
            }
        });
    }

在這裏插入圖片描述

3.6 同步POST請求

同步POST請求的步驟類似於同步GET請求,大抵分爲:

  • 構造OkHttpClient對象;
  • 構造FormBody對象(鍵值對)
  • 構造Request對象;
  • 通過前兩步中的對象構建Call對象;
  • 通過call.execute()方法來提交同步請求。

代碼如下:

private void postBySynchronized() {
        btn_post_syn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.構建Client對象
                OkHttpClient client = new OkHttpClient();
                // 2.採用建造者模式和鏈式調用構建鍵值對對象
                FormBody formBody = new FormBody.Builder()
                        .add("username", "admin")
                        .add("password", "123456")
                        .build();
                // 3.採用建造者模式和鏈式調用構建Request對象
                final Request request = new Request.Builder()
                        .url(URL) // 請求URL
                        .post(formBody) // 默認就是get請求,可以不寫
                        .build();
                // 4.通過1和3產生的Client和Request對象生成Call對象
                final Call call = client.newCall(request);
                // 5.同步發送post請求需要使用execute()方法,並且爲了防止主線程阻塞需要放在子線程中自行
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 6.構建response對象
                            Response response = call.execute();
                            Log.d(TAG, "同步發送post請求成功!請求到的信息爲:" + response.body().string());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }

在這裏插入圖片描述

3.7 異步POST請求

異步POST請求的前面幾個步驟和同步方式一樣,只是最後一部是通過call.enqueue(Callback()) 來提交請求,代碼如下:

private void postByAsynchronized() {
        btn_post_asy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.構建Client對象
                OkHttpClient client = new OkHttpClient();
                // 2.採用建造者模式和鏈式調用構建鍵值對對象
                FormBody formBody = new FormBody.Builder()
                        .add("username", "admin")
                        .add("password", "123456")
                        .build();
                // 3.採用建造者模式和鏈式調用構建Request對象
                final Request request = new Request.Builder()
                        .url(URL) // 請求URL
                        .post(formBody) // 默認就是get請求,可以不寫
                        .build();
                // 4.通過1和3產生的Client和Request對象生成Call對象
                Call call = client.newCall(request);
                // 5.調用Call對象的enqueue()方法,並且實現一個回調實現類
                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
                        Log.d(TAG, "異步發送post請求失敗!");
                        e.printStackTrace();
                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        Log.d(TAG, "異步發送post請求成功!請求到的信息爲:" + response.body().string());
                    }
                });
            }
        });
    }

在這裏插入圖片描述

4 總結

  • 同步調用:編寫簡單,但是會阻塞主線程,一般不適用
  • 異步調用:回調函數是在子線程,我們不能在子線程更新UI,需要藉助於runOnUiThread()方法或者Handler來處理

一般來說,雖說同步/異步調用各有優劣,在實際項目裏,我們還是異步調用會相對用得多一些,搭配RxJava/RxAndroid以及EventBus,可以提升用戶的體驗。當然,如果是爲了快速開發比較簡單的小demo,同步調用也夠用了。

5.源碼地址

AFL——Android框架學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章