移动开发最新RxHttp-完美适配Android-10-11-上传-下载-进度监听,2024-2024蚂蚁金服Android面试真题解析

学习交流

群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

相比于单纯的上传文件,我们仅需额外调用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卡存储路径
}, {
//异常失败
})
复制代码

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对象

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

//RxJava
RxHttp.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)

//kotlin 协程
val uri = RxHttp.get(“/service/…/xxx.apk”)
.toAppendDownload(factory, Dispatchers.Main) {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = it.getProgress() //当前进度 0-100
long currentSize = it.getCurrentSize() //当前已上传的字节大小
long totalSize = it.getTotalSize() //要上传的总字节大小
}
.await() //这里返回工厂类构建的Uri对象

//RxJava
RxHttp.get(“/service/…/xxx.apk”)
.asAppendDownload(factory, AndroidSchedulers.mainThread()) {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = it.getProgress() //当前进度 0-100
long currentSize = it.getCurrentSize() //当前已上传的字节大小
long totalSize = it.getTotalSize() //要上传的总字节大小
}
.subscribe({
//成功回调,这里返回工厂类构建的Uri对象
}, {
//异常失败
})
复制代码

5、如何构建Uri对象?

在上面代码中,我们自定义了Android10DownloadFactory类,其中最为关键的代码就是如何构建一个Uri对象,接下来,就教大家如何去构建一个Uri,马上开始,如下:

public Uri getUri(Context context) {
ContentValues values = new ContentValues();
//1、配置文件名
values.put(MediaStore.MediaColumns.DISPLAY_NAME, “1.jpg”);
//2、配置文件类型
values.put(MediaStore.MediaColumns.MIME_TYPE, “image/jpeg”)
//3、配置存储目录
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
//4、将配置好的对象插入到某张表中,最终得到Uri对象
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
复制代码

  • 第一步,配置文件名称,这个就没啥好说的了

  • 第二步,配置文件类型,每个文件都应该有一个类型描述,这样,后续查找时,就可以根据这个类型去查找出同一类型的文件,如:查找相册,此属性是可选的,如果不配置,后续就无法根据类型查找到这个文件

  • 第三步,配置存储目录,这个是相对路径,总共有10个目录可选,如下:

  • Environment.DIRECTORY_DOCUMENTS 对应路径:/storage/emulated/0/Documents/

  • Environment.DIRECTORY_DOWNLOADS 对应路径:/storage/emulated/0/Download/

  • Environment.DIRECTORY_DCIM 对应路径:/storage/emulated/0/DCIM/

  • Environment.DIRECTORY_PICTURES 对应路径:/storage/emulated/0/Pictures/

  • Environment.DIRECTORY_MOVIES 对应路径:/storage/emulated/0/Movies/

  • Environment.DIRECTORY_ALARMS 对应路径:/storage/emulated/0/Alrams/

  • Environment.DIRECTORY_MUSIC 对应路径:/storage/emulated/0/Music/

  • Environment.DIRECTORY_NOTIFICATIONS 对应路径:/storage/emulated/0/Notifications/

  • Environment.DIRECTORY_PODCASTS 对应路径:/storage/emulated/0/Podcasts/

  • Environment.DIRECTORY_RINGTONES 对应路径:/storage/emulated/0/Ringtones/

如果需要在以上目录下,创建子目录,则传入的时候,直接带上即可,如下

values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + “/RxHttp”);
复制代码

  • 第四步,插入到对应的表中,总共有5张表可选,如下:

  • 存储图片:MediaStore.Images.Media.EXTERNAL_CONTENT_URI

  • 存储视频:MediaStore.Video.Media.EXTERNAL_CONTENT_URI

  • 存储音频:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

  • 存储任意文件:MediaStore.Downloads.EXTERNAL_CONTENT_URI

  • 存储任意文件:MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)

需要特殊说明下,以上5张表中,只能存入对应文件类型的信息,如我们不能将音频文件信息,插入到MediaStore.Images.Media.EXTERNAL_CONTENT_URI图片表中,插入时,系统会直接抛出异常

注意事项

以上5张表中,除了对插入的文件类型有限制外,还对要插入的相对路径有限制,如,我们将一个apk文件下载/storage/emulated/0/Download/RxHttp/目录下,并插入到图片表中,如下:

public Uri getUri(Context context) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, “1.apk”);
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + “/RxHttp”);
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
复制代码

最后的最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的

最后,互联网不存在所谓的寒冬,只是你没有努力罢了!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

我们去适应环境,而不是环境来适应我们!**

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的

[外链图片转存中…(img-hqmukeW8-1715451230101)]

最后,互联网不存在所谓的寒冬,只是你没有努力罢了!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值