[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()
}
相关推荐
当归10243 小时前
接雨水的算法
android·java·算法
猿小帅014 小时前
androidnetflix手机版遥控器操作
android·framework
l and5 小时前
Android Http-server 本地 web 服务
android
CYRUS STUDIO5 小时前
使用 AndroidNativeEmu 调用 JNI 函数
android·汇编·arm开发·arm·逆向·jni
消失的旧时光-19435 小时前
Android 串口通信
android
风浅月明5 小时前
[Android]使用WorkManager循环执行任务
android
_extraordinary_7 小时前
Linux权限(一)
android·linux·excel
人生!?8 小时前
给小米/红米手机root(工具基本为官方工具)——KernelSU篇
android·linux·智能手机
古苏9 小时前
Android输入事件传递流程系统源码级解析
android