Android 高版本 DownloadManager 封装工具类,支持 APK 断点续传与自动安装

主要有以下优点

  1. 兼容高版本 Android:适配 Android 10 及以上版本的存储权限和安装权限。
  2. 断点续传:支持从断点继续下载。
  3. 下载进度监听:实时获取下载进度并回调。
  4. 错误处理:处理下载失败、网络异常等情况。
  5. 自动安装 APK:下载完成后自动安装 APK 文件。
  6. 通知栏进度:显示下载进度和状态。

优化后的 DownloadManager 工具类

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.core.content.FileProvider;
import java.io.File;public class DownloadUtils {private static final String TAG = "DownloadUtils";private static DownloadUtils instance;private DownloadManager downloadManager;private long downloadId;private Context context;private DownloadProgressListener progressListener;private DownloadObserver downloadObserver;private DownloadUtils(Context context) {this.context = context.getApplicationContext();downloadManager = (DownloadManager) this.context.getSystemService(Context.DOWNLOAD_SERVICE);}public static synchronized DownloadUtils getInstance(Context context) {if (instance == null) {instance = new DownloadUtils(context);}return instance;}/*** 下载文件** @param url      文件下载地址* @param fileName 保存的文件名* @param listener 下载进度监听器*/public void downloadFile(String url, String fileName, DownloadProgressListener listener) {this.progressListener = listener;// 创建下载请求DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));request.setTitle("文件下载");request.setDescription("正在下载文件...");request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);// 设置下载路径if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {// Android 10 及以上版本,使用应用专属目录request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName);} else {// Android 10 以下版本,使用公共下载目录request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);}// 支持断点续传request.setAllowedOverMetered(true); // 允许使用移动网络request.setAllowedOverRoaming(true); // 允许漫游时下载// 开始下载downloadId = downloadManager.enqueue(request);// 注册下载完成监听context.registerReceiver(downloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));// 注册下载进度监听if (progressListener != null) {downloadObserver = new DownloadObserver(new Handler(Looper.getMainLooper()), downloadId);context.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true, downloadObserver);}}/*** 下载完成的广播接收器*/private final BroadcastReceiver downloadCompleteReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);if (id == downloadId) {if (progressListener != null) {progressListener.onDownloadComplete();}installApk(context);}}};/*** 安装 APK 文件*/private void installApk(Context context) {File apkFile;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {// Android 10 及以上版本,使用应用专属目录apkFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app-update.apk");} else {// Android 10 以下版本,使用公共下载目录apkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "app-update.apk");}if (!apkFile.exists()) {Log.e(TAG, "APK 文件不存在");return;}// 使用 FileProvider 获取文件的 UriUri apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);// 创建安装 IntentIntent installIntent = new Intent(Intent.ACTION_VIEW);installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 适配 Android 7.0 及以上版本if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);}// 适配 Android 8.0 及以上版本,允许安装未知来源的应用if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {if (!context.getPackageManager().canRequestPackageInstalls()) {// 跳转到设置页面,允许安装未知来源应用Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,Uri.parse("package:" + context.getPackageName()));intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);return;}}context.startActivity(installIntent);// 注销广播接收器和内容观察者context.unregisterReceiver(downloadCompleteReceiver);if (downloadObserver != null) {context.getContentResolver().unregisterContentObserver(downloadObserver);}}/*** 下载进度观察者*/private class DownloadObserver extends ContentObserver {private final long downloadId;public DownloadObserver(Handler handler, long downloadId) {super(handler);this.downloadId = downloadId;}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);queryDownloadProgress();}private void queryDownloadProgress() {DownloadManager.Query query = new DownloadManager.Query();query.setFilterById(downloadId);try (Cursor cursor = downloadManager.query(query)) {if (cursor != null && cursor.moveToFirst()) {int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));int bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));int bytesTotal = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));if (status == DownloadManager.STATUS_RUNNING && bytesTotal > 0) {int progress = (int) ((bytesDownloaded * 100L) / bytesTotal);if (progressListener != null) {progressListener.onProgress(progress);}} else if (status == DownloadManager.STATUS_FAILED) {if (progressListener != null) {progressListener.onError("下载失败");}}}}}}/*** 下载进度监听器*/public interface DownloadProgressListener {void onProgress(int progress); // 下载进度(0-100)void onError(String message);  // 下载失败void onDownloadComplete();     // 下载完成}
}

使用示例

// 初始化工具类
DownloadUtils downloadUtils = DownloadUtils.getInstance(context);// 开始下载
downloadUtils.downloadFile("https://example.com/app-update.apk", "app-update.apk", new DownloadUtils.DownloadProgressListener() {@Overridepublic void onProgress(int progress) {Log.d(TAG, "下载进度: " + progress + "%");}@Overridepublic void onError(String message) {Log.e(TAG, "下载失败: " + message);}@Overridepublic void onDownloadComplete() {Log.d(TAG, "下载完成");}
});

优化点说明

  1. 兼容高版本 Android

    • 使用 FileProvider 提供文件的 Uri,适配 Android 7.0 及以上版本。
    • 适配 Android 8.0 及以上版本的未知来源应用安装权限。
  2. 断点续传

    • 通过 setAllowedOverMeteredsetAllowedOverRoaming 支持断点续传。
  3. 下载进度监听

    • 使用 ContentObserver 监听下载进度,并通过回调实时更新 UI。
  4. 错误处理

    • DownloadObserver 中检查下载状态,如果下载失败,通过 onError 回调通知调用方。
  5. 自动安装 APK

    • 下载完成后自动安装 APK 文件,适配高版本 Android 的权限要求。

总结

该工具类封装了 DownloadManager 的核心功能,并针对高版本 Android 进行了优化,支持断点续传、下载进度监听和自动安装 APK 文件。适用于需要文件下载和 APK 升级功能的 Android 应用。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言实现括号匹配检查及栈的应用详解

目录 栈数据结构简介 C语言实现栈 栈的初始化 栈的销毁 栈的插入 栈的删除 栈的判空 获取栈顶数据 利用栈实现括号匹配检查 总结 在编程中,经常会遇到需要检查括号是否匹配的问题,比如在编译器中检查代码的语法正确性,或者在…

【机器学习chp12】半监督学习(自我训练+协同训练多视角学习+生成模型+半监督SVM+基于图的半监督算法+半监督聚类)

目录 一、半监督学习简介 1、半监督学习的定义和基本思想 2、归纳学习 和 直推学习 (1)归纳学习 (2)直推学习 3、半监督学习的作用与优势 4、半监督学习的关键假设 5、半监督学习的应用 6、半监督学习的常见方法 7、半…

2024 年第四届高校大数据挑战赛-赛题 A:岩石的自动鉴定

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

基于WebRTC与P2P技术,嵌入式视频通话EasyRTC实现智能硬件音视频交互,适配Linux、ARM、RTOS、LiteOS

EasyRTC不仅仅是一个连接工具,更是一个经过深度优化的通信桥梁。它在嵌入式设备上进行了特殊优化,通过轻量级SDK设计、内存和存储优化以及硬件加速支持,解决了传统WebRTC在嵌入式设备上的适配难题,显著节省了嵌入式设备的资源。 1…

[c语言日寄]字符串进阶:KMP算法

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋:这是一个专注于C语言刷题的专栏,精选题目,搭配详细题解、拓展算法。从基础语法到复杂算法,题目涉及的知识点全面覆盖,助力你系统提升。无论你是初学者,还是…

Android源码学习之Overlay

在 Android Framework 开发中,Overlay 主要用于修改和替换系统或应用的资源,而无需直接修改源码,与源码解耦。Overlay 机制可以分为 两种类型: 静态 Overlay(Static Resource Overlay, SRO) 在 编译时 覆…

【MySQL】基本操作 —— DDL

目录 DDLDDL 常用操作对数据库的常用操作查看所有数据库创建数据库切换、显示当前数据库删除数据库修改数据库编码 对表的常用操作创建表数据类型数值类型日期和时间类型字符串类型 查看当前数据库所有表查看指定表的创建语句查看指定表结构删除表 对表结构的常用操作给表添加字…

网络安全需要学多久才能入门?

网络安全是一个复杂且不断发展的领域,想要入行该领域,我们需要付出足够多的时间和精力好好学习相关知识,才可以获得一份不错的工作,那么网络安全需要学多久才能入门?我们通过这篇文章来了解一下。 学习网络安全的入门时间因个人的…

【测试语言基础篇】Python基础之List列表

一、Python 列表(List) 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。 Python有6个序列的内置类型,但最常见的是列表和元组。序列都可…

编译系统设计原理概述

目录 简介 词法分析 正则表达式 有穷状态自动机 从正则表达式到有穷自动机的转换 单词识别 简介 主要介绍编译系统设计过程中涉及的原理与技术,主要分为前端设计和后端设计两 个模块。前端部分包括词法分析器、语法分析器的构建和语义分析过程的设计…

ArcGIS Pro 车牌分区数据处理与地图制作全攻略

在大数据时代,地理信息系统(GIS)技术在各个领域都有着广泛的应用,而 ArcGIS Pro 作为一款功能强大的 GIS 软件,为数据处理和地图制作提供了丰富的工具和便捷的操作流程。 车牌数据作为一种重要的地理空间数据&#xf…

前端登录鉴权全解析:主流方案对比与实现指南

文章目录 一、常见登录鉴权方式概览1.1 主流方案对比1.2 技术特性对比 二、Session/Cookie方案2.1 实现原理2.2 代码实现2.3 优缺点分析 三、JWT方案3.1 实现原理3.2 代码实现3.3 优缺点分析 四、OAuth方案4.1 实现原理4.2 代码实现4.3 优缺点分析 五、SSO方案5.1 实现原理5.2 …

算法系列之回溯算法求解数独及所有可能解

有没有对数独感兴趣的朋友呢?数独作为一款经典的逻辑游戏,其目标是在一个9x9的方格中填入数字1至9,确保每一行、每一列以及每一个3x3的子网格中都包含这些数字且不重复。尽管数独的规则看似简单,但编写一个能够自动求解数独的程序…

华为hcia——Datacom实验指南——TCP传输原理和数据段格式

什么是TCP TCP是一种可靠的端到端的传输层协议,仅应用于单波通信。 采用TCP协议作为传输方式的应用层服务,再进行数据传输前,都需要进行TCP协议的创建。 TCP报文的格式 sequence number(序列号) 占4个字节&#x…

Vlog 片头制作

打开剪映,新建草稿,导入黑色背景。 拉长时间轴,背景时常调整为4.2秒。 添加文本,输入 5 个“|”,每个中间 2 个空格,如下| | | | |,然后手动放大文本,让中间显示出四个间隔。 继续添…

【Nacos】服务发布之优雅预热上线方案

目录 一、背景二、注册时机2.1、注册机制2.2、分析源码找到注册时机 三、注册前心跳健康检测3.1、方案实施3.2、源码分析3.3、优化代码 四、流量权重配置五、总结5.1、整体完整流程:5.2、流程图:5.1、优化方案完整代码: 一、背景 有些面向广…

VXLAN 组播 RP

一、Anycast RP 在每个 VTEP 上,每个多播组都会建立一个源树 (S,G),并且在双活 Leaf 设备上到 RP 地址是 ECMP 路径。 在 PIM ASM 模式下,(S,G) 组在 VTEP 端创建。由于每个 VTEP 都能够为特定的多播组发送和接收多播流量,因此每…

【第七节】windows sdk编程:Windows 中的对话框

目录 引言 一、对话框简介 1.1 对话框的创建 1.2 基本函数 1.3 模态对话框与非模态对话框 1.4 对话框与窗口的区别 二、模态对话框编程方法 2.1 模态对话框编程 2.2 消息框 三、非模态对话框编程方法 四、综合代码案例 引言 在Windows应用程序开发中,对话…

安装并配置终端字体

1. 简介 在使用 Oh My Zsh Powerlevel10k 时,正确的字体配置至关重要。Powerlevel10k 依赖 Nerd Fonts 扩展字体,以正确显示 Git 状态、分支、时间、图标等信息。 如果没有正确配置字体,你可能会看到 乱码、问号(?&#xff09…

LeetCode - #227 基于 Swift 实现基本计算器

摘要 在这篇文章中,我们将实现一个基于 Swift 语言的基本计算器。该计算器能够解析和计算包含 、-、* 和 / 的数学表达式,并且遵循运算符的优先级规则。整数除法仅保留整数部分,不能使用 eval() 这样的内置解析方法。 描述 给你一个字符串表…