Android 应用内下载 APK Demo(Kotlin语言)

应用内下载更新包

包含:
权限检测、通知栏生成进度、实时进度反馈、下载完成自动安装、通知栏点击安装

效果

代码

第三方依赖(仅仅主要提供上下文对象Context):

implementation 'com.blankj:utilcodex:1.30.6'

下载工具类(直接拷贝粘贴用,包的):

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context.NOTIFICATION_SERVICE
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.text.TextUtils
import androidx.core.app.NotificationCompat
import androidx.core.app.TaskStackBuilder
import androidx.core.content.FileProvider
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.PermissionUtils
import com.blankj.utilcode.util.PermissionUtils.SimpleCallback
import com.blankj.utilcode.util.ToastUtils
import com.blankj.utilcode.util.Utils
import com.fxyandtjh.voiceaccounting.R
import com.fxyandtjh.voiceaccounting.entity.ApkProgress
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.URLclass DownLoadApk private constructor() {private lateinit var builder: NotificationCompat.Builderprivate lateinit var notification: Notificationprivate lateinit var notificationManager: NotificationManagerprivate var lastUpdateNoticeTime = 0Lcompanion object {val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {DownLoadApk()}}fun download(url: String, callBack: (ApkProgress) -> Unit) {PermissionUtils.permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE).callback(object : SimpleCallback {override fun onGranted() {startDownLoad(url, callBack)}override fun onDenied() {ToastUtils.showShort(Utils.getApp().getText(R.string.deny_permission_update))}}).request()}// 开始下载private fun startDownLoad(url: String, callBack: (ApkProgress) -> Unit) {if (TextUtils.isEmpty(url)) {return}// 获取文件名val fileName = File(URL(url).path).nameif (TextUtils.isEmpty(fileName)) {return}// 创建通知createNotification()// 下载外部公有目录: /storage/emulated/0/Android/data/包名/files/Download/val targetFile =Utils.getApp().getExternalFilesDir("${Environment.DIRECTORY_DOWNLOADS}${File.separator}$fileName")// 如果这个目录下 已经有同名文件了 那么要移除文件targetFile?.let {if (it.exists()) {it.delete()}}val a = targetFile?.absolutePath?.replace(fileName, "") ?: ""Downloader(a, fileName).downLoad(url, { curBytes, totalBytes ->callBack.invoke(ApkProgress(bytes = curBytes, totalBytes = totalBytes))// 更新通知进度updateNotificationProgress(curBytes, totalBytes)}, { file ->callBack.invoke(ApkProgress(path = file.path))//  点击通知进行安装installAPKWhenNoticeClicked(file)}, { _ ->callBack.invoke(ApkProgress(code = 400))})}// 生成通知栏private fun createNotification() {notificationManager =Utils.getApp().getSystemService(NOTIFICATION_SERVICE) as NotificationManager// 获取渠道IDval channel = NotificationChannel("app_channel_ID","app_channel_NAME",NotificationManager.IMPORTANCE_DEFAULT)notificationManager.createNotificationChannel(channel)// 获取应用图标val packageName = AppUtils.getAppPackageName()var iconId = 0try {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {iconId = Utils.getApp().packageManager.getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(0)).icon} else {iconId = Utils.getApp().packageManager.getApplicationInfo(packageName, 0).icon}} catch (e: Exception) {e.printStackTrace()iconId = R.mipmap.logo}builder = NotificationCompat.Builder(Utils.getApp(), "app_channel_ID").setSmallIcon(iconId).setContentTitle("新版本更新").setContentText("正在下载...").setProgress(100, 0, false)notification = builder.build()notificationManager.notify(1, notification)}// 更新通知进度private fun updateNotificationProgress(curBytes: Long, totalBytes: Long) {var tipText = ""if (curBytes == totalBytes && totalBytes != 0L) {// 下载完成tipText = "下载完成(点击安装)。"builder.setProgress(0, 0, false)} else if (totalBytes == 0L) {tipText = "正在下载: 0%"builder.setProgress(100, 0, false)} else {val progress = curBytes * 100 / totalBytestipText = "正在下载:$progress%"builder.setProgress(100, progress.toInt(), false)}builder.setContentText(tipText)// 下载过程中,进度反馈会非常频繁,频繁更新通知将会消耗大量内存 这里做一个限制 每250毫秒更新一次进度limitUpdateNotificationRate(curBytes != totalBytes && totalBytes != 0L)}// 限制更新通知进度的频率private fun limitUpdateNotificationRate(shouldLimit: Boolean) {val currentTime = System.currentTimeMillis()if (!shouldLimit || currentTime - lastUpdateNoticeTime > 250) {// 250 毫秒更新一次lastUpdateNoticeTime = currentTimenotificationManager.notify(1, builder.build())}}// 安装应用程序private fun installAPKWhenNoticeClicked(file: File, clickNotice: Boolean = false) {val intent = Intent(Intent.ACTION_VIEW)intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)val uri = FileProvider.getUriForFile(Utils.getApp(),AppUtils.getAppPackageName() + ".fileprovider",file)intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)intent.setDataAndType(uri, "application/vnd.android.package-archive")val stackBuilder = TaskStackBuilder.create(Utils.getApp())stackBuilder.addNextIntentWithParentStack(intent)val resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)builder.setContentIntent(resultPendingIntent)builder.setAutoCancel(true)notificationManager.notify(1, builder.build())}// 内部下载类private class Downloader(val targetPath: String, val targetName: String) {fun downLoad(url: String,onProgress: (Long, Long) -> Unit,onFinished: (File) -> Unit,onError: (Exception) -> Unit) {val client = OkHttpClient.Builder().protocols(listOf(Protocol.HTTP_1_1)).build()val request = Request.Builder().url(url).get().build()client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {onError.invoke(e)}override fun onResponse(call: Call, response: Response) {val file = saveToFile(response) { curByte, totalByte ->onProgress.invoke(curByte, totalByte)}file?.let {if (it.exists()) {onFinished(it)} else {onError(RuntimeException("Failed to save file"))}} ?: onError(RuntimeException("Failed to save file"))}})}private fun saveToFile(response: Response,onProgress: (Long, Long) -> Unit): File? {if (TextUtils.isEmpty(targetPath) || TextUtils.isEmpty(targetName) || response.body == null) {return null}// 设置缓冲大小val buff = ByteArray(40960)var len = 0val inputStream = response.body!!.byteStream()val total: Long = response.body!!.contentLength()var sum = 0Lval dir = File(targetPath)if (!dir.exists()) {dir.mkdirs()}val targetFile = File(dir, targetName)val fileOutputStream = FileOutputStream(targetFile)try {while (inputStream.read(buff).also { len = it } != -1) {sum += lenfileOutputStream.write(buff, 0, len)onProgress(sum, total)}} catch (e: IOException) {e.printStackTrace()}fileOutputStream.flush()fileOutputStream.close()inputStream.close()return targetFile}}
}

