1、前言

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

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



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

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

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

gradle依赖

必须

  1. //使用kapt依赖rxhttp-compiler时必须
  2. apply plugin: 'kotlin-kapt'
  3. android {
  4. //必须,java 8或更高
  5. compileOptions {
  6. sourceCompatibility JavaVersion.VERSION_1_8
  7. targetCompatibility JavaVersion.VERSION_1_8
  8. }
  9. }
  10. dependencies {
  11. implementation 'com.ljx.rxhttp:rxhttp:2.5.2'
  12. implementation 'com.squareup.okhttp3:okhttp:4.9.0' //rxhttp v2.2.2版本起,需要手动依赖okhttp
  13. kapt 'com.ljx.rxhttp:rxhttp-compiler:2.5.2' //生成RxHttp类,纯Java项目,请使用annotationProcessor代替kapt
  14. }

可选

  1. android {
  2. defaultConfig {
  3. javaCompileOptions {
  4. annotationProcessorOptions {
  5. arguments = [
  6. //使用asXxx方法时必须,告知RxHttp你依赖的rxjava版本,可传入rxjava2、rxjava3
  7. rxhttp_rxjava: 'rxjava3'
  8. rxhttp_package: 'rxhttp' //非必须,指定RxHttp类包名
  9. ]
  10. }
  11. }
  12. }
  13. }
  14. dependencies {
  15. implementation 'com.ljx.rxlife:rxlife-coroutine:2.0.1' //管理协程生命周期,页面销毁,关闭请求
  16. //rxjava2 (RxJava2/Rxjava3二选一,使用asXxx方法时必须)
  17. implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
  18. implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
  19. implementation 'com.ljx.rxlife2:rxlife-rxjava:2.0.0' //管理RxJava2生命周期,页面销毁,关闭请求
  20. //rxjava3
  21. implementation 'io.reactivex.rxjava3:rxjava:3.0.6'
  22. implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
  23. implementation 'com.ljx.rxlife3:rxlife-rxjava:3.0.0' //管理RxJava3生命周期,页面销毁,关闭请求
  24. //非必须,根据自己需求选择 RxHttp默认内置了GsonConverter
  25. implementation 'com.ljx.rxhttp:converter-fastjson:2.5.2'
  26. implementation 'com.ljx.rxhttp:converter-jackson:2.5.2'
  27. implementation 'com.ljx.rxhttp:converter-moshi:2.5.2'
  28. implementation 'com.ljx.rxhttp:converter-protobuf:2.5.2'
  29. implementation 'com.ljx.rxhttp:converter-simplexml:2.5.2'
  30. }

2、Android 10/11 分区存储

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

requestLegacyExternalStorage属性

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

  1. <manifest ...>
  2. <!-- This attribute is "false" by default on apps targeting
  3. Android 10 or higher. -->
  4. <application android:requestLegacyExternalStorage="true" ... >
  5. ...
  6. </application>
  7. </manifest>

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

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

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

分区存储优势

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

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

新的文件访问方式

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

3、上传

3.1、简单上传

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

  1. //kotlin 协程
  2. val result = RxHttp.postForm("/service/...")
  3. .add("key", "value")
  4. .addFile("file", File("xxx/1.jpg"))
  5. .awaitString() //awaitXxx系列方法是挂断方法
  6. //RxJava
  7. RxHttp.postForm("/service/...")
  8. .add("key", "value")
  9. .addFile("file", File("xxx/1.jpg"))
  10. .asString()
  11. .subscribe({
  12. //成功回调
  13. }, {
  14. //异常回调
  15. })

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

  1. //添加单个文件
  2. addFile(String, File)
  3. //添加多个文件,每个文件对应相同的key
  4. addFile(String, List<? extends File> fileList)
  5. //添加多个文件,每个文件对应不同的key
  6. addFile(Map<String, ? extends File> fileMap)
  7. //等等其它addFile方法

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


  1. val context = getContext(); //获取上下文对象
  2. //获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
  3. val uri = Uri.parse("content://media/external/downloads/13417")
  4. //kotlin 协程
  5. val result = RxHttp.postForm("/service/...")
  6. .add("key", "value")
  7. .addPart(context, "file", uri)
  8. .awaitString() //awaitXxx系列方法是挂断方法
  9. //RxJava
  10. RxHttp.postForm("/service/...")
  11. .add("key", "value")
  12. .addPart(context, "file", uri)
  13. .asString()
  14. .subscribe({
  15. //成功回调
  16. }, {
  17. //异常回调
  18. })

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

  1. //添加单个Uri对象
  2. addPart(Context, String, Uri)
  3. //添加多个Uri对象,每个Uri对应相同的key
  4. addParts(ContextString, List<? extends Uri> uris)
  5. //添加多个Uri对象,每个Uri对应不同的key
  6. addParts(Context context, Map<String, ? extends Uri> uriMap)
  7. //等等其它addPart方法

