Android通过okhttp下载文件(本文案例 下载mp4到本地,并更新到相册)

news2025/4/3 15:27:17

使用步骤分为两步

第一步导入 okhttp3 依赖

第二步调用本文提供的 utils

第一步这里不做说明了,直接提供第二步复制即用

在这里插入图片描述

DownloadUtil 中 download 为下载文件 参数说明

在这里插入图片描述
这里主要看你把 destFileName 下载文件名称定义为什么后缀,比如我定义为 .mp4 下载后 就是 mp4 格式

这里 destFileDir 下载目录要说一下,如果没有开启存储权限或者使用了系统默认路径就会报错 比如 /0 文件一类的错误,怎么使用可以参考 open failed: ENOENT (No such file or directory) 解决办法

DownloadUtil 中 saveVideoToAlbum 为将下载好的视频更新到手机图库中,原来的放式已经随着安全性提高不适用了,这里基本就是复制出一份更新到系统层的文件夹

源码

DownloadUtil.download(mVideoUrl,getUrlPath(),"sing示例名称${System.currentTimeMillis()}.mp4",object : DownloadUtil.OnDownloadListener{
                override fun onDownloadSuccess(file: File?) {
                    "下载成功".toast()
                    //更新视频到相册
                    DownloadUtil.saveVideoToAlbum(this@MoreActivity,file?.absolutePath)
                    Log.e("视频下载", "下载成功: ${file?.absolutePath}")
                }

                override fun onDownloading(progress: Int) {
                    Log.e("视频下载", "下载进度:${progress}")
                }

                override fun onDownloadFailed(e: Exception?) {
                    LoadingSingDialog.dismiss()
                    Log.e("视频下载", "下载失败:${e?.printStackTrace()}")
                }

            })

DownloadUtil

object DownloadUtil {

    private var okHttpClient: OkHttpClient? = null

