RxHttp 2000+star,协程请求,仅需三步

RxHttp 让你眼前一亮的Http请求框架

gradle依赖

//使用kapt依赖rxhttp-compiler,需要导入kapt插件
 apply plugin: ‘kotlin-kapt’android {
 defaultConfig {
 javaCompileOptions {
 annotationProcessorOptions {
 arguments = [
 //必须,告知RxHttp你依赖的okhttp版本,目前已适配 v3.12.0 - v4.9.0版本 (v4.3.0除外)
 rxhttp_okhttp: ‘4.9.0’,
 //使用asXxx方法时必须,告知RxHttp你依赖的rxjava版本,可传入rxjava2、rxjava3
 rxhttp_rxjava: ‘rxjava3’,
 rxhttp_package: ‘rxhttp’ //非必须,指定RxHttp类包名
 ]
 }
 }
 }
 //必须,java 8或更高
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_8
 targetCompatibility JavaVersion.VERSION_1_8
 }
 }
 dependencies {
 //以下3个为必须,
 implementation ‘com.ljx.rxhttp:rxhttp:2.4.1’
 implementation ‘com.squareup.okhttp3:okhttp:4.9.0’ //rxhttp v2.2.2版本起,需要手动依赖okhttp
 kapt ‘com.ljx.rxhttp:rxhttp-compiler:2.4.1’ //生成RxHttp类,纯Java项目,请使用annotationProcessor代替kaptimplementation ‘com.ljx.rxlife:rxlife-coroutine:2.0.1’ //管理协程生命周期,页面销毁,关闭请求
//rxjava2 (RxJava2/Rxjava3二选一,使用asXxx方法时必须)
 implementation ‘io.reactivex.rxjava2:rxjava:2.2.8’
 implementation ‘io.reactivex.rxjava2:rxandroid:2.1.1’
 implementation ‘com.ljx.rxlife2:rxlife-rxjava:2.0.0’ //管理RxJava2生命周期,页面销毁,关闭请求//rxjava3
 implementation ‘io.reactivex.rxjava3:rxjava:3.0.6’
 implementation ‘io.reactivex.rxjava3:rxandroid:3.0.0’
 implementation ‘com.ljx.rxlife3:rxlife-rxjava:3.0.0’ //管理RxJava3生命周期,页面销毁,关闭请求//非必须,根据自己需求选择 RxHttp默认内置了GsonConverter
 implementation ‘com.ljx.rxhttp:converter-fastjson:2.4.1’
 implementation ‘com.ljx.rxhttp:converter-jackson:2.4.1’
 implementation ‘com.ljx.rxhttp:converter-moshi:2.4.1’
 implementation ‘com.ljx.rxhttp:converter-protobuf:2.4.1’
 implementation ‘com.ljx.rxhttp:converter-simplexml:2.4.1’
 }


复制代码

2、Android 10/11 分区存储

当我们App的targetSdkVersion更改为28以上,并且运行在Android 10以上设备时,我们无法再以绝对路径的方式,去读写非沙盒目录下的文件,当然,如果App是覆盖安装(如:targetSdkVersion 28 覆盖安装为 29),则会保持原来的访问方式。

requestLegacyExternalStorage属性

如果我们的app将targetSdkVersion更改为28以上,且想保持原来的访问方式,则需要在清单文件中将 requestLegacyExternalStorage 的值设置为 true,如下:

<manifest …>
<application android:requestLegacyExternalStorage=“true” … >
 …



复制代码

此时,便可继续以原来的方式去读写文件,然而,在Android 11上,Google又给了它新的含义,来看看官网的原话

android10版本下载安装 android10下载在哪下载_echarts

也就是说,在Android 11设备上,targetSdkVersion为29以上的app,将强制开启分区存储,requestLegacyExternalStorage属性失效

注意,只要同时满足以上两个条件,不管是覆盖安装还是requestLegacyExternalStorage = true,都会强制开启分区存储

分区存储优势

  • 对用户来说,解决了文件乱放的现象
  • 对于开发者来说,我们无需写权限,就可以在分区目录下创建文件,并且访问自己创建的文件,不需要读权限(访问其它应用创建的文件,还是需要读权限)

新的文件访问方式

android10版本下载安装 android10下载在哪下载_上传_02

此图来源于作者[连续三届村草]分享的Android 10(Q)/11® 分区存储适配一文,感谢作者的总结

