[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()
}
相关推荐
LSL666_3 小时前
5 Repository 层接口
android·运维·elasticsearch·jenkins·repository
alexhilton7 小时前
在Jetpack Compose中创建CRT屏幕效果
android·kotlin·android jetpack
2501_940094029 小时前
emu系列模拟器最新汉化版 安卓版 怀旧游戏模拟器全集附可运行游戏ROM
android·游戏·安卓·模拟器
下位子9 小时前
『OpenGL学习滤镜相机』- Day9: CameraX 基础集成
android·opengl
参宿四南河三11 小时前
Android Compose SideEffect(副作用)实例加倍详解
android·app
火柴就是我12 小时前
mmkv的 mmap 的理解
android
没有了遇见12 小时前
Android之直播宽高比和相机宽高比不支持后动态获取所支持的宽高比
android
shenshizhong12 小时前
揭开 kotlin 中协程的神秘面纱
android·kotlin
vivo高启强13 小时前
如何简单 hack agp 执行过程中的某个类
android
沐怡旸13 小时前
【底层机制】 Android ION内存分配器深度解析
android·面试