前言
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) 没想到就是封装的,仔细看name和age是全局变量,但是他们是private的,这就意味着name和age是不能直接被访问到的,但是我们发现有get和set的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
如果我们得对传入的name和age进行一些解析修改,再设置,那么这时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)
文末
感谢大家看到这里,这是我最近两天在做的事情,如果发现内容有问题欢迎指出。也是为了给自己加深印象,今后还会继续了解封装,并且更多的去使用封装,如果你有更好的想法也可以告诉我。
发表回复