3.2、带进度上传

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

  1. //kotlin 协程
  2. val result = RxHttp.postForm("/service/...")
  3. .add("key", "value")
  4. .addFile("file", File("xxx/1.jpg"))
  5. .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程
  6. //上传进度回调,0-100,仅在进度有更新时才会回调
  7. val currentProgress = it.progress //当前进度 0-100
  8. val currentSize = it.currentSize //当前已上传的字节大小
  9. val totalSize = it.totalSize //要上传的总字节大小
  10. }
  11. .awaitString() //awaitXxx系列方法是挂断方法
  12. //RxJava
  13. RxHttp.postForm("/service/...")
  14. .add("key", "value")
  15. .addFile("file", File("xxx/1.jpg"))
  16. .upload(AndroidSchedulers.mainThread()) {
  17. //上传进度回调,0-100,仅在进度有更新时才会回调
  18. val currentProgress = it.progress //当前进度 0-100
  19. val currentSize = it.currentSize //当前已上传的字节大小
  20. val totalSize = it.totalSize //要上传的总字节大小
  21. }
  22. .asString()
  23. .subscribe({
  24. //成功回调
  25. }, {
  26. //异常回调
  27. })

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

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

  1. val context = getContext(); //获取上下文对象
  2. //获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
  3. val uri = Uri.parse("content://media/external/downloads/13417")
  4. //kotlin 协程
  5. val result = RxHttp.postForm("/service/...")
  6. .add("key", "value")
  7. .addPart(context, "file", uri)
  8. .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程
  9. //上传进度回调,0-100,仅在进度有更新时才会回调
  10. val currentProgress = it.progress //当前进度 0-100
  11. val currentSize = it.currentSize //当前已上传的字节大小
  12. val totalSize = it.totalSize //要上传的总字节大小
  13. }
  14. .awaitString() //awaitXxx系列方法是挂断方法
  15. //RxJava
  16. RxHttp.postForm("/service/...")
  17. .add("key", "value")
  18. .addPart(context, "file", uri)
  19. .upload(AndroidSchedulers.mainThread()) {
  20. //上传进度回调,0-100,仅在进度有更新时才会回调
  21. val currentProgress = it.progress //当前进度 0-100
  22. val currentSize = it.currentSize //当前已上传的字节大小
  23. val totalSize = it.totalSize //要上传的总字节大小
  24. }
  25. .asString()
  26. .subscribe({
  27. //成功回调
  28. }, {
  29. //异常回调
  30. })

怎么样?是不是so easy!!

4、下载

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

  1. //kotlin
  2. fun IRxHttp.toDownload(
  3. destPath: String,
  4. context: CoroutineContext? = null,
  5. progress: (suspend (ProgressT<String>) -> Unit)? = null
  6. )
  7. fun IRxHttp.toDownload(
  8. context: Context,
  9. uri: Uri,
  10. coroutineContext: CoroutineContext? = null,
  11. progress: (suspend (ProgressT<Uri>) -> Unit)? = null
  12. )
  13. fun <T> IRxHttp.toDownload(
  14. osFactory: OutputStreamFactory<T>,
  15. context: CoroutineContext? = null,
  16. progress: (suspend (ProgressT<T>) -> Unit)? = null
  17. )

