HTTP报文及Retrofit基本使用

1 前言

  每次使用 Retrofit 做网络请求都要到网上去搜索代码,然后复制、粘贴;有时候某个注解的使用方式忘记了,也会查查这个注解的使用例子。查的多了就觉得烦了,所以想着自己总结一下,下次使用直接查自己的博客就好了。
  从本质来看,Retrofit 网络请求工作都是 OkHttp 做的,它只是对 OkHttp 的又一次封装。
   Retrofit 通过注解的方式,封装了网络请求的接口,所以学习 Retrofit 的关键是学会注解。那注解是用来干啥使的呢?注解其实是用来拼接网络请求接口用的,也可以说是用来拼接报文的。所以在学习注解之前,应该先学习一下报文的知识。学完了报文再学注解,理解起注解的意思来就会非常容易了。

2 报文

2.1 报文的格式

  为什么要在学习 Retrofit 之前,要先学习报文呢?
  因为每一次网络交互,其实都是报文的交互。App 发送请求报文给服务器,服务器收到请求后发送响应报文给App,App根据响应报文再解析出自己需要的数据进行显示,这就完成了一次网络交互。
  所以,只要知道了报文的格式,再熟悉一下 Retrofit 的注解,就能够很容易理解、记忆 Retrofit 的使用方式了。

2.1.1 请求报文的格式

  先说一下 HTTP 请求报文的组成吧,一个HTTP请求报文由请求行、请求头、空行和请求体四部分组成,其一般格式如图一所示。

图一 HTTP请求报文.png

2.1.1.1 请求行

  请求行由请求方法、路径和 HTTP 协议版本这三个字段组成,字段间使用空格间隔。图一中的 POST 就是请求方法,/user/register 就是请求路径,HTTP/1.1 就是 HTTP 协议版本。

  HTTP 协议从版本角度分为 HTTP 1.0 和 HTTP1.1。
  HTTP 1.0 请求方法:GETPOSTHEAD
  HTTP1.1 新增的请求方法:OPTIONSPUTDELETETRACECONNECT
  所以 ,HTTP 协议总共有 8 种请求方法。这里说的 HTTP 1.0 和 HTTP1.1 就是HTTP 协议的版本。

  下面记录下各个请求方法的作用。
  【GET】 请求数据。请求指定的URL,并返回实体主体(常见的是返回 Json 串)。
  【POST】 新增或修改。向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
  【HEAD】 获取响应头。类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
  【PUT】 修改。从客户端向服务器传送的数据取代指定的文档的内容。
  【DELETE】 删除。请求服务器删除指定的页面。
  【CONNECT】 HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
  【OPTIONS】 允许客户端查看服务器的性能
  【TRACE】 回显服务器收到的请求,主要用于测试或诊断

  虽然我们有这么多种请求方法,但常用的还是 GET、POST、HEAD 、PUT 和 DELETE 这五种方法。
  GET 请求用于从服务器获取数据,比如获取 Json 格式的数据,一般来说,GET 请求报文是没有请求体的,
  POST 方法用于新增或修改资源,典型的应用就是用户注册,注册一个新用户,就是向服务器新增一个资源,POST 方法是有请求体的。
  HEAD 方法和 GET 方法类似,他们的区别就是 HEAD 用于获取响应头,它不返回响应体,而 GET 方法既会返回响应头,也会返回响应体。
  PUT 方法用于修改服务器资源,典型应用就是修改昵称,它和 POST 方法的区别是 POST 方法既可以修改资源,也可以新增资源,但 PUT 方法只能新增资源,不能修改资源。
  DELETE 方法用于删除资源,没什么可说的。

  说完了请求行中的请求方法,接着说说请求行中的路径。那请求行中的路径是怎么来的呢?我举例说一下吧,如图二所示,我有 https://www.wanandroid.com/user/register 这样一个接口,那这个接口中的 https 就表示协议类型,www.wanandroid.com 就表示主机地址,/user/register 就是路径了,这个解释一目了然,我就不多说了。

  请求行中的协议版本前面已经提过了,就不赘述了。


