[Android]应用内更新问题

问题:

以前的代码

Kotlin 复制代码
    /**
     * @param url          下载连接
     * @param listener     下载监听
     */
    fun download(url: String, onError: (Exception) -> Unit = {}, onProgress: (Int) -> Unit = {}, onDone: (File) -> Unit) {
        this.onProgress = onProgress
        val destFileDir = ctx.getExternalFilesDir("/DownloadFile")?.path ?: "/storage/emulate/0/DownloadFile" //储存下载文件的目录
        val dir = File(destFileDir)
        //dir.delete()
        if (!dir.exists()) dir.mkdirs()
        //保存文件的绝对路径
        val destFileName = getNameFromUrl(url)
        val file = File(dir, destFileName)
        if (file.exists()) file.deleteOnExit()
        okHttpClient.newCall(Request.Builder().url(url).build()).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                uiHandler.post { onError(e) }
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                response.body?.byteStream()?.let {
                    val sum = response.body!!.contentLength()
                    FileOutputStream(file).withInputStream(it) { p ->
                        uiHandler.post {
                            if (p >= sum) onDone(file)
                        }
                    }
                }
            }
        })
    }

权限会在andorid 12被废除,导致下载apk保存失败,导致应用无法更新

Kotlin 复制代码
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许应用从外部存储设备读取数据。 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 允许应用写入内部存储。这通常用于保存临时文件或应用私有数据。 -->
    <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /> <!-- 允许应用从内部存储读取数据。 -->
    <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> <!-- 允许应用录制音频。这对于语音记录应用或需要音频输入功能的应用非常重要。 -->

WRITE_EXTERNAL_STORAGE 权限在 Android 11(API 级别 30)及更高版本中被限制,并且在 Android 12(API 级别 31)中被彻底废弃。从 Android 11 开始,即使应用程序请求了 WRITE_EXTERNAL_STORAGE 权限,系统也会将其视为 READ_EXTERNAL_STORAGE 权限。因此,在 Android 11 及更高版本中,应用程序将无法直接写入外部存储,而必须通过其他方式来访问共享文件系统。

因此,从 Android 11 开始,开发者需要使用更安全的 Scoped Storage 或其他适当的方式来处理文件访问,以兼容最新的 Android 版本。

解决:

将apk保存到app的沙盒,这样就可以不需要权限,可以实现应用内更新.

Kotlin 复制代码
package com.mofsaas.www.appupdate

import android.app.Activity
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.azhon.appupdate.listener.OnDownloadListenerAdapter
import com.azhon.appupdate.manager.DownloadManager
import com.mofsaas.www.BuildConfig
import com.mofsaas.www.R
import java.io.File

private fun downMsg(progress: Int) = "下载进度:$progress%,请稍候"
fun Activity.startAppUpdate(apkUrl: String) {
    val ctx = this

    val url =
        if (BuildConfig.DEBUG) "https://downv6.qq.com/qqweb/QQ_1/android_apk/Android_9.0.81_64.apk" else apkUrl

    if (url.isBlank()) {
        Toast.makeText(ctx, "无效的更新地址", Toast.LENGTH_SHORT).show()
        return
    }

    val builder = AlertDialog.Builder(ctx).apply {
        setTitle("下载安装包")
        setMessage(downMsg(0))
        setNegativeButton("取消") { dialog, _ ->
            dialog.dismiss()
            // 不在此处处理逻辑,统一到DialogFragment.onDismiss中处理
        }
    }
    val dlg = builder.create()

    fun downMsg(progress: Int) = "下载进度:$progress%,请稍候"

    val apkName = "appupdate.apk"
    // 可优化部分
    // 1.增加MD5值,减少下载次数
    val manager = DownloadManager.Builder(ctx).run {
        apkUrl(url)
        apkName(apkName)
        smallIcon(R.mipmap.ic_launcher)
        showBgdToast(false)
        onDownloadListener(object : OnDownloadListenerAdapter() {
            override fun start() {
                Toast.makeText(ctx, "开始下载新版本", Toast.LENGTH_SHORT).show()
            }

            override fun done(apk: File) {
                dlg.setTitle("安装")
                dlg.setMessage("开始安装,请允许相关授权")
            }

            override fun cancel() {
                dlg.dismiss()
            }

            override fun error(e: Throwable) {
                Toast.makeText(ctx, "下载发生错误", Toast.LENGTH_SHORT).show()
                dlg.dismiss()
            }

            override fun downloading(max: Int, progress: Int) {
                val curr = (progress / max.toDouble() * 100.0).toInt()
                //Toast.makeText(ctx, "downloading $max $progress ${downMsg(curr)}", Toast.LENGTH_SHORT).show()
                dlg.setMessage(downMsg(curr))
            }
        })
        build()
    }

    manager.download()

    dlg.apply {
        setCanceledOnTouchOutside(false)
        setCancelable(false)
        setOnDismissListener {
            manager.cancel()
            manager.release()
            Toast.makeText(ctx, "更新已取消", Toast.LENGTH_SHORT).show()
        }
    }

    dlg.show()
}
相关推荐
三少爷的鞋5 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)9 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码11 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓16 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程16 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer