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

RxHttp 完美适配Android 10/11 上传/下载/进度监听随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的文件,为此,RxHttp 在2.4.0版本中就正式适配了分区存储,并且,可以非常优雅的实…

1、前言

随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的文件,为此,RxHttp 2.4.0版本中就正式适配了分区存储,并且,可以非常优雅的实现文件上传/下载/进度监听,三步即可搞懂任意请求。

老规矩,先看看请求三部曲

image.png

如果你想了解RxHttp更过功能,请查看以下系列文章

RxHttp ,比Retrofit 更优雅的协程体验

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

RxHttp 全网Http缓存最优解

gradle依赖

必须

jitpack添加到项目的build.gradle文件中,如下:

allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

注:RxHttp 2.6.0版本起,已全面从JCenter迁移至jitpack

//使用kapt依赖rxhttp-compiler时必须
apply plugin: 'kotlin-kapt'

android {
    //必须,java 8或更高
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    def rxhttp_version = '3.0.1'
    implementation 'com.squareup.okhttp3:okhttp:4.10.0' 
    implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version"
    kapt "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" //生成RxHttp类,纯Java项目,请使用annotationProcessor代替kapt
 }

2、可选

android {
    kapt {
        arguments {
            //依赖了RxJava时,rxhttp_rxjava参数为必须,传入RxJava版本号
            arg("rxhttp_rxjava", "3.1.4")  
            arg("rxhttp_package", "rxhttp")  //指定RxHttp类包名,非必须
        }
    }
    //如果项目未集成kotlin,通过javaCompileOptions方法传参,在defaultConfig标签下
    annotationProcessorOptions {
        arguments = [
            rxhttp_rxjava: '3.1.4',
            rxhttp_package: 'rxhttp'
        ]
    }
}
dependencies {    
    //rxjava2 (RxJava2/Rxjava3二选一,使用asXxx方法时必须)
    implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //管理RxJava2生命周期,页面销毁,关闭请求

    //rxjava3
    implementation 'io.reactivex.rxjava3:rxjava:3.1.4'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //管理RxJava3生命周期,页面销毁,关闭请求

    //非必须,根据自己需求选择 RxHttp默认内置了GsonConverter
    implementation "com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version"
    implementation "com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version"
    implementation "com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version"
    implementation "com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version"
    implementation "com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version"
    implementation "com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version"
}

2、Android 10/11 分区存储

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

requestLegacyExternalStorage属性

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

<manifest ...>
<!-- This attribute is "false" by default on apps targeting Android 10 or higher. -->
   <application android:requestLegacyExternalStorage="true" ... >
   ...
   </application>
</manifest>

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

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

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

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

分区存储优势

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

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

新的文件访问方式

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

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

3、上传

3.1、简单上传

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

//kotlin 协程
val result = RxHttp.postForm("/service/...")             
    .add("key", "value")
    .addFile("file", File("xxx/1.jpg"))
    .toAwait<User>()
    .await()   //await方法是挂断方法 

//Kotlin Flow 流
RxHttp.postForm("/service/...")      
    .add("key", "value")
    .addFile("file", File("xxx/1.jpg")) 
    .toFlow<User>()
    .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }
    
//RxJava
RxHttp.postForm("/service/...")      
    .add("key", "value")
    .addFile("file", File("xxx/1.jpg")) 
    .toObservable<User>()                             
    .subscribe({                       
        //成功回调 
    }, {                       
        //异常回调 
    })                             

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

//添加单个文件
addFile(String, File) 
//添加多个文件,每个文件对应相同的key
addFiles(String, List<? extends File> fileList) 
//添加多个文件,每个文件对应不同的key
addFiles(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)
    .toAwait<User>()
    .await()   //await方法是挂断方法 
    
//Kotlin Flow 流
RxHttp.postForm("/service/...")      
    .add("key", "value")
    .addPart(context, "file", uri) 
    .toFlow<User>()
    .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }
    
//RxJava
RxHttp.postForm("/service/...")        
    .add("key", "value")
    .addPart(context, "file", uri)                              
    .toObservable<User>()                                                 
    .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 Flow 
RxHttp.postForm("/service/...")             
    .add("key", "value")
    .addFile("file", File("xxx/1.jpg"))
    .toFlow<String> {
        //上传进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }.catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.postForm("/service/...")      
    .add("key", "value")
    .addFile("file", File("xxx/1.jpg"))
    .toObservableString()
    .onMainProgress {            
        //上传进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .subscribe({                       
        //成功回调 
    }, {                       
        //异常回调 
    })                   

对于Flow流,仅需要在toFlow方法传入进度回调即可;对于RxJava,则需要调用onMainProgress去监控进度

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

val context = getContext();  //获取上下文对象 
//获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
val uri = Uri.parse("content://media/external/downloads/13417")

//kotlin Flow
RxHttp.postForm("/service/...")             
    .add("key", "value")
    .addPart(context, "file", uri)     
    .toFlow<String> {
        //上传进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }.catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.postForm("/service/...")      
    .add("key", "value")
    .addPart(context, "file", uri) 
    .toObservableString()
    .onMainProgress {            
        //上传进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .subscribe({                       
        //成功回调 
    }, {                       
        //异常回调 
    })                