3、上传

3.1、简单上传

在介绍Android 10文件上传前,我们先来看看Android 10之前是如何上传文件的,如下:

//kotlin 协程
 val result = RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addFile(“file”, new File(“xxx/1.jpg”))
 .awaitString() //awaitXxx系列方法是挂断方法//RxJava
 RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addFile(“file”, new File(“xxx/1.jpg”))
 .asString()
 .subscribe({
 //成功回调
 }, {
 //异常回调
 })
 复制代码

以上,我们仅需调用 addFile方法添加文件对象即可,RxHttp提供了一系列addFile方法,列出几个常用的,如下:

//添加单个文件
 addFile(String, File)
 //添加多个文件,每个文件对应相同的key
 addFile(String, List<? extends File> fileList)
 //添加多个文件,每个文件对应不同的key
 addFile(Map<String, ? extends File> fileMap)
 //等等其它addFile方法
 复制代码

在Android 10,我们需要通过Uri对象去上传文件,在RxHttp中,通过addPart方法添加Uri对象,如下:

val context = getContext(); //获取上下文对象
 //获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
 val uri = Uri.parse(“content://media/external/downloads/13417”)//kotlin 协程
 val result = RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addPart(context, “file”, uri)
 .awaitString() //awaitXxx系列方法是挂断方法//RxJava
 RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addPart(context, “file”, uri)
 .asString()
 .subscribe({
 //成功回调
 }, {
 //异常回调
 })
 复制代码

同样的,RxHttp内部提供了一系列addPart方法供大家选择,列出几个常用的,如下:

//添加单个Uri对象
 addPart(Context, String, Uri)
 //添加多个Uri对象,每个Uri对应相同的key
 addParts(Context,String, List<? extends Uri> uris)
 //添加多个Uri对象,每个Uri对应不同的key
 addParts(Context context, Map<String, ? extends Uri> uriMap)
 //等等其它addPart方法
 复制代码

3.2、带进度上传

老规矩,看看Android 10之前是如何监听上传进度的,如下:

//kotlin 协程
 val result = RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addFile(“file”, new File(“xxx/1.jpg”))
 .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程
 //上传进度回调,0-100,仅在进度有更新时才会回调
 val currentProgress = it.getProgress() //当前进度 0-100
 val currentSize = it.getCurrentSize() //当前已上传的字节大小
 val totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .awaitString() //awaitXxx系列方法是挂断方法//RxJava
 RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addFile(“file”, new File(“xxx/1.jpg”))
 .upload(AndroidSchedulers.mainThread()) {
 //上传进度回调,0-100,仅在进度有更新时才会回调
 int currentProgress = it.getProgress() //当前进度 0-100
 long currentSize = it.getCurrentSize() //当前已上传的字节大小
 long totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .asString()
 .subscribe({
 //成功回调
 }, {
 //异常回调
 })
 复制代码

相比于单纯的上传文件,我们仅需额外调用upload操作符,传入线程调度器及进度回调即可。

同样的,对于Andorid 10,我们仅需要将File对象换成Uri对象即可,如下:

val context = getContext(); //获取上下文对象
 //获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
 val uri = Uri.parse(“content://media/external/downloads/13417”)//kotlin 协程
 val result = RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addPart(context, “file”, uri)
 .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程
 //上传进度回调,0-100,仅在进度有更新时才会回调
 val currentProgress = it.getProgress() //当前进度 0-100
 val currentSize = it.getCurrentSize() //当前已上传的字节大小
 val totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .awaitString() //awaitXxx系列方法是挂断方法//RxJava
 RxHttp.postForm(“/service/…”)
 .add(“key”, “value”)
 .addPart(context, “file”, uri)
 .upload(AndroidSchedulers.mainThread()) {
 //上传进度回调,0-100,仅在进度有更新时才会回调
 int currentProgress = it.getProgress() //当前进度 0-100
 long currentSize = it.getCurrentSize() //当前已上传的字节大小
 long totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .asString()
 .subscribe({
 //成功回调
 }, {
 //异常回调
 })
 复制代码

怎么样?是不是so easy!!

4、下载

下载较于上传,要丰富很多,RxHttp内部提供类一系列下载方法来满足不同的需求,如下:

//kotlin
 fun IRxHttp.toDownload(
 destPath: String,
 context: CoroutineContext? = null,
 progress: (suspend (ProgressT) -> Unit)? = null
 )
 fun IRxHttp.toDownload(
 context: Context,
 uri: Uri,
 coroutineContext: CoroutineContext? = null,
 progress: (suspend (ProgressT) -> Unit)? = null
 )
 fun IRxHttp.toDownload(
 osFactory: OutputStreamFactory,
 context: CoroutineContext? = null,
 progress: (suspend (ProgressT) -> Unit)? = null
 )


复制代码

4.1、简单下载

在Android 10之前,我们仅需传入一个本地文件路径即可,如下:

val localPath = “/sdcard/…/xxx.apk”
 //kotlin 协程
 val result = RxHttp.get(“/service/…/xxx.apk”)
 .toDownload(localPath)
 .await() //这里返回sd卡存储路径//RxJava
 RxHttp.get(“/service/…/xxx.apk”)
 .asDownload(localPath)
 .subscribe({
 //成功回调,这里返回sd卡存储路径
 }, {
 //异常回调
 })


复制代码

而到了Android 10,我们需要自定义一个Android10DownloadFactory类,继承UriFactory类,如下:

class Android10DownloadFactory @JvmOverloads constructor(

context: Context,
 fileName: String,
 queryUri: Uri? = null
 ) : UriFactory(context, queryUri, fileName) {override fun getUri(response: Response): Uri {
 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
 ContentValues().run {
 put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) //文件名
 //取contentType响应头作为文件类型
 put(MediaStore.MediaColumns.MIME_TYPE, response.body?.contentType().toString())
 //下载到Download目录
 put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
 val uri = queryUri ?: MediaStore.Downloads.EXTERNAL_CONTENT_URI
 context.contentResolver.insert(uri, this)
 } ?: throw NullPointerException(“Uri insert fail, Please change the file name”)
 } else {
 Uri.fromFile(File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), displayName))
 }
 }
 }


