写点什么

RxHttp- 完美适配 Android-10-11- 上传 - 下载 - 进度监听

用户头像
Android架构
关注
发布于: 47 分钟前

requestLegacyExternalStorage 属性


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


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



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


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


分区存储优势


  • 对用户来说,解决了文件乱放的现象

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


新的文件访问方式



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

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 系列方法是挂断方法


//RxJavaRxHttp.postForm("/service/...")


.add("key", "value").addFile("file", new File("xxx/1.jpg")).asString()


.subscribe({


//成功回调


}, {


//异常回调


})


复制代码


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


//添加单个文件 addFile(String, File)//添加多个文件,每个文件对应相同的 keyaddFile(String, List<? extends File> fileList)//添加多个文件,每个文件对应不同的 keyaddFile(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 系列方法是挂断方法


//RxJavaRxHttp.postForm("/service/...")


.add("key", "value").addPart(context, "file", uri)


.asString()


.subscribe({


//成功回调


}, {


//异常回调


})复制代码


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


//添加单个 Uri 对象 addPart(Context, String, Uri)//添加多个 Uri 对象,每个 Uri 对应相同的 keyaddParts(Context,String, List<? extends Uri> uris)//添加多个 Uri 对象,每个 Uri 对应不同的 keyaddParts(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 系列方法是挂断方法


//RxJavaRxHttp.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 系列方法是挂断方法


//RxJavaRxHttp.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内部提供类一系列下载方法来满足不同的需求,如下:


//kotlinfun IRxHttp.toDownload(destPath:


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


String,context: CoroutineContext? = null,progress: (suspend (ProgressT<String>) -> Unit)? = null)fun IRxHttp.toDownload(context: Context,uri: Uri,coroutineContext: CoroutineContext? = null,progress: (suspend (ProgressT<Uri>) -> Unit)? = null)fun <T> IRxHttp.toDownload(osFactory: OutputStreamFactory<T>,context: CoroutineContext? = null,progress: (suspend (ProgressT<T>) -> Unit)? = null)复制代码

4.1、简单下载

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


val localPath = "/sdcard/.../xxx.apk"//kotlin 协程 val result = RxHttp.get("/service/.../xxx.apk")


.toDownload(localPath).await() //这里返回 sd 卡存储路径


//RxJavaRxHttp.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_URIcontext.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 对象


//RxJavaRxHttp.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 卡存储路径


//RxJavaRxHttp.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 卡存储路径


}, {


//异常失败


})复制代码


Android 10 以上,传入我们定义的Android10DownloadFactory对象的同时,再传入传入线程调度器及进度监听即可,如下:


val factory = Android10DownloadFactory(context, "test.apk")


//kotlin 协程 val uri = RxHttp.get("/service/.../xxx.apk")


.toDownload(factory, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调


int currentProgress = it.getProgress() //当前进度 0-100


long currentSize = it.getCurrentSize() //当前已上传的字节大小


long totalSize = it.getTotalSize() //要上传的总字节大小}.await() //这里返回工厂类构建的 Uri 对象


//RxJavaRxHttp.get("/service/.../xxx.apk")


.asDownload(factory, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调


int currentProgress = it.getProgress() //当前进度 0-100


long currentSize = it.getCurrentSize() //当前已上传的字节大小


long totalSize = it.getTotalSize() //要上传的总字节大小}.subscribe({


//成功回调,这里返回工厂类构建的 Uri 对象


}, {


//异常失败


})复制代码

4.3、带进度断点下载

对于断点下载,我们需要调用一系列asAppendDownload、toAppendDownload方法,可以把它们理解为追加下载,实现如下:


Android 10 之前


val localPath = "/sdcard/.../xxx.apk"


//kotlin 协程 val result = RxHttp.get("/service/.../xxx.apk")


.toAppendDownload(localPath, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调


int currentProgress = it.getProgress() //当前进度 0-100


long currentSize = it.getCurrentSize() //当前已上传的字节大小


long totalSize = it.getTotalSize() //要上传的总字节大小}.await() //这里返回 sd 卡存储路径


//RxJavaRxHttp.get("/service/.../xxx.apk")


.asAppendDownload(localPath, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调


int currentProgress = it.getProgress() //当前进度 0-100


long currentSize = it.getCurrentSize() //当前已上传的字节大小


long totalSize = it.getTotalSize() //要上传的总字节大小}.subscribe({


//成功回调,这里返回 sd 卡存储路径


}, {


//异常失败


})复制代码


在 Android 10 上,有一点需要注意的是,我们在构建Android10DownloadFactory对象时,需要传入第三个参数queryUri,可以把它理解为要查询的文件夹,断点下载,RxHttp 内部会根据文件名在指定的文件夹下查找对应的文件,得到当前文件的长度,也就是断点位置,从而告诉服务端从哪里开始下载,如下:


val queryUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI//在 Download 目录下查找 test.apk 文件 val factory = Android10DownloadFactory(context, "test.apk", queryUri)

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
RxHttp-完美适配Android-10-11-上传-下载-进度监听