4.1、简单下载

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

  1. val localPath = "/sdcard/.../xxx.apk"
  2. //kotlin 协程
  3. val result = RxHttp.get("/service/.../xxx.apk")
  4. .toDownload(localPath)
  5. .await() //这里返回sd卡存储路径
  6. //RxJava
  7. RxHttp.get("/service/.../xxx.apk")
  8. .asDownload(localPath)
  9. .subscribe({
  10. //成功回调,这里返回sd卡存储路径
  11. }, {
  12. //异常回调
  13. })

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

  1. class Android10DownloadFactory @JvmOverloads constructor(
  2. context: Context,
  3. fileName: String,
  4. queryUri: Uri? = null
  5. ) : UriFactory(context, queryUri, fileName) {
  6. override fun getUri(response: Response): Uri {
  7. return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
  8. ContentValues().run {
  9. put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) //文件名
  10. //取contentType响应头作为文件类型
  11. put(MediaStore.MediaColumns.MIME_TYPE, response.body?.contentType().toString())
  12. //下载到Download目录
  13. put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
  14. val uri = queryUri ?: MediaStore.Downloads.EXTERNAL_CONTENT_URI
  15. context.contentResolver.insert(uri, this)
  16. } ?: throw NullPointerException("Uri insert fail, Please change the file name")
  17. } else {
  18. Uri.fromFile(File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), displayName))
  19. }
  20. }
  21. }

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

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

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

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

  1. val factory = Android10DownloadFactory(context, "test.apk")
  2. //kotlin 协程
  3. val uri = RxHttp.get("/service/.../xxx.apk")
  4. .toDownload(factory)
  5. .await() //这里返回工厂类构建的Uri对象
  6. //RxJava
  7. RxHttp.get("/service/.../xxx.apk")
  8. .asDownload(factory)
  9. .subscribe({
  10. //成功回调,这里返回工厂类构建的Uri对象
  11. }, {
  12. //异常失败
  13. })

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

4.2、带进度下载

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

Android 10之前

  1. val localPath = "/sdcard/.../xxx.apk"
  2. //kotlin 协程
  3. val result = RxHttp.get("/service/.../xxx.apk")
  4. .toDownload(localPath, Dispatchers.Main) {
  5. //下载进度回调,0-100,仅在进度有更新时才会回调
  6. val currentProgress = it.progress //当前进度 0-100
  7. val currentSize = it.currentSize //当前已上传的字节大小
  8. val totalSize = it.totalSize //要上传的总字节大小
  9. }
  10. .await() //这里返回sd卡存储路径
  11. //RxJava
  12. RxHttp.get("/service/.../xxx.apk")
  13. .asDownload(localPath, AndroidSchedulers.mainThread()) {
  14. //下载进度回调,0-100,仅在进度有更新时才会回调
  15. val currentProgress = it.progress //当前进度 0-100
  16. val currentSize = it.currentSize //当前已上传的字节大小
  17. val totalSize = it.totalSize //要上传的总字节大小
  18. }
  19. .subscribe({
  20. //成功回调,这里返回sd卡存储路径
  21. }, {
  22. //异常失败
  23. })

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

  1. val factory = Android10DownloadFactory(context, "test.apk")
  2. //kotlin 协程
  3. val uri = RxHttp.get("/service/.../xxx.apk")
  4. .toDownload(factory, Dispatchers.Main) {
  5. //下载进度回调,0-100,仅在进度有更新时才会回调
  6. val currentProgress = it.progress //当前进度 0-100
  7. val currentSize = it.currentSize //当前已上传的字节大小
  8. val totalSize = it.totalSize //要上传的总字节大小
  9. }
  10. .await() //这里返回工厂类构建的Uri对象
  11. //RxJava
  12. RxHttp.get("/service/.../xxx.apk")
  13. .asDownload(factory, AndroidSchedulers.mainThread()) {
  14. //下载进度回调,0-100,仅在进度有更新时才会回调
  15. val currentProgress = it.progress //当前进度 0-100
  16. val currentSize = it.currentSize //当前已上传的字节大小
  17. val totalSize = it.totalSize //要上传的总字节大小
  18. }
  19. .subscribe({
  20. //成功回调,这里返回工厂类构建的Uri对象
  21. }, {
  22. //异常失败
  23. })