实体类:

data class ApkProgress(val bytes: Long = 0L, // 当前下载 字节数val totalBytes: Long = 0L, // 总共需要下载的字节数val path: String = "", // 下载完成后,文件保存的绝对路径,未下载完成时,为""空字符串val code: Int = 200  // 下载结果码 异常非 200
)

使用

downloadLink就是APK下载地址,apkProgess通过回调方式返回

DownLoadApk.instance.download(downloadLink) { apkProgress ->}

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

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

相关文章

【Python】python员工信息管理系统(数据库版本)(GUI界面+数据库文件+源码)【独一无二】

在这里插入图片描述> 👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈&#xff…

爬虫提速!用Python实现多线程下载器!

✨ 内容: 在网络应用中,下载速度往往是用户体验的关键。多线程下载可以显著提升下载速度,通过将一个文件分成多个部分并行下载,可以更高效地利用带宽资源。今天,我们将通过一个实际案例,学习如何用Python实…

2024年软件系统与信息处理国际会议(ICSSIP 2024)即将召开!

2024年软件系统与信息处理国际会议(ICSSIP 2024)将于2024年10月25-27日在中国昆明举行。引领技术前沿,共谋创新未来。ICSSIP 2024将汇聚来自世界各地的专家学者,他们将在会上分享最新的研究成果、技术突破及实践经验。会议议题涵盖…

昇思25天学习打卡营第1天|快速入门-实现一个简单的深度学习模型

目录 实验环境 Jupyter云上开发环境使用 导包 处理数据集 网络构建 模型训练 评估模型性能 保存模型 加载模型 预测推理 实验环境 02-快速入门.ipynb (4) - JupyterLab (mindspore.cn) 规格:4u 16G 20G 镜像:py39-ms2.3.0rc1 特性&#xff1…

【计算机网络】IP分片实验

一:实验目的 1:理解IP数据报分片的工作原理。 2:理解IP协议报文类型和格式。 二:实验仪器设备及软件 硬件:RCMS-C服务器、网线、Windows 2019/2003操作系统的计算机等。 软件:记事本、WireShark、Chrom…

Pc端vue2实现横向纵向鼠标滚动布局

类似uniaapp中的scroll-view组件,可横向可竖向,样式需要自己跳整一下 横向:(鼠标按下滑动里面的元素,可滑动,滚动条和左右都可以调整) 纵向: 代码实现:主页面引入组件 <template><div><!-- 调用组件 --><!-- vertical 垂直 写宽高 例如: widt…

失业潮下,有人靠天工AI做副业年入10万?

前言 你好&#xff0c;我是咪咪酱 这篇文章总结2个AI副业项目&#xff0c;不用写代码&#xff0c;就能做的2个副业项目。 第一&#xff1a;AI生成微信表情包&#xff0c;上传到微信表情包平台等&#xff0c;坚持下去&#xff0c;会有可观的收入。 第二&#xff1a;AI生成连载…

