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对象
}, {
//异常失败
})
复制代码
以上asDownload
、toDownload
方法都接收一个UriFactory
类型参数,故我们可以直接传入Android10DownloadFactory
对象。
4.2、带进度下载
对于带进度下载,我们只需要调用asDownload
、toDownload
方法时,传入线程调度器及进度回调即可,如下:
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)
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
[外链图片转存中…(img-wOQS04pa-1715245515291)]
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
[外链图片转存中…(img-UiatOKh7-1715245515292)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!