阅读 35

基于Retrofit2实现的LycheeHttp

写这个库的目的是为了能够让代码看起来简约,优雅。本库是使用Kotlin实现的,所以它只支持Kotlin,同时集成了上传,下载,断点续传,添加通用参数,和参数签名等功能,节省开发时间。

github地址:github.com/VipMinF/Lyc…

本库其他相关文章

框架引入

dependencies {
    implementation 'com.vecharm:lycheehttp:1.0.2'
}
复制代码

如果你喜欢用RxJava 还需要加入

dependencies {
     //RxJava
     implementation 'com.vecharm.lycheehttp:lychee_rxjava:1.0.2'
    //或者 RxJava2
     implementation 'com.vecharm.lycheehttp:lychee_rxjava2:1.0.2'
}
复制代码

初始化

在Application中调用LycheeHttp::ini,如果你不喜欢这种方式,自己写个单例,调用时才初始化也是可以的,比较简单我就不写了。

    override fun onCreate() {
        super.onCreate()
        LycheeHttp.init(MyCoreConfig(this))
    }
复制代码

下载的API定义

下载只需要使用 Download 注解API就可以啦

    @Download
    @GET("https://xxxx/xxxx.apk")
    fun download(): Call<DownloadBean>
复制代码

上传的API定义

  1. 根据文件名称的后缀名获取,使用Upload 进行注解
    @Upload
    @Multipart
    @POST("http://xxx/xxx")
    fun upload(@Part("file") file: File): Call<ResultBean<UploadResult>>
复制代码
  1. 对某个file进行注解,使用FileType("png") 或者FileType("image/png")
    @Multipart
    @POST("http:/xxx/xxx")
    fun upload(@Part("file") @FileType("png") file: File): Call<ResultBean<UploadResult>>
复制代码
  1. 对整个方法的所有file参数进行注解,使用MultiFileType("png")或者MultiFileType("image/png")
    @Multipart
    @MultiFileType("png")
    @POST("http://xxx/xxx")
    fun upload(@PartMap map: MutableMap<String, Any>): Call<ResultBean<UploadResult>>
复制代码

API的定义和原来没有什么区别,只是多了几个注解,上传再也不用RequestBody作为参数了。如果要打印文件的内容,可以使用FileLog,一般这种需求比较少。