图二 URL.png
2.1.1.2 请求头

  请求头:请求头由键值对组成,每一行就是一组键值对,键和值之间用英文冒号分隔,像图一中的 Host: www.wanandroid.com 这样就是一组请求头。

  下面记录一些比较常见的请求头。
  User-Agent: 用户代理,即是谁实际发送请求、接受响应的,例如⼿机浏览器、某款⼿机 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 的长度(字节)
  Accept:表示客户端希望服务器返回什么类型的响应
  Accept-Charset: 客户端接受的字符集。如 utf-8
  Accept-Encoding: 客户端接受的压缩编码类型。如 gzip
  Accept-Charset:表示客户端希望服务器返回的内容的编码格式
Location 指定重定向的⽬标 URL

2.1.2 响应报文的格式

  响应报文和请求报文的格式差不多,响应报文由状态行、响应头和响应体组成,响应报文格式如图三所示。状态行由 Http协议版本,响应码和响应信息组成。
  响应头和响应体的意思和请求报文一样,所以也没有什么可多说的了。

图三 HTTP响应报文.png

   下面着重说一下状态码。状态码由三位数字组成,第一个数字定义了响应的类别。
   1xx:指示信息--表示请求已接收,继续处理。
   2xx:成功--表示请求已被成功接收、理解、接受。
   3xx:重定向--要完成请求必须进行更进一步的操作。
   4xx:客户端错误--请求有语法错误或请求无法实现。
   5xx:服务器端错误--服务器未能实现合法的请求。

   常见状态代码、状态描述的说明如下。
   200 OK:客户端请求成功。
   400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
   401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
   403 Forbidden:服务器收到请求,但是拒绝提供服务。
   404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
   500 Internal Server Error:服务器发生不可预期的错误。
   503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

2.2 报文的实例

  现在我有这样一个接口:https://www.wanandroid.com/user/register(该接口来自于鸿洋老师的玩 Android 开放 API),使用 Postman 发送一下 POST请求,如图四所示,我们看到了该接口返回了 Json 数据,说明我们请求成功了。

图四 Postman模拟注册请求.png

  接下来,通过点击如图四所示的红框圈出来的“Code”,即可看到如图五所示的网络请求的报文,这时我们就看到了之前提到的那些东西,比如 Content-Type、Host 等。其实我们平时在用 OkHttp 做网络请求的时候,也可以通过拦截器拿到这些东西。

图五 请求报文.png

  看完请求报文我们再看一下响应报文。不过,Postman将响应报文分开显示了,没有像请求报文一样集中在一起。

图六 Postman 响应.png

  如图六所示,状态行、响应头和响应体是分开的。

  图六中红框圈出来的 Body 代表响应体,我们 Android 端见得最多的响应体格式就是 Json 格式了吧,哈哈。
  图六中红框圈出来的 Headers 代表响应头,比较常见的响应头部有 Content-Type、Date等。
  图六中红框圈出来的 Status:200 OK 是状态行的一部分,200是响应码,OK 是响应信息,还缺一个没显示的是 Http 协议版本,它显示不显示对我们来说也不重要,反正用不到。

3 使用

  说了半天,终于开始写 Retrofit 的使用了,真不容易啊。现在如果我要请求这个接口的数据: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 里添加网络权限。

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

  第三步,新建实体类
  通过 Postman 请求接口,获取返回的 Json 数据,使用 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;
        }
    }
}

  第四步,添加接口
  新建存放请求数据的接口的类 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 是方法名。

  第五步,创建 Retrofit 管理类 RetrofitManager。下面的 BASE_URL 就是https://wanandroid.com/wxarticle/chapters/json 这个接口的协议和域名。注意一下,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 里调用 Retrofit 方法进行异步或同步请求网络数据。注意同步请求会阻塞 UI 线程,所以同步请求应在子线程中执行。

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) {
                //创建ApiService对象
                ApiService apiService = RetrofitManager.getInstance().create(ApiService.class);
                Call<ArticleModel> article = apiService.getArticle();
                //异步请求数据
                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() {

                        //创建ApiService对象
                        ApiService apiService = RetrofitManager.getInstance().create(ApiService.class);
                        Call<ArticleModel> article = apiService.getArticle();

                        ArticleModel articleModel = null;
                        try {
                            //同步请求数据
                            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 总结

  先写这么多吧,注解下次再写了。写东西真麻烦啊,越写越多,越写越多,我都没想到报文的内容写了这么久。关键我都写烦了,还觉得自己没写清楚呢。真的是觉得把一个东西写清楚是需要功力的,我还没有功力啊。。。我就不瞎感慨了,早点回去洗衣服了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容