OKsimple:一个对okhttp进行二次封装的网络请求库,相比retrofit更简单易用,扩展性强,基于okhttp4.X版本和kotlin。目前的大部分网络请求框架都是用java写的,而且对okhttp的支持也只支持到okhttp3.x。但oksimple基于okhttp4.X和谷歌官方钦定android开发语言kotlin。所以说面向未来也没什么不对。将来也会持续更新okhttp的版本,在保证兼容性的前提下和官方保持同步。目前更新到okttp4.2.2。
- get,post,postjson等常规请求
- 文件下载,支持进度监听,支持断点续传
- 文件上传,支持进度监听
- 表单提交,多文件上传以及进度监听
- 支持接入glide实现glide图片加载进度监听
- 全局请求头和全局参数的添加
- 每次请求默认添加tag,支持自定义tag
- 每次请求支持自定义CacheControl
- 对短时间多次相同的请求进行了拦截处理,有效防止用户多次点击导致的重复请求
- 基于kotlin但也对java做了支持
- 同步okhttp最新版本,支持brotli compression
断点续传
glide进度监听
基于okhttp的二次封装库。设计之初有参考okgo,但比okgo更加简单易用。源码量相对okgo也少很多,但okgo实现的功能,Oksimple也能实现。因为之前的服务器用不了了,所以sample里没法使用真实服务器地址,只能写一些伪代码,但大部分功能是经过测试的。觉得好用的话,给个star吧
在根节点的build.gradle
allprojects {
repositories {
google()
jcenter()
maven {
url "https://dl.bintray.com/gateoftruth/public"
}
}
}
然后在app的build.gradle
implementation 'com.alen.simpleutil:OkSimple:1.1.0'
或者你也可以fork一下,自己编译,oksimple只依赖了okhttp,没有其他依赖,编译出来的aar包也只有30几KB,同时,Oksimple采用api引入okhttp,所以你引入了oksimple的话,就不用重新引入okhttp了。
所有get,post,postjson,文件上传下载等方法,都是经过测试可行的。但有些方法,不方便写测试用例,便写的随便了些,所以demo中的很多测试类是无法直接运行的,请结合自身项目进行测试。使用过程中有问题的可以先参考demo里的写法,或者提issue给我
oksimple 没有对okhttpclient进行任何的封装方法,基本上okhttpclient提供了接口的方法,我都没有进行二次封装,因为感觉没有必要,我只创建了一个默认的对象,就像这样
var okHttpClient = OkHttpClient()
所以如果你对okhttpclient有什么特殊处理,诸如connectTimeout,protocols,拦截器等,因为oksimple是基于饿汉模式的全局单例模式,建议在application中对其进行初始化,就像这样
OkSimple.okHttpClient=OkHttpClient.Builder().addInterceptor(logInterceptor)
.connectTimeout(100,TimeUnit.SECONDS)
.writeTimeout(100,TimeUnit.SECONDS)
.readTimeout(100,TimeUnit.SECONDS)
.protocols(listOf(Protocol.HTTP_1_1))
.build()
关于缓存,okhttp框架本身就对缓存做了相应处理,二次封装感觉实在没有必要。对缓存存在疑问的,可以看一下我翻译的这篇文章
全局参数的话,诸如各种渠道参数等,或者全局的header,诸如token等,可以分别调用
OkSimple.addGlobalParams("key","value")
和
OkSimple.addGlobalHeader("key","value")
你可以在任何地方调用这两个方法,都会生效,如果你想对这些参数进行一些操作,那么直接获取OkSimple.globalHeaderMap和OkSimple.globalParamsMap即可。
同时,Oksimple默认会防止重复请求,也就是当你向服务器发出一个请求的时候,如果服务器没有返回结果,无论这个结果是成功,失败或者超时,都会对之后的同一个请求进行拦截,之后的请求,不会发送到服务器,直到当前请求有了返回。而判断是否是同一个请求,是根据URL来进行判断的。防止重复请求的开关是
OkSimple.preventContinuousRequests=true//默认为true,当为false时,则不会防止重复请求
当开关开启的时候,是可以有效防止诸如用户狂点按钮,导致短时间发送多个请求的情况发生的
在开始介绍get,post等请求前,需要先介绍一下ResultCallBack。在Oksimple中,所有的请求回调结果,都是基于ResultCallBack这个抽象类,这个抽象类定义了一系列的抽象方法,并且继承了BaseProgressListener接口实现上传和下载进度监听。不同的请求,需要回调不同的callback。
abstract class ResultCallBack : BaseProgressListener {
/**
* 这些参数是文件下载用的
*/
var filename = ""
var filePath = ""
var downloadLength=0L
var contentLength=0L
/**
* 开始网络请求之前调用,在主线程回调,可以用来弹出dialog等
*/
abstract fun start()
/**
* 和okhttp3.Callback.onResponse(call: Call, response: Response) 同步回调,用于获取请求的结果
*/
abstract fun response(call: Call, response: Response)
/**
* 和okhttp3.Callback.onFailure(call: Call, e: IOException) 同步回调,用于接受异常,在主线程回调
*/
abstract fun failure(call: Call, e: Exception)
/**
* 当responseBody 为空时回调
*/
abstract fun responseBodyGetNull(call: Call, response: Response)
/**
* 捕获诸如gson解析抛出的JsonSyntaxException之类的异常
*/
abstract fun otherException(call: Call, response: Response, e: Exception)
}
而BaseProgressListener则没什么好说的了
interface BaseProgressListener {
fun downloadProgress(url:String,total: Long, current: Long)
fun downloadProgressOnMainThread(url:String,total: Long, current: Long)
fun uploadProgress(fileName: String, total: Long, current: Long)
fun uploadProgressOnMainThread(fileName: String, total: Long, current: Long)
}
因为我的下载和上传进度是参照okhttp的官方sample来写的,同时也是为了能接入glide监听图片的加载进度,所以默认的上传和下载的进度回调都是在子线程回调,但我们一般展示进度是在主线程,所以我又新增了两个主线程回调的方法。 目前内置了四个callback,涵盖了常用的大部分需求,这四个callback都继承自ResultCallBack,当然,你也可以根据需求重写
- DataCallBack,用于常get,post,文件上传等,需要获取服务器端返回json的都可以重写这个
- FileResultCallBack,用于文件下载,支持断点续传
- GlideCallBack,接入glide,获取glide加载进度
- BitmapResultCallBack,获取bitmap,支持下载进度,通常情况下用不到,因为一般不会直接下载图片,都是接入第三方框架,作为GlideCallBack的父类
DataCallBack继承自ResultCallBack并新增了三个方法
/**
*对传入responseBody.string()进行预处理,例如不想实体类太复杂的话,可以在这里使用JSONObject等对服务器返回的json进行成功或者失败判断的预处理
*/
open fun preProcessBodyString(bodyString:String):String
/**
* 需自己根据需要进行重写,传入preProcessBodyString()返回的数据,返回泛型参数。
*/
abstract fun stringToData(string:String):E
/**
* 最终返回结果,在主线程回调。
*/
abstract fun getData(data: E, rawBodyString: String, call: Call, response: Response)
这里我以gson为例子,重写了datacalllback,那么就会是这样
abstract class GsonCallBack<E> :DataCallBack<E>() {
private val gson=Gson()
override fun stringToData(string: String): E {
val type=(javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
return gson.fromJson(string,type)
}
}
重写的意义在于把服务器端返回的json数据,转化为泛型定义的实体类,这里你可以使用gson,fastjson。也可以不像我一样使用getGenericSuperclass方法,而改为直接传入一个class类,都是可以的,看自己的喜好。
以上面的GsonCallBack为例子,实际使用的话,get请求的kotlin版本可以这么写:(post和postjson请求同理,就不多介绍,具体可以参考demo)
OkSimple.get(url).apply {
tag=xxx
requestCacheControl= CacheControl.FORCE_CACHE
params(key,value)
addHeader(key,value)
}.execute(object :GsonCallBack<T>(){
override fun start() {
dialog.show()//重写start(),进行诸如弹出dialog的操作
}
override fun getData(
data: T,
rawBodyString: String,
call: Call,
response: Response
) {
dialog.dismiss()
}
override fun failure(call: Call, e: Exception) {
dialog.dismiss()
e.printStackTrace()
}
})
java的话可以这么写:
OkSimple.INSTANCE.get(url).setTheTag(xxx).addHeader(key,value).setTheRequestCacheControl(CacheControl.FORCE_CACHE).params(key,value).execute(new GsonCallBack<T>() {
@Override
public void failure(@NotNull Call call, @NotNull Exception e) {
}
@Override
public void getData(T s, @NotNull String rawBodyString, @NotNull Call call, @NotNull Response response) {
}
});
怎么写的话看个人喜好,这里说一下几个链式调用的方法,addHeader我想就不用多说了,requestCacheControl是okhttp.Request类自带的一个方法,用于支持缓存控制,具体可以参考okhttp的官方文档,而okhttp的全局缓存策略的话,可以在初始化的时候通过自定义okhttpclient传入。tag的话,默认是url,用于支持取消请求。params的话,默认会拼接在url之后,post请求也可以调用。OkSimple里的所有请求,在发起的时候,都可以带上这几个参数,后面的请求,我就不再重复介绍了。
通常文件上传之后,服务器端也会返回json,告诉你上传成功还是失败。所以这里依然是使用DataCallBack。为了方便介绍,我这里还是以GsonCallBack为例子。
OkSimple.postForm(url).addFormPart(key,file,mediaTypeString).addFormPart(key,value).execute(object :GsonCallBack<T>(){
override fun getData(
data: T,
rawBodyString: String,
call: Call,
response: Response
) {
}
override fun failure(call: Call, e: Exception) {
e.printStackTrace()
}
override fun uploadProgressOnMainThread(fileName: String, total: Long, current: Long) {
//更新progresbar等ui控件在这里更新
}
override fun uploadProgress(fileName: String, total: Long, current: Long) {
super.uploadProgress(fileName, total, current)
}
})
通常情况下来说,表单提交的文件上传这样就可以了。Oksimple在使用postForm进行文件上传的时候,是以表单形式提交,同时也支持一个key一个file和一个key多个file。这里先说一下mediaTypeString这个参数,这个参数是string类型,不是必传的,是可选参数,默认值是application/octet-stream,但比如上传图片的时候,有的服务器不认application/octet-stream,只认"image/jpg",那么这个时候,那么你就把服务器需要的mediatype传过去就行了。然后是fun uploadProgress(fileName: String, total: Long, current: Long)这个方法,因为我重写了RequestBody,所以uploadProgress默认会在子线程被okhttp回调,并且这个方法如果你点进去,看到父类实现的话,是这样实现的
fun uploadProgress(fileName: String, total: Long, current: Long) {
OkSimple.mainHandler.post {
uploadProgressOnMainThread(fileName, total, current)
}
}
所以,如果你想在自己的handler处理子线程和主线程的通讯,那么你可以把super.uploadProgress(fileName, total, current)这句删掉,同时uploadProgressOnMainThread也就不会被回调了。还有就是有的服务器上传单个文件不是使用表单形式,是使用post形式,没有key值,那么你可以这么写
OkSimple.uploadFile(url,file,mediaTypeString).execute(object :GsonCallBack<T>(){
override fun getData(
data: T,
rawBodyString: String,
call: Call,
response: Response
) {
}
override fun failure(call: Call, e: Exception) {
}
override fun downloadProgressOnMainThread(url: String, total: Long, current: Long) {
}
override fun downloadProgress(url: String, total: Long, current: Long) {
super.downloadProgress(url, total, current)
}
})
OkSimple.downloadFile(url,filename,filepath).execute(object :FileResultCallBack(){
override fun downloadProgressOnMainThread(url: String, total: Long, current: Long) {
}
override fun downloadProgress(url: String, total: Long, current: Long) {
super.downloadProgress(url, total, current)
}
override fun failure(call: Call, e: Exception) {
}
override fun finish(file: File) {
}
})
通过如上代码,便可完成文件下载,下载完的文件,会在finish()方法里回调,finish是FileResultCallBack继承ResultCallBack后新增的方法,用于获取下载完成的文件。Oksimple默认支持断点续传,假如你的服务器不支持断点续传,也可照常下载。如果你不想断点续传,想重新下载,请在下载前把存在的文件删除。在okhttp的逻辑里,是没有断点续传的概念的,只有通过tag取消一个请求,然后再次请求的概念。oksimple默认给每一个请求一个和url一样的tag,也可以自定义tag,具体可以参考demo。downloadProgressOnMainThread和downloadProgress与之前文件上传的uploadProgressOnMainThread和uploadProgress同理。因为我重写了ResponseBody,所以okhttp默认会在子线程回调downloadProgress,如果你想自己控制主线程子线程的切换,同样可以删掉 super.downloadProgress(url, total, current)这句代码即可。
如果你看源代码的话,你会发现GlideCallBack是一个空方法。这是因为Oksimple本质是一个网络请求的框架, 不会考虑引入glide等其他库,所以我提供的只是接入glide的能力。因此我把它放在了demo里而不是lib里,如果你想实现demo里那样的效果的话,我会提供一个很简单的思路。当然你也可以直接去看源码。 首先你需要引入glide相关的库
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.github.bumptech.glide:annotations:4.9.0'
kapt 'com.github.bumptech.glide:compiler:4.9.0'//kotlin 使用kapt
implementation 'com.github.bumptech.glide:okhttp3-integration:4.9.0'//这是glide对okhttp的支持库,必须要引入
之后创建一个类,继承自GlideCallBack,类似这样的
class MyGlideCallBack: GlideCallBack() {
val urlProgressListener= hashMapOf<String,ImageProgress>()
companion object{
val instance=MyGlideCallBack()
}
override fun downloadProgress(url: String, total: Long, current: Long) {
OkSimple.mainHandler.post {
downloadProgressOnMainThread(url, total, current)
}
}
override fun downloadProgressOnMainThread(url: String, total: Long, current: Long) {
urlProgressListener[url]?.downloadProgress( total, current)
if (total==current){
urlProgressListener.remove(url)
}
}
}
然后创建glidemodule
@GlideModule
class GlideModule :AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
registry.replace(GlideUrl::class.java,InputStream::class.java,OkHttpUrlLoader.Factory(OkSimple.getGlideClient(MyGlideCallBack.instance)))
}
}
这里需要注意的是,当你调用 registry.replace(GlideUrl::class.java,InputStream::class.java,OkHttpUrlLoader.Factory(OkSimple.getGlideClient(MyGlideCallBack.instance)))方法的时候,你对okhttpclient进行的诸如readTimeout()或者 protocols()等初始化会在glide通过okhttp进行访问的时候保留下来。因为都是同一个okhttpclient。但header,globeheader和globeparams等不会进行保留。因为glide并没有使用我的request。
之后创建一个自定义viewgroup
class GlideProgressImageView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
FrameLayout(context, attrs, defStyleAttr) {
constructor(context: Context,attrs: AttributeSet?):this(context,attrs,0)
constructor(context: Context):this(context,null,0)
private val baseView:View = LayoutInflater.from(context).inflate(R.layout.view_glide_progress,this,true)
private val imageView=findViewById<ImageView>(R.id.iv_for_glide)
private val progress=findViewById<ProgressBar>(R.id.progress_glide)
fun<T> into(url:String,requestBuilder:RequestBuilder<T>){
requestBuilder.into(imageView)
MyGlideCallBack.instance.urlProgressListener[url]=object :ImageProgress{
override fun downloadProgress(total: Long, current: Long) {
val totalDouble=total.toDouble()
var percent=current/totalDouble
percent*=100
progress.progress=percent.toInt()
}
}
}
}
使用的时候这样使用
class GlideTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_glide_test)
val url="https://images.pexels.com/photos/1837591/pexels-photo-1837591.jpeg?cs=srgb&dl=architecture-big-ben-bird-s-eye-view-1837591.jpg&fm=jpg"
val url2="https://images.pexels.com/photos/1414050/pexels-photo-1414050.jpeg?cs=srgb&dl=architecture-big-wheel-buoy-1414050.jpg&fm=jpg"
val options=RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE)
glideProgressImageView.into(url,GlideApp.with(this).load(url).apply(options))
glideProgressImageView2.into(url2,GlideApp.with(this).load(url2).apply(options))
}
}
view_glide_progress和activity_glide_test请点击查看 。详细的代码都在demo里,我在这里说一下实现glide进度监听的原理。要实现glide进度监听,首先就是要在AppGlideModule里调用Registry.replace,把glide原来使用的httpurlconnection替换为okhttp,在替换的时候,需要传入一个callback统一接收所有url的回调,这里我继承我的GlideCallBack,在回调的时候通过url进行区分。剩下的就很简单了,每次glide 加载前,传入一个url和listener到MyGlideCallBack中,这样,就完成了glide的进度监听。至于具体使用过程中,如何封装,我的demo也仅供参考。