怎么样?是不是so easy!!

4、下载

对于下载,RxHttp提供了一系列toDownloadAwaittoDownaloadFlowtoDownloadObservable方法执行下载操作,分别对应Await、Flow、RxJava,这里贴出toDownloadAwait系列方法代码,如下:

//kotlin
fun CallFactory.toDownloadAwait( destPath: String, append: Boolean = false, capacity: Int = 1, progress: (suspend (Progress) -> Unit)? = null ): Await<String>

fun CallFactory.toDownloadAwait( context: Context, uri: Uri, append: Boolean = false, capacity: Int = 1, progress: (suspend (Progress) -> Unit)? = null ): Await<Uri>

fun <T> CallFactory.toDownloadAwait( osFactory: OutputStreamFactory<T>, append: Boolean = false, capacity: Int = 1, progress: (suspend (Progress) -> Unit)? = null ): : Await<T>

以上3个方法,前2个最终都会调用第三个方法,其中append参数代表是否追加下载,也就是是否断点下载,默认false,capacity参数是设置队列缓存大小的,仅当在监听进度时才生效,防止上游生产事件快,下游消费慢,导致事件堆积问题,capacity = 1代表,队列size始终为1,新的事件会覆盖旧的事件。

4.1、简单下载

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

val localPath = "/sdcard/.../xxx.apk"
//kotlin Await 协程
val result = RxHttp.get("/service/.../xxx.apk")       
    .toDownloadAwait(localPath)
    .await()  //这里返回sd卡存储路径
    
//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadFlow(localPath)
    .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadObservable(localPath)
    .subscribe({                
        //成功回调,这里返回sd卡存储路径 
    }, {                
        //异常回调 
    })

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

class Android10DownloadFactory @JvmOverloads constructor(
    context: Context,
    fileName: String,
    queryUri: Uri? = null
) : UriFactory(context, queryUri, fileName) {

    override fun insert(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抽象类,实现insert(Response)方法
  • 接着就在实现方法里判断SDK版本,Android 10以上,就通过contentResolver构建Uri对象,否则就根据File对象构建Uri对象,这样就可以兼容到所有系统版本
  • 最后返回Uri对象即可

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

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

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

//kotlin Await 协程
val uri = RxHttp.get("/service/.../xxx.apk")       
    .toDownloadAwait(factory)
    .await()  //这里返回工厂类构建的Uri对象
    
//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadFlow(factory)
    .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadObservable(factory)
    .subscribe({                
        //成功回调,这里返回工厂类构建的Uri对象 
    }, {                
        //异常失败 
    })

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

4.2、带进度下载

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

Android 10之前

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

//kotlin Await 协程
val result = RxHttp.get("/service/.../xxx.apk")       
    .toDownloadAwait(localPath) {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .await()  //这里返回sd卡存储路径
    
//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadFlow(localPath) {
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }.catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadObservable(localPath)
    .onMainProgress {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .subscribe({                
        //成功回调,这里返回sd卡存储路径 
    }, {                
        //异常失败 
    }) 

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

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

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

//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadFlow(factory) {
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    } .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .asDownload(factory)
    .onMainProgress {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .subscribe({                
        //成功回调,这里返回工厂类构建的Uri对象 
    }, {                
        //异常失败 
    }) 

4.3、带进度断点下载

对于断点下载,RxJava需要调用asAppendDownload方法,Await、Flow继续调用toDownload、toFlow方法,其中append参数传true即可实现如下:

Android 10之前

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

//kotlin Await 协程
val result = RxHttp.get("/service/.../xxx.apk")       
    .toDownloadAwait(localPath, true) {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .await()  //这里返回sd卡存储路径
    
//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDowbloadFlow(localPath, true) {
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    } .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadObservable(localPath, true)
    .onMainProgress {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .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 Await 协程
val uri = RxHttp.get("/service/.../xxx.apk")       
    .toDownloadAwait(factory, true) {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .await()  //这里返回工厂类构建的Uri对象
    
//kotlin Flow
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadFlow(factory, true) {
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    } .catch {
        //异常回调 
    }.collect {
        //成功回调 
    }  
    
//RxJava
RxHttp.get("/service/.../xxx.apk")       
    .toDownloadObservable(factory, true)
    .onMainProgress {
        //下载进度回调,0-100,仅在进度有更新时才会回调 
        val currentProgress = it.progress //当前进度 0-100 
        val currentSize = it.currentSize  //当前已上传的字节大小 
        val totalSize = it.totalSize      //要上传的总字节大小 
    }
    .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);
}         

当执行到insert操作时,系统将会直接报错,报错信息如下:

Primary directory Download not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]

大致意思就是,Download目录不允许插入到MediaStore.Images.Media.EXTERNAL_CONTENT_URI表中,该表只允许插入DCIMPictures目录

6、小结

开源不易,写文章更不易,喜欢的话,还需劳烦大家给本文点个赞,可以的话,再给个star,我将感激不尽,🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14418.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注