问题:
以前的代码
/*** @param url 下载连接* @param listener 下载监听*/fun download(url: String, onError: (Exception) -> Unit = {}, onProgress: (Int) -> Unit = {}, onDone: (File) -> Unit) {this.onProgress = onProgressval 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保存失败,导致应用无法更新
<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的沙盒,这样就可以不需要权限,可以实现应用内更新.
package com.mofsaas.www.appupdateimport 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.Fileprivate fun downMsg(progress: Int) = "下载进度:$progress%,请稍候"
fun Activity.startAppUpdate(apkUrl: String) {val ctx = thisval url =if (BuildConfig.DEBUG) "https://downv6.qq.com/qqweb/QQ_1/android_apk/Android_9.0.81_64.apk" else apkUrlif (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()
}