复制代码

这里简单介绍下上面的代码,本文后续会详细介绍为啥定义这样一个类,以及如何构建一个Uri对象。

  • 首先就是继承UriFactory抽象类,实现getUri(Response)方法
  • 接着就在实现方法里判断SDK版本,Android 10以上,就通过contentResolver构建Uri对象,否则就根据File对象构建Uri对象,这样就可以兼容到所有系统版本
  • 最后返回Uri对象即可

注:以上代码,基本可以满足大部分人的需求,如你有特殊需求,构建Uri的过程的作出简单的修改即可

有了Android10DownloadFactory类,执行Android 10下载就会及其方便,如下:

val factory = Android10DownloadFactory(context, “test.apk”)
//kotlin 协程
 val uri = RxHttp.get(“/service/…/xxx.apk”)
 .toDownload(factory)
 .await() //这里返回工厂类构建的Uri对象//RxJava
 RxHttp.get(“/service/…/xxx.apk”)
 .asDownload(factory)
 .subscribe({
 //成功回调,这里返回工厂类构建的Uri对象
 }, {
 //异常失败
 })
 复制代码

以上asDownloadtoDownload方法都接收一个UriFactory类型参数,故我们可以直接传入Android10DownloadFactory对象。

4.2、带进度下载

对于带进度下载,我们只需要调用asDownloadtoDownload方法时,传入线程调度器及进度回调即可,如下:

Android 10之前

val localPath = “/sdcard/…/xxx.apk”
//kotlin 协程
 val result = RxHttp.get(“/service/…/xxx.apk”)
 .toDownload(localPath, Dispatchers.Main) {
 //下载进度回调,0-100,仅在进度有更新时才会回调
 int currentProgress = it.getProgress() //当前进度 0-100
 long currentSize = it.getCurrentSize() //当前已上传的字节大小
 long totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .await() //这里返回sd卡存储路径//RxJava
 RxHttp.get(“/service/…/xxx.apk”)
 .asDownload(localPath, AndroidSchedulers.mainThread()) {
 //下载进度回调,0-100,仅在进度有更新时才会回调
 int currentProgress = it.getProgress() //当前进度 0-100
 long currentSize = it.getCurrentSize() //当前已上传的字节大小
 long totalSize = it.getTotalSize() //要上传的总字节大小
 }
 .subscribe({
 //成功回调,这里返回sd卡存储路径
 }, {
 //异常失败
 })
 复制代码