imcys.com
遵从中二的召唤,来吧少年!

okHttp简单封装与优雅的Gson解析

前言

okHttp可能大家会去经常使用,但对于一个像我这样的学习者来说,可能很少去封装它。其实我对封装这个概念是很模糊的,换句话说我并不清楚什么是封装。但是我现在想要去封装okHttp ,却不太明白如何封装,于是在网上找了许多类似的文章。实际上这些文章对初学者不太友好,有时候是明白写的东西,却不知道如何使用,所以,我打算结合自己的认知写一篇笔记文章。

封装

在开始封装okHttp 前,我们得先明白什么才是封装,于是我去网上搜了一搜。

原来一直在使用封装?

我去runoob看了看介绍,实际上还是比较模糊的,他的描述可能不太容易懂,于是我继续向下看。

【这里的内容和封装okHttp 关系不是特别大,如果着急,你可以跳过】

runoob

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
public class Person{
    private String name;
    private int age;
​
    public int getAge(){
      return age;
    }
​
    public String getName(){
      return name;
    }
​
    public void setAge(int age){
      this.age = age;
    }
​
    public void setName(String name){
      this.name = name;
    }
}

???绝了,我们常用的 Bean(Model) 没想到就是封装的,仔细看nameage是全局变量,但是他们是private的,这就意味着nameage是不能直接被访问到的,但是我们发现有get和set的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

如果我们得对传入的nameage进行一些解析修改,再设置,那么这时set方法显得比较有用了,如果直接对name传入原始数据就会出问题,但是我们使用构造方法或者set,在设置变量时增加解析代码,这样就可以直接使用set传入了。那么如果name不能被访问,而是必须使用get/set,这样我们就可以保护我们的代码不出问题,而且也不需要改调用代码,直接使用set和get即可。

封装(重构)okHttp

我们先来想一个问题,为什么要封装它?显然我们更多时候是为了调用方便,这个时候是不是和上面封装的意义不同了?是的,在这里我们封装okHttp是为了更方便的使用它,如果我们正常去使用okhttp要写多少东西?

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://wwww.baidu.com").build();
//同步请求
Response syncResponse = client.newCall(request).execute();
//异步请求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) { }
    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { }
});

正常异步/同步get请求用法,要写许多的东西,每次写请求比较麻烦对吧,而且还有许多重复出现的,比如OkHttpClient和newCall是可以复用的,为了实现这个复用,我们就需要封装一下,方便下次使用。

仔细看代码,如果是个简单的get请求,我们只需要传入一个url和一个实现的Callback,说干就干,我们把这些代码抽离出来,单独组成一个工具类HttpUtils 我们先来看看效果。

class HttpUtils {
    private val TAG = HttpUtils::class.java.simpleName
    /**
     * get请求执行方法
     * @param url String 请求地址
     * @param callBack Callback
     */
    fun get1(url: String, callBack: Callback) {

        val okHttpClient = OkHttpClient()
        val request: Request = Request.Builder()
            .url(url)
            .get()
            .build()
        okHttpClient.newCall(request).enqueue(callBack)
    }
}

接下来我们在活动里使用这个封装

        HttpUtils()
            .get("https://mzh.misakamoe.com/api/v1/OperationLogService/operationLogs?type=3&page=1&num=10",
                object : Callback {
                    override fun onFailure(call: Call, e: IOException) {
                    }

                    override fun onResponse(call: Call, response: Response) {
                    }

                }
            )

怎么样?是不是很简洁了?但是仅仅是这样还不行。

添加Param和Headers

我们请求中会需要设置一些Headers和post的参数,但是为了方便,我们需要尽可能减少代码量,因此,我们需要模仿一下okhttp的设置方式。

我们分别设置两个Map,为params和headers,并且新增两个方法,向这两个Map添加数据,这里需要注意的是,返回必须是HttpUtils 这样我们才能做出HttpUtils().addHeader(xxx).addParam(xxxx).post(xxx,xxx)这样的效果。

    /**
     * 添加post的form参数
     * @param key String
     * @param value String
     * @return HttpUtils
     */
    fun addParam(key: String, value: String): HttpUtils {
        params[key] = value
        return this
    }


    /**
     * 添加请求头
     * @param key String
     * @param value String
     * @return HttpUtils
     */
    fun addHeader(key: String, value: String): HttpUtils {
        headers[key] = value
        return this
    }

OK解决了这两个问题,基本上封装就完成了,接下来我们补全基本用法,比如get,post和post提交json,文件这样的,下面我提供一个我写好的类(没有写完整)

/**
 * @author imcys
 *
 * 此类为okhttp3的封装类
 */
class HttpUtils {
    private val TAG = HttpUtils::class.java.simpleName
    private var params = mutableMapOf<String, String>()
    private var headers = mutableMapOf<String, String>()



    /**
     * get请求执行方法
     * @param url String 请求地址
     * @param callBack Callback
     */
    fun get(url: String, callBack: Callback) {

        val okHttpClient = OkHttpClient()
        val request: Request = Request.Builder().apply {
            headers.forEach {
                addHeader(it.key, it.value)
            }
            url(url)
            get()
        }.build()
        okHttpClient.newCall(request).enqueue(callBack)
    }


