HTTP報文及Retrofit基本使用

1 前言

??每次使用 Retrofit 做網(wǎng)絡請求都要到網(wǎng)上去搜索代碼,然后復制、粘貼;有時候某個注解的使用方式忘記了,也會查查這個注解的使用例子。查的多了就覺得煩了,所以想著自己總結(jié)一下,下次使用直接查自己的博客就好了。
??從本質(zhì)來看,Retrofit 網(wǎng)絡請求工作都是 OkHttp 做的,它只是對 OkHttp 的又一次封裝。
?? Retrofit 通過注解的方式,封裝了網(wǎng)絡請求的接口,所以學習 Retrofit 的關鍵是學會注解。那注解是用來干啥使的呢?注解其實是用來拼接網(wǎng)絡請求接口用的,也可以說是用來拼接報文的。所以在學習注解之前,應該先學習一下報文的知識。學完了報文再學注解,理解起注解的意思來就會非常容易了。

2 報文

2.1 報文的格式

??為什么要在學習 Retrofit 之前,要先學習報文呢?
??因為每一次網(wǎng)絡交互,其實都是報文的交互。App 發(fā)送請求報文給服務器,服務器收到請求后發(fā)送響應報文給App,App根據(jù)響應報文再解析出自己需要的數(shù)據(jù)進行顯示,這就完成了一次網(wǎng)絡交互。
??所以,只要知道了報文的格式,再熟悉一下 Retrofit 的注解,就能夠很容易理解、記憶 Retrofit 的使用方式了。

2.1.1 請求報文的格式

??先說一下 HTTP 請求報文的組成吧,一個HTTP請求報文由請求行、請求頭、空行和請求體四部分組成,其一般格式如圖一所示。

圖一 HTTP請求報文.png

2.1.1.1 請求行

??請求行由請求方法、路徑和 HTTP 協(xié)議版本這三個字段組成,字段間使用空格間隔。圖一中的 POST 就是請求方法,/user/register 就是請求路徑,HTTP/1.1 就是 HTTP 協(xié)議版本。

??HTTP 協(xié)議從版本角度分為 HTTP 1.0 和 HTTP1.1。
??HTTP 1.0 請求方法:GET,POSTHEAD
??HTTP1.1 新增的請求方法:OPTIONS,PUT, DELETETRACECONNECT。
??所以 ,HTTP 協(xié)議總共有 8 種請求方法。這里說的 HTTP 1.0 和 HTTP1.1 就是HTTP 協(xié)議的版本。

??下面記錄下各個請求方法的作用。
??【GET】 請求數(shù)據(jù)。請求指定的URL,并返回實體主體(常見的是返回 Json 串)。
??【POST】 新增或修改。向指定資源提交數(shù)據(jù)進行處理請求(例如提交表單或者上傳文件)。數(shù)據(jù)被包含在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的修改。
??【HEAD】 獲取響應頭。類似于get請求,只不過返回的響應中沒有具體的內(nèi)容,用于獲取報頭
??【PUT】 修改。從客戶端向服務器傳送的數(shù)據(jù)取代指定的文檔的內(nèi)容。
??【DELETE】 刪除。請求服務器刪除指定的頁面。
??【CONNECT】 HTTP/1.1協(xié)議中預留給能夠?qū)⑦B接改為管道方式的代理服務器。
??【OPTIONS】 允許客戶端查看服務器的性能
??【TRACE】 回顯服務器收到的請求,主要用于測試或診斷

??雖然我們有這么多種請求方法,但常用的還是 GET、POST、HEAD 、PUT 和 DELETE 這五種方法。
??GET 請求用于從服務器獲取數(shù)據(jù),比如獲取 Json 格式的數(shù)據(jù),一般來說,GET 請求報文是沒有請求體的,
??POST 方法用于新增或修改資源,典型的應用就是用戶注冊,注冊一個新用戶,就是向服務器新增一個資源,POST 方法是有請求體的。
??HEAD 方法和 GET 方法類似,他們的區(qū)別就是 HEAD 用于獲取響應頭,它不返回響應體,而 GET 方法既會返回響應頭,也會返回響應體。
??PUT 方法用于修改服務器資源,典型應用就是修改昵稱,它和 POST 方法的區(qū)別是 POST 方法既可以修改資源,也可以新增資源,但 PUT 方法只能新增資源,不能修改資源。
??DELETE 方法用于刪除資源,沒什么可說的。

??說完了請求行中的請求方法,接著說說請求行中的路徑。那請求行中的路徑是怎么來的呢?我舉例說一下吧,如圖二所示,我有 https://www.wanandroid.com/user/register 這樣一個接口,那這個接口中的 https 就表示協(xié)議類型,www.wanandroid.com 就表示主機地址,/user/register 就是路徑了,這個解釋一目了然,我就不多說了。

??請求行中的協(xié)議版本前面已經(jīng)提過了,就不贅述了。


圖二 URL.png
2.1.1.2 請求頭

??請求頭:請求頭由鍵值對組成,每一行就是一組鍵值對,鍵和值之間用英文冒號分隔,像圖一中的 Host: www.wanandroid.com 這樣就是一組請求頭。

