Yanzhishang / OkSimple

OkSimple :更好用的网络请求框架

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

断点续传


download


glide进度监听


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了。

关于demo

所有get,post,postjson,文件上传下载等方法,都是经过测试可行的。但有些方法,不方便写测试用例,便写的随便了些,所以demo中的很多测试类是无法直接运行的,请结合自身项目进行测试。使用过程中有问题的可以先参考demo里的写法,或者提issue给我

使用方法

1.全局初始化

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时,则不会防止重复请求

当开关开启的时候,是可以有效防止诸如用户狂点按钮,导致短时间发送多个请求的情况发生的

2.ResultCallBack

在开始介绍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的父类
3.DataCallBack

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里的所有请求,在发起的时候,都可以带上这几个参数,后面的请求,我就不再重复介绍了。
4.DataCallBack文件上传

通常文件上传之后,服务器端也会返回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)
            }

        })
5.FileResultCallBack文件下载
 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)这句代码即可。

6.GlideCallBack进行glide图片加载进度监听

如果你看源代码的话,你会发现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_progressactivity_glide_test请点击查看 。详细的代码都在demo里,我在这里说一下实现glide进度监听的原理。要实现glide进度监听,首先就是要在AppGlideModule里调用Registry.replace,把glide原来使用的httpurlconnection替换为okhttp,在替换的时候,需要传入一个callback统一接收所有url的回调,这里我继承我的GlideCallBack,在回调的时候通过url进行区分。剩下的就很简单了,每次glide 加载前,传入一个url和listener到MyGlideCallBack中,这样,就完成了glide的进度监听。至于具体使用过程中,如何封装,我的demo也仅供参考。

About

OkSimple :更好用的网络请求框架


Languages

Language:Kotlin 92.6%Language:Java 7.4%