    /**
     * post请求类
     * @param url String 请求地址
     * @param callBack Callback
     */
    @SuppressLint("NewApi")
    fun post(url: String, callBack: Callback) {
        val okHttpClient = OkHttpClient()

        //构建FormBody
        val formBody: FormBody.Builder = FormBody.Builder()
        //添加params参数
        params.forEach(formBody::add)
        //构建request并且添加headers
        val request: Request = Request.Builder()
            .apply {
                //设置请求头
                headers.forEach {
                    addHeader(it.key, it.value)
                }
                //设置请求地址和参数
                url(url)
                post(formBody.build())
            }.build()
        okHttpClient.newCall(request).enqueue(callBack)
    }


    /**
     * post提交Json
     * @param url String 请求地址
     * @param jsonString String 请求json
     * @param callBack Callback
     */
    fun postJson(url: String, jsonString: String, callBack: Callback) {
        val okHttpClient = OkHttpClient()
        val stringBody = jsonString.toRequestBody("application/json;charset=utf-8".toMediaType())
        val request: Request = Request.Builder().apply {
            headers.forEach {
                addHeader(it.key, it.value)
            }
            //设置请求地址和参数
            url(url)
            post(stringBody)
        }.build()

        okHttpClient.newCall(request).enqueue(callBack)

    }


    /**
     * 添加post的form参数
     * @param key String
     * @param value String
     * @return HttpUtils
     */
    fun addParam(key: String, value: String): HttpUtils {
        params[key] = value
        return this
    }


    /**
     * 添加请求头
     * @param key String
     * @param value String
     * @return HttpUtils
     */
    fun addHeader(key: String, value: String): HttpUtils {
        headers[key] = value
        return this
    }


}

最后我们来看看用法,这段代码是写在需要调用请求的类里的,返回的是B站的一个粉丝情况。

HttpUtils()
            .addHeader(
                "cookie",
                "xxxxx"
            )
            .get(
                "https://api.bilibili.com/x/web-interface/nav/stat",
                object : Callback {
                    override fun onFailure(call: Call, e: IOException) {
                       //请求失败
                    }

                    override fun onResponse(call: Call, response: Response) {
                        //请求成功
                    }

                }
            )

Gson优雅解析Json

谈完上面的请求问题,我们来说说怎么处理响应,先来看看我之前的。

val blackRoomJson = JSONObject(response.body()!!.string())
var blackRoomData = blackRoomJson.optJSONObject("data")
val blackRoomArray = blackRoomData.optJSONArray("operationLogs")

比如请求结果拿到了,那么我就会去使用自带的一个fastJson来解析,但是这样写的我是有一点点难受的,每次我需要写一大堆的解析,所以这次我换用了Gson,这个GitHub有,我就不放地址了。

使用Gson和fastJson差不多,我们需要有一个Bean类,但是这个类中的变量名称得和接口返回的对应起来,否则会出问题。

我使用了这个接口,并且生成了实体类,但实际上这不是我手写的,也不是我写好变量名用as生成的。

我们使用一个辅助插件 GsonFormat ,按住alt+s就可以显示对话框,直接将返回的json输入进去,就可以帮你完成这一操作。彻底解放,不用再敲。

Gson搭配okHttp

我们直接在onResponse方法中使用Gson,并且随便输出一个值。

        HttpUtils()
            .get("https://mzh.misakamoe.com/api/v1/AdminService/admins?page=1&num=10",
                object : Callback {
                    override fun onFailure(call: Call, e: IOException) {
                        TODO("Not yet implemented")
                    }

                    override fun onResponse(call: Call, response: Response) {
                        val disciplineAdmin: DisciplineAdmin =
                            Gson().fromJson(response.body!!.string(), DisciplineAdmin::class.java)
                        Log.e("Gson", disciplineAdmin.data.admins[0].adminName)
                    }

                }
            )

显然我们成功了!

并且不难发现,下面这段代码就是将Json字符串解析为实体类的功能。

val disciplineAdmin: DisciplineAdmin =
                            Gson().fromJson(response.body!!.string(), DisciplineAdmin::class.java)

文末

感谢大家看到这里,这是我最近两天在做的事情,如果发现内容有问题欢迎指出。也是为了给自己加深印象,今后还会继续了解封装,并且更多的去使用封装,如果你有更好的想法也可以告诉我。

萌新杰少

文章作者

I im CYS,一个热爱二次元的大专开发者

发表回复

textsms
account_circle
email

Captcha Code

萌新杰少の秘密基地

okHttp简单封装与优雅的Gson解析
前言 okHttp可能大家会去经常使用,但对于一个像我这样的学习者来说,可能很少去封装它。其实我对封装这个概念是很模糊的,换句话说我并不清楚什么是封装。但是我现在想要去封装okHttp…
扫描二维码继续阅读
2022-10-14