??下面記錄一些比較常見的請求頭。
??User-Agent: 用戶代理,即是誰實際發(fā)送請求、接受響應的,例如?機瀏覽器、某款?機 App
??Host:主機名
??Content-Type: 指定 Body 的類型,比如 Content-Type: text/html; charset=utf-8、
Content-Type: application/x-www-form-urlencoded、Content-Type: multipart/form-data; boundary=----
Content-Type: application/json; charset=utf-8等等。
??Content-Encoding:壓縮類型。如 gzip
??Content-Length:請求體或響應體的長度 指定 Body 的長度(字節(jié))
??Accept:表示客戶端希望服務器返回什么類型的響應
??Accept-Charset: 客戶端接受的字符集。如 utf-8
??Accept-Encoding: 客戶端接受的壓縮編碼類型。如 gzip
??Accept-Charset:表示客戶端希望服務器返回的內(nèi)容的編碼格式
Location 指定重定向的?標 URL

2.1.2 響應報文的格式

??響應報文和請求報文的格式差不多,響應報文由狀態(tài)行、響應頭和響應體組成,響應報文格式如圖三所示。狀態(tài)行由 Http協(xié)議版本,響應碼和響應信息組成。
??響應頭和響應體的意思和請求報文一樣,所以也沒有什么可多說的了。

圖三 HTTP響應報文.png

?? 下面著重說一下狀態(tài)碼。狀態(tài)碼由三位數(shù)字組成,第一個數(shù)字定義了響應的類別。
?? 1xx:指示信息--表示請求已接收,繼續(xù)處理。
?? 2xx:成功--表示請求已被成功接收、理解、接受。
?? 3xx:重定向--要完成請求必須進行更進一步的操作。
?? 4xx:客戶端錯誤--請求有語法錯誤或請求無法實現(xiàn)。
?? 5xx:服務器端錯誤--服務器未能實現(xiàn)合法的請求。

?? 常見狀態(tài)代碼、狀態(tài)描述的說明如下。
?? 200 OK:客戶端請求成功。
?? 400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解。
?? 401 Unauthorized:請求未經(jīng)授權(quán),這個狀態(tài)代碼必須和WWW-Authenticate報頭域一起使用。
?? 403 Forbidden:服務器收到請求,但是拒絕提供服務。
?? 404 Not Found:請求資源不存在,舉個例子:輸入了錯誤的URL。
?? 500 Internal Server Error:服務器發(fā)生不可預期的錯誤。
?? 503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常。

2.2 報文的實例

??現(xiàn)在我有這樣一個接口:https://www.wanandroid.com/user/register(該接口來自于鴻洋老師的玩 Android 開放 API),使用 Postman 發(fā)送一下 POST請求,如圖四所示,我們看到了該接口返回了 Json 數(shù)據(jù),說明我們請求成功了。

圖四 Postman模擬注冊請求.png

??接下來,通過點擊如圖四所示的紅框圈出來的“Code”,即可看到如圖五所示的網(wǎng)絡請求的報文,這時我們就看到了之前提到的那些東西,比如 Content-Type、Host 等。其實我們平時在用 OkHttp 做網(wǎng)絡請求的時候,也可以通過攔截器拿到這些東西。

圖五 請求報文.png

??看完請求報文我們再看一下響應報文。不過,Postman將響應報文分開顯示了,沒有像請求報文一樣集中在一起。

圖六 Postman 響應.png

??如圖六所示,狀態(tài)行、響應頭和響應體是分開的。

??圖六中紅框圈出來的 Body 代表響應體,我們 Android 端見得最多的響應體格式就是 Json 格式了吧,哈哈。
??圖六中紅框圈出來的 Headers 代表響應頭,比較常見的響應頭部有 Content-Type、Date等。
??圖六中紅框圈出來的 Status:200 OK 是狀態(tài)行的一部分,200是響應碼,OK 是響應信息,還缺一個沒顯示的是 Http 協(xié)議版本,它顯示不顯示對我們來說也不重要,反正用不到。

3 使用

??說了半天,終于開始寫 Retrofit 的使用了,真不容易啊?,F(xiàn)在如果我要請求這個接口的數(shù)據(jù):https://wanandroid.com/wxarticle/chapters/json ,我該怎么做呢?
??第一步,添加依賴
??新建一個 Project,在 app 的 build.gradle (不是 project 的 build.gradle)添加如下依賴:

    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

??第二步,在 AndroidManifest.xml 里添加網(wǎng)絡權(quán)限。

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

??第三步,新建實體類
??通過 Postman 請求接口,獲取返回的 Json 數(shù)據(jù),使用 GsonFormat(Android Studio 的插件) 生成實體類 ArticleModel 。

package isuperred.com.github.retrofit;

import java.util.List;

public class ArticleModel {
    private int errorCode;
    private String errorMsg;
    private List<DataBean> data;

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public List<DataBean> getData() {
        return data;
    }

    public void setData(List<DataBean> data) {
        this.data = data;
    }

    public static class DataBean {       

