[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()
}
相关推荐
李堇2 小时前
android滚动列表VerticalRollingTextView
android·java
lxysbly3 小时前
n64模拟器安卓版带金手指2026
android
游戏开发爱好者86 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
王码码20356 小时前
Flutter for OpenHarmony 实战之基础组件:第三十一篇 Chip 系列组件 — 灵活的标签化交互
android·flutter·交互·harmonyos
黑码哥7 小时前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓7 小时前
[JDBC]元数据
android
独行soc7 小时前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能7 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿7 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc8 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