HTTP报文及Retrofit基本使用

2019-05-23  本文已影响0人  iSuperRed

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 总结

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

上一篇下一篇

猜你喜欢

热点阅读