        private int courseId;
        private int id;
        private String name;
        private int order;
        private int parentChapterId;
        private boolean userControlSetTop;
        private int visible;
        private List<?> children;

        public int getCourseId() {
            return courseId;
        }

        public void setCourseId(int courseId) {
            this.courseId = courseId;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getOrder() {
            return order;
        }

        public void setOrder(int order) {
            this.order = order;
        }

        public int getParentChapterId() {
            return parentChapterId;
        }

        public void setParentChapterId(int parentChapterId) {
            this.parentChapterId = parentChapterId;
        }

        public boolean isUserControlSetTop() {
            return userControlSetTop;
        }

        public void setUserControlSetTop(boolean userControlSetTop) {
            this.userControlSetTop = userControlSetTop;
        }

        public int getVisible() {
            return visible;
        }

        public void setVisible(int visible) {
            this.visible = visible;
        }

        public List<?> getChildren() {
            return children;
        }

        public void setChildren(List<?> children) {
            this.children = children;
        }
    }
}

??第四步,添加接口
??新建存放請求數(shù)據(jù)的接口的類 ApiService。

package isuperred.com.github.retrofit;

import retrofit2.Call;
import retrofit2.http.GET;

public interface ApiService {

    @GET("wxarticle/chapters/json")
    Call<ArticleModel> getArticle();

}

??上面的 @GET 注解表示該接口使用 GET 請求方法,"wxarticle/chapters/json" 就是請求報文中請求行里 路徑 這個字段,Call 是固定不變的(如果使用 Rxjava,Call 會被替換為 Observable),泛型 ArticleModel 就是第三步新建的實體類,getArticle 是方法名。

??第五步,創(chuàng)建 Retrofit 管理類 RetrofitManager。下面的 BASE_URL 就是https://wanandroid.com/wxarticle/chapters/json 這個接口的協(xié)議和域名。注意一下,BASE_URL 最后是有“/”的,ApiService 接口的注解里的 wxarticle/chapters/json 最前面是沒有“/” 的。

package isuperred.com.github.retrofit;

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitManager {

    private static RetrofitManager instance;
    private static final String BASE_URL = "https://www.wanandroid.com/";
    private Retrofit mRetrofit;

    public RetrofitManager() {

        mRetrofit = new Retrofit
                .Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public static RetrofitManager getInstance() {
        if (instance == null) {
            synchronized (RetrofitManager.class) {
                instance = new RetrofitManager();
            }
        }
        return instance;
    }

    public <T> T create(final Class<T> service) {
        return mRetrofit.create(service);
    }
}

??第六步,在 Activity 里調(diào)用 Retrofit 方法進行異步或同步請求網(wǎng)絡數(shù)據(jù)。注意同步請求會阻塞 UI 線程,所以同步請求應在子線程中執(zhí)行。

package isuperred.com.github.retrofit;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;
import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Button btnEnqueue = findViewById(R.id.btn_enqueue);
        Button btnExecute = findViewById(R.id.btn_execute);

        btnEnqueue.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //創(chuàng)建ApiService對象
                ApiService apiService = RetrofitManager.getInstance().create(ApiService.class);
                Call<ArticleModel> article = apiService.getArticle();
                //異步請求數(shù)據(jù)
                article.enqueue(new Callback<ArticleModel>() {
                    @Override
                    public void onResponse(Call<ArticleModel> call, Response<ArticleModel> response) {
                        ArticleModel articleModel = response.body();
                        if (articleModel == null) {
                            return;
                        }
                        List<ArticleModel.DataBean> dataBeans = articleModel.getData();
                        for (int i = 0; i < dataBeans.size(); i++) {
                            Log.e(TAG, "onResponse Name: " + dataBeans.get(i).getName());
                        }
                    }

                    @Override
                    public void onFailure(Call<ArticleModel> call, Throwable t) {
                        t.printStackTrace();

                    }
                });
            }
        });

        btnExecute.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        //創(chuàng)建ApiService對象
                        ApiService apiService = RetrofitManager.getInstance().create(ApiService.class);
                        Call<ArticleModel> article = apiService.getArticle();

                        ArticleModel articleModel = null;
                        try {
                            //同步請求數(shù)據(jù)
                            articleModel = article.execute().body();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if (articleModel == null) {
                            return;
                        }
                        List<ArticleModel.DataBean> dataBeans = articleModel.getData();
                        for (int i = 0; i < dataBeans.size(); i++) {
                            Log.e(TAG, "onResponse Name: " + dataBeans.get(i).getName());
                        }

                    }
                }).start();

            }
        });

    }
}

??其對應的布局文件 activity_main.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <Button
        android:id="@+id/btn_enqueue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="異步請求"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_execute"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="同步請求"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_enqueue" />

</android.support.constraint.ConstraintLayout>

4 總結(jié)

??先寫這么多吧,注解下次再寫了。寫東西真麻煩啊,越寫越多,越寫越多,我都沒想到報文的內(nèi)容寫了這么久。關鍵我都寫煩了,還覺得自己沒寫清楚呢。真的是覺得把一個東西寫清楚是需要功力的,我還沒有功力啊。。。我就不瞎感慨了,早點回去洗衣服了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容