4.3、带进度断点下载

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

Android 10之前

  1. val localPath = "/sdcard/.../xxx.apk"
  2. //kotlin 协程
  3. val result = RxHttp.get("/service/.../xxx.apk")
  4. .toAppendDownload(localPath, Dispatchers.Main) {
  5. //下载进度回调,0-100,仅在进度有更新时才会回调
  6. val currentProgress = it.progress //当前进度 0-100
  7. val currentSize = it.currentSize //当前已上传的字节大小
  8. val totalSize = it.totalSize //要上传的总字节大小
  9. }
  10. .await() //这里返回sd卡存储路径
  11. //RxJava
  12. RxHttp.get("/service/.../xxx.apk")
  13. .asAppendDownload(localPath, AndroidSchedulers.mainThread()) {
  14. //下载进度回调,0-100,仅在进度有更新时才会回调
  15. val currentProgress = it.progress //当前进度 0-100
  16. val currentSize = it.currentSize //当前已上传的字节大小
  17. val totalSize = it.totalSize //要上传的总字节大小
  18. }
  19. .subscribe({
  20. //成功回调,这里返回sd卡存储路径
  21. }, {
  22. //异常失败
  23. })

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

  1. val queryUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
  2. //在Download目录下查找test.apk文件
  3. val factory = Android10DownloadFactory(context, "test.apk", queryUri)
  4. //kotlin 协程
  5. val uri = RxHttp.get("/service/.../xxx.apk")
  6. .toAppendDownload(factory, Dispatchers.Main) {
  7. //下载进度回调,0-100,仅在进度有更新时才会回调
  8. val currentProgress = it.progress //当前进度 0-100
  9. val currentSize = it.currentSize //当前已上传的字节大小
  10. val totalSize = it.totalSize //要上传的总字节大小
  11. }
  12. .await() //这里返回工厂类构建的Uri对象
  13. //RxJava
  14. RxHttp.get("/service/.../xxx.apk")
  15. .asAppendDownload(factory, AndroidSchedulers.mainThread()) {
  16. //下载进度回调,0-100,仅在进度有更新时才会回调
  17. val currentProgress = it.progress //当前进度 0-100
  18. val currentSize = it.currentSize //当前已上传的字节大小
  19. val totalSize = it.totalSize //要上传的总字节大小
  20. }
  21. .subscribe({
  22. //成功回调,这里返回工厂类构建的Uri对象
  23. }, {
  24. //异常失败
  25. })

5、如何构建Uri对象?

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

  1. public Uri getUri(Context context) {
  2. ContentValues values = new ContentValues();
  3. //1、配置文件名
  4. values.put(MediaStore.MediaColumns.DISPLAY_NAME, "1.jpg");
  5. //2、配置文件类型
  6. values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
  7. //3、配置存储目录
  8. values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
  9. //4、将配置好的对象插入到某张表中,最终得到Uri对象
  10. return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
  11. }
  • 第一步,配置文件名称,这个就没啥好说的了

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

  • 第三步,配置存储目录,这个是相对路径,总共有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/

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

  1. 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/目录下,并插入到图片表中,如下:

  1. public Uri getUri(Context context) {
  2. ContentValues values = new ContentValues();
  3. values.put(MediaStore.MediaColumns.DISPLAY_NAME, "1.apk");
  4. values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/RxHttp");
  5. return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
  6. }

当执行到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,我将感激不尽,

