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

发表回复