    /**
     * @param url          下载连接
     * @param destFileDir  下载的文件储存目录
     * @param destFileName 下载文件名称
     * @param listener     下载监听
     */
    fun download(url: String, destFileDir: String, destFileName: String, listener: OnDownloadListener) {
        if (url == null || url == ""){
            return
        }
        if (okHttpClient == null){
            okHttpClient = OkHttpClient()
        }
        val request: Request = Request.Builder().url(url).build()
        okHttpClient!!.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                // 下载失败监听回调
                listener.onDownloadFailed(e)
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                if (response.body != null) {
                    var inputStream: InputStream? = null
                    val buf = ByteArray(2048)
                    var len = 0
                    var fos: FileOutputStream? = null
                    // 储存下载文件的目录
                    val dir = File(destFileDir)
                    if (!dir.exists()) {
                        dir.mkdirs()
                    }
                    val file = File(dir, destFileName)
                    try {
                        inputStream = response.body!!.byteStream()
                        val total: Long = response.body!!.contentLength()
                        fos = FileOutputStream(file)
                        var sum: Long = 0
                        while (inputStream.read(buf).also { len = it } != -1) {
                            fos.write(buf, 0, len)
                            sum += len.toLong()
                            val progress = (sum * 1.0f / total * 100).toInt()
                            // 下载中更新进度条
                            listener.onDownloading(progress)
                        }
                        fos.flush()
                        // 下载完成
                        listener.onDownloadSuccess(file)
                    } catch (e: Exception) {
                        listener.onDownloadFailed(e)
                    } finally {
                        try {
                            inputStream?.close()
                        } catch (e: IOException) {
                            listener.onDownloadFailed(e)
                        }
                        try {
                            fos?.close()
                        } catch (e: IOException) {
                            listener.onDownloadFailed(e)
                        }
                    }
                }else{
                    listener.onDownloadFailed(IOException("接口失败"))
                }
            }
        })
    }

    interface OnDownloadListener {
        /**
         * @param file 下载成功后的文件
         */
        fun onDownloadSuccess(file: File?)

        /**
         * @param progress 下载进度
         */
        fun onDownloading(progress: Int)

        /**
         * @param e 下载异常信息
         */
        fun onDownloadFailed(e: Exception?)
    }


    /**
     * 将视频保存到系统相册
     */
    fun saveVideoToAlbum(context: Context, videoFile: String?): Boolean {
        if (videoFile == null || videoFile == ""){
            return false
        }
        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            saveVideoToAlbumBeforeQ(context, videoFile)
        } else {
            saveVideoToAlbumAfterQ(context, videoFile)
        }
    }

    private fun saveVideoToAlbumAfterQ(context: Context, videoFile: String): Boolean {
        return try {
            val contentResolver = context.contentResolver
            val tempFile = File(videoFile)
            val contentValues = getVideoContentValues(context, tempFile, System.currentTimeMillis())
            val uri =
                contentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues)
            copyFileAfterQ(context, contentResolver, tempFile, uri)
            contentValues.clear()
            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
            context.contentResolver.update(uri!!, contentValues, null, null)
            context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri))
            true
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
            false
        }
    }

    private fun saveVideoToAlbumBeforeQ(context: Context, videoFile: String): Boolean {
        val picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
        val tempFile = File(videoFile)
        val destFile = File(picDir, context.packageName + File.separator + tempFile.name)
        var ins: FileInputStream? = null
        var ous: BufferedOutputStream? = null
        return try {
            ins = FileInputStream(tempFile)
            ous = BufferedOutputStream(FileOutputStream(destFile))
            var nread = 0L
            val buf = ByteArray(1024)
            var n: Int
            while (ins.read(buf).also { n = it } > 0) {
                ous.write(buf, 0, n)
                nread += n.toLong()
            }
            MediaScannerConnection.scanFile(
                context, arrayOf(destFile.absolutePath), arrayOf("video/*")
            ) { path: String?, uri: Uri? -> }
            true
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
            false
        } finally {
            try {
                ins?.close()
                ous?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    @Throws(IOException::class)
    private fun copyFileAfterQ(
        context: Context,
        localContentResolver: ContentResolver,
        tempFile: File,
        localUri: Uri?
    ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
            context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q
        ) {
            //拷贝文件到相册的uri,android10及以上得这么干,否则不会显示。可以参考ScreenMediaRecorder的save方法
            val os = localContentResolver.openOutputStream(localUri!!)
            Files.copy(tempFile.toPath(), os)
            os!!.close()
            tempFile.delete()
        }
    }


    /**
     * 获取视频的contentValue
     */
    private fun getVideoContentValues(context: Context, paramFile: File, timestamp: Long): ContentValues {
        val localContentValues = ContentValues()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            localContentValues.put(
                MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM
                        + File.separator + context.packageName
            )
        }
        localContentValues.put(MediaStore.Video.Media.TITLE, paramFile.name)
        localContentValues.put(MediaStore.Video.Media.DISPLAY_NAME, paramFile.name)
        localContentValues.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
        localContentValues.put(MediaStore.Video.Media.DATE_TAKEN, timestamp)
        localContentValues.put(MediaStore.Video.Media.DATE_MODIFIED, timestamp)
        localContentValues.put(MediaStore.Video.Media.DATE_ADDED, timestamp)
        localContentValues.put(MediaStore.Video.Media.SIZE, paramFile.length())
        return localContentValues
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2259293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为eNSP:VRRP

一、VRRP背景概述 在现代网络环境中&#xff0c;主机通常通过默认网关进行网络通信。当默认网关出现故障时&#xff0c;网络通信会中断&#xff0c;影响业务连续性和稳定性。为了提高网络的可靠性和冗余性&#xff0c;采用虚拟路由冗余协议&#xff08;VRRP&#xff09;是一种…

2.生成Transformation

目录 前言 Source FlatMap KeyBy sum print 总结 前言 以下面的WordCount为例 package com.wlh.p1;import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.api.java.functions.KeySelector; import org.apache.flink.api.java.tuple…

PCL点云库入门——PCL库可视化之CloudViewer类简单点云信息显示

1、前言 可视化&#xff08;visualization&#xff09;涉及运用计算机图形学和图像处理技术&#xff0c;将数据转换成图像并在屏幕上展示&#xff0c;同时支持交互式处理。在PCL库中&#xff0c;一系列强大的可视化工具可供使用&#xff0c;其中较为流行的包括CloudViewer和PCL…

AI大模型学习笔记|多目标算法梳理、举例

多目标算法学习内容推荐&#xff1a; 1.通俗易懂讲算法-多目标优化-NSGA-II(附代码讲解)_哔哩哔哩_bilibili 2.多目标优化 (python pyomo pareto 最优)_哔哩哔哩_bilibili 学习笔记&#xff1a; 通过网盘分享的文件&#xff1a;多目标算法学习笔记 链接: https://pan.baidu.com…

印闪网络:阿里云数据库MongoDB版助力金融科技出海企业降本增效

客户背景 上海印闪网络科技有限公司&#xff0c;于2017年1月成立&#xff0c;投资方包括红杉资本等多家国际知名风投公司。公司业务聚焦东南亚普惠金融&#xff0c;常年稳居行业头部。创始团队来自腾讯&#xff0c;中国团队主要由运营、风控及产研人员组成&#xff0c;核心成员…

路由引入问题(双点双向路由回馈问题)

简介 总所周知&#xff0c;路由引入import又称路由重分发redistribute&#xff0c;为了解决不同路由协议进程间路由信息不互通而使用的技术&#xff0c;由于不同路由协议的算法、机制、开销等因素的差异&#xff0c;它们之间无法直接交换路由信息。因此&#xff0c;路由引入技…

windows安装gradle

目录 1. gradle的简介2. 安装操作2.1 下载2.2 配置环境变量2.3 测试验证 3. 总结 1. gradle的简介 Gradle 是一个开源的项目自动化构建工具&#xff0c;专注于灵活性和性能。它基于 Apache Ant 和 Apache Maven 的概念&#xff0c;但采用了 Groovy 或 Kotlin 作为领域特定语言…

数据库中的代数运算

这些代数基本运算通常被封装在数据库查询语言中&#xff0c;如SQL中的SELECT、FROM、WHERE等子句&#xff0c;使得用户可以更方便地对数据库进行查询和处理。 下面的介绍基于以下两个关系来说明&#xff1a; 传统的集合运算 并&#xff08;∪&#xff09; 合并两个关系中的元组…

Linux驱动开发(12):中断子系统–按键中断实验

本章我们以按键为例讲解在驱动程序中如何使用中断&#xff0c; 在学习本章之前建议先回顾一下关于中断相关的裸机部分相关章节&#xff0c; 这里主要介绍在驱动中如何使用中断&#xff0c;对于中断的概念及GIC中断控制器相关内容不再进行讲解。 本章配套源码和设备树插件位于“…

智能家居WTR096-16S录放音芯片方案,实现语音播报提示及录音留言功能

前言&#xff1a; 在当今社会的高速运转之下&#xff0c;夜幕低垂之时&#xff0c;许多辛勤工作的父母尚未归家。对于肩负家庭责任的他们而言&#xff0c;确保孩童按时用餐与居家安全成为心头大事。此时&#xff0c;家居留言录音提示功能应运而生&#xff0c;恰似家中的一位无形…

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; 一、信号和槽的基本…

Zemax 中的 LED 阵列模型

LED 阵列的光学特性 LED 阵列由多个发光二极管 &#xff08;LED&#xff09; 组成&#xff0c;这些二极管以特定模式或配置排列&#xff0c;以实现均匀照明、更高强度或特定照明特性。这些阵列广泛用于显示器、照明系统、光通信和传感等应用。 LED 阵列的光学特性对于了解它如…

Qt编写区位码gb2312、机内码、国标码————附带详细介绍和编码实现

文章目录 0 背景1 了解编码1.1 ASCII码1.2 机内码、国标码、区位码1.2.1 区位码1.2.2 国标码&#xff08;GB 2312-80&#xff09;1.2.3 汉字机内码&#xff08;GB 2312&#xff09; 1.3 GBK和GB2312的区别2 编码实现2.1 QString数据转QByteArray类型2.1.1 使用QTextCodec2.1.2 …

【Linux系统】—— 权限的概念

【Linux系统】—— 权限的概念 1 权限1.1 什么是权限1.2 为什么要有权限1.3 理解权限 2 文件的权限2.1 文件角色2.2 文件权限2.3 修改文件权限2.3.1 修改目标属性2.3.1.1 字符修改法2.3.1.2 8进制修改法 2.3.2 修改角色 3 文件权限补充知识点3.1 只能修改自己的文件权限3.2 没有…

js:我要在template中v-for循环遍历这个centrerTopdata,我希望自循环前面三个就可以了怎么写

问&#xff1a; 我按在要在template中v-for循环遍历这个centrerTopdata&#xff0c;我希望自循环前面三个就可以了怎么写&#xff1f; 回答&#xff1a; 问&#xff1a; <div v-for"(item, index) in centrerTopdata.slice(0, 3)" :key"index"> d…

016 在路由器上配置 DHCP

配置路由器端口IP地址 将路由器的端口地址配置好&#xff0c; 左边的网络地址是 192.168.1.0 右边的网络地址是 192.168.2.0 配置路由器的DHCP服务 打开命令窗口&#xff0c;进入特权模式 进入全局配置 conf t创建一个DHCP地址池&#xff1b; po1 是地址池的名称&#xf…

使用IP自签名SSL证书

最近需要创建WebSocket服务器并使用SSL证书&#xff0c;由于是内网测试&#xff0c;所以需要使用指定IP的自签SSL证书。 其实笔者前面博文 使用nexus3作为Docker镜像仓库 解决nexus3登录x509: certificate has expired or is not yet valid 中有创建过相应的证书&#xff0c;这…

多模态大模型(二)——用Transformer Encoder和Decoder的方法(BLIP、CoCa、BEiTv3)

文章目录 BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 理解、生成我都要&#xff0c;一个很有效的、根据图片生成caption的工具1. BLIP的研究动机2. BLIP的模型结构3. CapFilt Model4. BLIP的训练过程 CoCa: C…

vue季度选择器(antd2.0 版本无此控件,单独写一个)

vue季度选择器 效果显示 效果显示 <template><div><a-popoverplacement"bottom"overlayClassName"season-picker"trigger"click"v-model"showSeason"><template #content><div class"season-picker-b…

基于Spring Boot + Vue的摄影师分享交流社区的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…