目录

[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()
}
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
zhangphil14 分钟前
Android ViewStub显示VISIBLE与消失GONE,Kotlin
android·kotlin
API小爬虫1 小时前
利用 PHP 爬虫获取京东商品详情 API 返回值说明及代码示例
android·爬虫·php
_一条咸鱼_8 小时前
深入剖析 Android Hilt 框架的依赖生命周期管理模块(六)
android·kotlin·android jetpack
_一条咸鱼_9 小时前
深入剖析 Android Hilt 的模块配置与初始化模块(五)
android·kotlin·android jetpack
斗锋在干嘛10 小时前
Android里面开子线程的方法
android
yangshuo128111 小时前
WSA(Windows 安卓子系统)过检测教程
android
jiet_h12 小时前
Kotlin 中 集合 Collection 的扩展方法完全指南
android·开发语言·kotlin
吃饭了呀呀呀12 小时前
🐳 《Android》 安卓开发教程 - 自定义 Toast
android·后端
斗锋在干嘛14 小时前
在Android中问AMS 和 PMS的区别和作用
android
雯0609~15 小时前
PHP:将关联数组转换为索引数组的完整示例
android·oracle·php