基於Retrofit2實現的LycheeHttp

VeCharm發表於2019-03-19

寫這個庫的目的是為了能夠讓程式碼看起來簡約,優雅。本庫是使用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

後話:第一次寫文章,寫的頭暈腦漲,寫的不太好。如果這篇文章對各位大大有用的話,可以給我點個贊鼓勵一下我哦,感謝!

相關文章