使用

      //普通请求
      getService<API>().hello().request {
          onSuccess = { Toast.makeText(App.app, it.data ?: "", Toast.LENGTH_SHORT).show() }
          onErrorMessage = {}
          onCompleted = {}
      }

      //单个文件下载
      getService<API>().download().request(File(App.app.externalCacheDir, "xx.apk")) {
          onSuccess = { Toast.makeText(App.app, "${it.downloadInfo?.fileName} 下载完成", Toast.LENGTH_SHORT).show() }
          onErrorMessage = {}
          onCompleted = {}
      }
        
      //多任务下载
      addDownloadTaskButton.setOnClickListener {
          val downloadTask = DownloadTask()
          val file = File(App.app.externalCacheDir, "qq${adapter.data.size + 1}.apk"
          downloadTask.download("https://xxx/xxx.apk", file)
          adapter.addData(downloadTask)
      }
        
      //多任务上传
      addUploadTaskButton.setOnClickListener {
          val uploadTask = UploadTask()
          uploadTask.upload(File(App.app.externalCacheDir, "qq${adapter.data.size + 1}.apk"))
          adapter.addData(uploadTask)
      }
        
复制代码

三个注解可以同时使用,优先级FileType > MultiFileType > Upload,喜欢哪一种就看你自己了

注意

对于多任务上传和下载,由于每个业务的API定义可能不一样,所以UploadTaskDownloadTask需要自己实现,下面是两个例子(Rxjava2版的),也可以完全自己写。

//上传
class UploadTask : DefaultTask() {
    override fun onCancel() {}
    override fun onResume(url: String, filePath: String) {}
    
    fun upload(file: File) {
        getService<API>().upload(file).upload {
            onUpdateProgress = onUpdate
            onSuccess = { Toast.makeText(App.app, "${id}上传完成", Toast.LENGTH_LONG).show() }
        }
    }
}
//下载
class DownloadTask : DefaultTask() {

    override fun onCancel() {
        service?.dispose()
    }

    override fun onResume(url: String, filePath: String) {
        download(url, File(filePath))
    }

    var service: Disposable? = null

    fun download(url: String, saveFile: File) {
        setPathInfo(url, saveFile.absolutePath)

        service = getService<API>().download(url, range.bytesRange()).request(saveFile.setRange(range)) {
            onUpdateProgress = onUpdate
            onSuccess = { Toast.makeText(App.app, "${id}下载完成", Toast.LENGTH_LONG).show() }
        }
    }
}
复制代码

另外注意如果你倾向于使用RxJava,别忘了添加RxJavaCallAdapterFactory或者你的自定义的实现

class RxJavaConfig(val context: Application) : DefaultCoreConfig() {
    ......
    override fun onInitRetrofit(builder: Retrofit.Builder): Retrofit.Builder {
        builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        return super.onInitRetrofit(builder)
    }
}
复制代码

最简配置

class SimplestConfig:DefaultCoreConfig() {
    override fun getHostString() = "https://host:port/"
}
复制代码

其他配置

如果你不喜欢默认的配置,可以实现ICoreConfig接口自己写一个,下面的我都是基于默认配置Defaultxxxx来讲。

  1. 如果要控制日志的打印,可以override DefaultCoreConfig::isShowLog
  2. 如果不喜欢Gson,可以override DefaultCoreConfig::getGsonConverterFactory
  3. 如果要加入CookieJar,可以override DefaultCoreConfig::getCookieJar,可以试试PersistentCookieJar
  4. 如果不喜欢默认的ResponseBean 或者说项目比较复杂,一些接口返回ResultBean,一些接口返回OKBean,那么可以实现IResponseHandler接口。并在api执行之前调用DefaultCoreConfig:registerResponseHandler
class SimplestConfig : DefaultCoreConfig() {
    init {
        /*
        * 注册自定义的返回值处理,可以注册多个
        * */
        registerResponseHandler(ResponseBean::class.java, MyResponseHandler::class.java)
    }
    .......
}
class MyResponseHandler : DefaultResponseHandler() {

    override fun onError(status: Int, message: String?) {
        if (10001 == status) {//没有登陆
            Toast.makeText(App.app, "没有登陆", Toast.LENGTH_LONG).show()
        }
        super.onError(status, message)
        Toast.makeText(App.app, "$status:$message", Toast.LENGTH_LONG).show()
    }
}
复制代码
  1. 如果有添加通用参数和通用头部的需求,可以override DefaultCoreConfig::getRequestConfig,然后继承DefaultRequestConfig,下面是例子。
class MyRequestConfig : DefaultRequestConfig() {

    /**
     * 添加默认头部参数
     * */
    override fun addHeaders(newRequestBuild: Request.Builder, oldRequest: Request) {
        newRequestBuild.addHeader("Accept", "application/json")
        newRequestBuild.addHeader("Accept-Language", "zh")
    }
    
    /**
     * 添加通用参数
     * */
    override fun onAddCommonParams(map: MutableMap<String, String>) {
        map["app_version"] = com.vecharm.lychee.sample.BuildConfig.VERSION_CODE.toString()
        map["nonce"] = map["nonce"] ?: randomUUID()
        map["timestamp"] = System.currentTimeMillis().div(1000).toString()
        map["pkg_name"] = App.app.packageName
        map["app_sign"] = "s9fkjs0a-d234ew-adfadf"
    }
    
    /**
     * 参数签名
     * */
    override fun onSignParams(map: MutableMap<String, String>) {
        super.onSignParams(map)
        //签完名将key移除,避免将这个传到服务器
        map.remove("app_sign")
    }
    
    /**
     * 不需要参与签名的字段,文件参与签名默认是进行md5
     * */
    override fun unSignParamNames() = arrayOf("file")

    /**
     * 是否参数签名
     * */
    override fun isSignParam() = true
}
复制代码
  1. 上传时,自动匹配的MediaType列表,放在DefaultMediaTypeManager,目前里面收集了300个左右,如果不够用,可以继承DefaultMediaTypeManagertypes添加,或者override DefaultMediaTypeManager::filter
  2. 如果不喜欢默认的下载或上传的进度的计算方式,可以实现ISpeedComputer,在初始化时替换ProgressHelper::downloadSpeedComputerProgressHelper::uploadSpeedComputer

后话:第一次写文章,写的头晕脑涨,写的不太好。如果这篇文章对各位大大有用的话,可以给我点个赞鼓励一下我哦,感谢!

关注下面的标签,发现更多相似文章
评论