MQ消息队列+Lua 脚本实现异步处理下单流程

具体实现和代码可参考我以前做过的笔记&#xff1a;《黑马点评》异步秒杀优化|消息队列 回顾一下下单流程&#xff1a; 用户发起请求 会先请求Nginx,Nginx反向代理到Tomcat&#xff0c;而Tomcat中的程序&#xff0c;会进行串行工作&#xff0c; 分为以下几个操作&#xff1…

KamaCoder 98. 所有可到达路径 + LC 797. All Paths From Source to Target

题目要求 给定一个有 n 个节点的有向无环图&#xff0c;节点编号从 1 到 n。请编写一个函数&#xff0c;找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。 输入描述 第一行包含两个整数 N&#xff0c;M&#xff0c;表示图中拥有 N 个节点&…

Apache Nifi挂接MQTT与Kafka实践

目录 1. 说明&#xff1a; 2. 方案设计&#xff1a; 2.1 资源配置&#xff1a; 2.2 交互Topics: 3. 实现步骤 3.1 Nifi 桌面 3.2 MqttToKafka 3.2.1 配置 3.2.2 测试 3.2.3 结果 3.3 KafkaToMqtt 3.3.1 配置 3.3.1 测试 3.3.1 结果 ​编辑 4. 总结&#xff…

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据 &#x1f4da;MT6816相关资料&#xff08;来自商家的相关资料&#xff09;&#xff1a; 资料&#xff1a;https://pan.baidu.com/s/1CAbdLBRi2dmL4D7cFve1XA?pwd8888 提取码&#xff1a;8888&#x1f4cd;驱动代码编写&…

计科录取75人!常州大学计算机考研考情分析!

常州大学&#xff08;Changzhou University&#xff09;&#xff0c;简称“常大”&#xff0c;位于江苏省常州市&#xff0c;是江苏省人民政府与中国石油天然气集团有限公司、中国石油化工集团有限公司及中国海洋石油集团有限公司共建的省属全日制本科院校&#xff0c;为全国深…

repo中的default.xml文件project name为什么一样?

文章目录 default.xml文件介绍为什么 name 是一样的&#xff0c;path 不一样&#xff1f;总结 default.xml文件介绍 在 repo 工具的 default.xml 文件中&#xff0c;定义了多个 project 元素&#xff0c;每个元素都代表一个 Git 仓库。 XML 定义了多个不同的 project 元素&…

Vue常用指令及其生命周期

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 目录 1.常用指令 1.1 v-bind 1.2 v-model 注意事项 1.3 v-on 注意事项 1.4 v-if / v-else-if / v-else 1.5 v-show 1.6 v-for 无索引 有索引 生命周期 定义 流程 1.常用指令 Vue当中的指令…

【MySQL进阶篇】锁:全局锁、表级锁以及行级锁

一、锁的概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须要解决的一个问题&am…

vs2019配置MySQL记录

vs2019配置MySQL记录 一、安装MySQL 参考&#xff1a;MySQL5.5.19的安装步骤 基本上就是一路默认安装就行。 二、验证 左下角打开MySQL 输入秘密能看到如下界面&#xff0c;即表示MySQL安装成功 三、安装vs2019的MySQL驱动 这里主要参考&#xff1a;Visual Studio 201…

MySQL练习03

题目 步骤 创建数据库 create database mydb11_stu; #创建 use mydb11_stu; #使用 创建表 student表 create table student( id int(10) not null unique primary key, name varchar(20) not null, sex varchar(4),birth year, department varchar(20), address var…

AR 眼镜之-充电动画定制-实现方案

目录 &#x1f4c2; 前言 AR 眼镜系统版本 充电动画 1. &#x1f531; 技术方案 1.1 方案介绍 1.2 实现方案 关机充电动画 亮屏/锁屏充电动画 2. &#x1f4a0; 关机充电动画 2.1 关机充电动画核心处理类与路径 2.2 实现细节 步骤一&#xff1a;1&#xff09;定制 …

从零开始学习网络安全渗透测试之基础入门篇——(二)Web架构前后端分离站Docker容器站OSS存储负载均衡CDN加速反向代理WAF防护

Web架构 Web架构是指构建和管理Web应用程序的方法和模式。随着技术的发展&#xff0c;Web架构也在不断演进。当前&#xff0c;最常用的Web架构包括以下几种&#xff1a; 单页面应用&#xff08;SPA&#xff09;&#xff1a; 特点&#xff1a;所有用户界面逻辑和数据处理都包含…

VSCode切换默认终端

我的VSCode默认终端为PowerShell&#xff0c;每次新建都会自动打开PowerShell。但是我想让每次都变为cmd&#xff0c;也就是Command Prompt 更改默认终端的操作方法如下&#xff1a; 键盘调出命令面板&#xff08;CtrlShiftP&#xff09;中,输入Terminal: Select Default Prof…