RxHttp 完美适配Android 10/11 上传/下载/进度监听的更多相关文章

  1. 使用Typescript重构axios(二十五)——文件上传下载进度监控

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  2. SSM + Android 网络文件上传下载

    SSM + Android 网络交互的那些事 2016年12月14日 17:58:36 ssm做为后台与android交互,相信只要是了解过的人都知道一些基本的数据交互,向json,对象,map的交互 ...

  3. FTP 上传下载 进度条

    11 /// <summary> /// 文件上传 /// </summary> /// <param name="filePath">原路径( ...

  4. 详细解读XMLHttpRequest(二)响应属性、二进制数据、监测上传下载进度

    本文主要参考:MDN 分析并操作 responseXML属性 如果你使用 XMLHttpRequest 来获得一个远程的 XML 文档的内容,responseXML 属性将会是一个由 XML 文档解析 ...

  5. ajax 上传文件,监听进度(progress)

    mdn 前端代码 github <body class="m-2"> <label for="a" class="btn btn-p ...

  6. jq 上传下载进度条

    里面只演示了下载的,挂载的是我的七牛服务器上的内容,上传事件和下载是一模一样的,为了大家不乱上传东西到我的服务器,而且我的服务器容量也不大,这里只展示了下载.代码: <!DOCTYPE html ...

  7. html5上传文件并监听进度

    出处:   http://blog.csdn.net/small_rice_/article/details/21391625

  8. 一行代码实现Okhttp,Retrofit,Glide下载上传进度监听

    https://mp.weixin.qq.com/s/bopDUFMB7EiK-MhLc3KDXQ essyan 鸿洋 2017-06-29 本文作者 本文由jessyan投稿. jessyan的博客 ...

  9. OkHttp 优雅封装 HttpUtils 之 上传下载解密

    曾经在代码里放荡不羁,如今在博文中日夜兼行,只为今天与你分享成果.如果觉得本文有用,记得关注我,我将带给你更多. 还没看过第一篇文章的欢迎移步:OkHttp 优雅封装 HttpUtils 之气海雪山初 ...

随机推荐

  1. 跟阿斌一起学鸿蒙(2). Ability vs App?

    在进一步实践之前,需要先弄明白一个概念:Ability. 不知道你有没有注意到,使用鸿蒙开发工具DevEco Studio创建项目时,我们选择创建的是一个个Ability. 这是为什么呢? 1. 鸿蒙 ...

  2. ResHacker 用命令行方式修改 windows PE文件版本号

    由于工作需要在詹金斯(genkins)集成环境打包,打包避免不了需要修改版本号,写入版本号最简单的方式通过修改windows rc文件 这就意味着,每次构建新版本前需要修改一次源文件 这个在用詹金斯集 ...

  3. CentOS下配置VNC

    配置桌面 # 安装gnome桌面环境 yum groupinstall Desktop -y # 安装中文语言支持包(可选) yum groupinstall 'Chinese Support' -y ...

  4. springmvc<一> 一些特殊的Bean

    Special Bean Types        HandlerMapping 基于前置或后置拦截器映射请求到处理器,具体实现方式由子类决定,        两种主要的实现             ...

  5. Python+moviepy音视频剪辑:视频帧数据的本质、Clip的fl方法进行变换处理的原理以及滚屏案例

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt+moviepy音视频剪辑实战 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一. ...

  6. Python中的"缝合器"zip函数:将多个可迭代对象组合成一个迭代器

    zip函数将参数中多个可迭代对象中相同序号的元素取出组合成一个元组作为输出列表的一个同样序号的元素,即输出列表的每个元素是一个元组,该元组的元素来源于参数中每个迭代对象的对应序号的元素. 具体可参考: ...

  7. PyQt(Python+Qt)学习随笔:model/view架构中QTableView视图的标题显示不正常问题

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在进行QTableView展示数据时,使用了QStandardItemModel的model,并在将 ...

  8. 写入到csv文件的两种方式(pd.DaaFrame 和 csv.writerow)

    第一种: pd.DataFrame to_csv tmp = pd.DataFrame({"id":[str(i) for i in range(len(test_x))],f&q ...

  9. 协程gevent学习

    import gevent def f1(): print(11) gevent.sleep(2) print(33) def f2(): print(22) gevent.sleep(1) prin ...

  10. 使用MySQL Shell创建MGR

    本篇知识点: 配置MGR所需的参数 使用MySQL Shell配置MGR shell.connect() var 设定临时变量 dba.createCluster() dba.getCluster() ...