You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin Firebase APK自动更新解析包出错求助

问题描述

我有一款未上架Google Play的Android应用,已实现从Firebase Storage检测更新并触发下载的逻辑,检测功能正常,但下载完成后启动安装时弹出**"an error occurred while parsing the package"**提示,手动安装该APK可正常运行。

现有代码

检测更新逻辑

private fun checkUpdates(){
    val storage = FirebaseStorage.getInstance()
    val storageRef = storage.reference
    val rootRef = storageRef.root
    rootRef.listAll().addOnSuccessListener { listResult ->
        for (item in listResult.items) {
            // item is a StorageReference for a file
            val fileName = item.name
            val version = fileName.substring(fileName.indexOf("app_v") + 5, fileName.indexOf(".apk"))
            if (version.replace(".", "") > BuildConfig.VERSION_CODE.toString().replace(".", "")) {
                val builder = AlertDialog.Builder(this)
                builder.setMessage(R.string.new_up_av)
                builder.setPositiveButton(R.string.install) { _, _ ->
                    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_STORAGE
                        )
                        download(version)
                    } else {
                        download(version)
                    }
                }
                val dialog = builder.create()
                dialog.show()
            }
            else{
                Toast.makeText(this, R.string.no_updates, Toast.LENGTH_LONG).show()
            }
        }
    } .addOnFailureListener{
        Toast.makeText(this, "ERROR", Toast.LENGTH_SHORT).show()
    }
}

下载函数

private fun download(version: String) {
    Toast.makeText(this, R.string.download_updates, Toast.LENGTH_SHORT).show()
    val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    val downloadUri = Uri.parse(//url for apk here.)
    val request = DownloadManager.Request(downloadUri)
        .setTitle(getString(R.string.download_updates))
        .setDescription(getString(R.string.app_name))
    val downloadId = downloadManager.enqueue(request)

    // Check if the download was successful
    if (downloadId == -1L) {
        // Download failed, show an error message
        Toast.makeText(this, "download failed", Toast.LENGTH_SHORT).show()
        return
    }

    // Listen for the download to complete and install the update
    val downloadReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
            if (downloadId == id) {
                installApk(context, "app_v$version.apk")
            }
        }
    }
    registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}

安装函数

fun installApk(context: Context, fileName: String) {
    val file = File(Environment.getExternalStorageDirectory().toString() + "/Download/" + filename)
    val fileUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
    val intent = Intent(Intent.ACTION_VIEW)
    intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    context.startActivity(intent)
}

Manifest Provider配置

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths" />
</provider>

provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="downloads" path="Download/" />
</paths>

解决方案

1. 修复安装函数的变量大小写错误

installApk参数为fileName,但代码中拼接路径时误用了小写的filename,导致找不到目标文件,触发解析错误。同时增加文件存在性校验:

fun installApk(context: Context, fileName: String) {
    // 修正变量名大小写
    val file = File(Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName)
    
    // 校验文件状态
    if (!file.exists() || !file.canRead()) {
        Toast.makeText(context, "下载的APK文件不存在或无法读取", Toast.LENGTH_SHORT).show()
        return
    }
    
    val fileUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
    val intent = Intent(Intent.ACTION_VIEW)
    intent.setDataAndType(fileUri, "application/vnd.android.package-archive")
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    context.startActivity(intent)
}

2. 准确获取DownloadManager的下载文件路径

避免硬编码路径,通过DownloadManager查询实际下载文件的Uri,适配不同系统的存储路径差异:

// 修改download函数中的广播接收器逻辑
val downloadReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
        if (downloadId == id) {
            val query = DownloadManager.Query().setFilterById(downloadId)
            val cursor = downloadManager.query(query)
            if (cursor.moveToFirst()) {
                val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
                if (status == DownloadManager.STATUS_SUCCESSFUL) {
                    val downloadUriStr = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
                    if (!downloadUriStr.isNullOrEmpty()) {
                        val file = File(Uri.parse(downloadUriStr).path!!)
                        installApk(context, file.name)
                    } else {
                        Toast.makeText(context, "无法获取下载文件路径", Toast.LENGTH_SHORT).show()
                    }
                } else {
                    Toast.makeText(context, "下载未完成,状态码:$status", Toast.LENGTH_SHORT).show()
                }
            }
            cursor.close()
        }
    }
}
registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))

3. 适配Android 10+分区存储

Android 10及以上不再支持WRITE_EXTERNAL_STORAGE权限,修改DownloadManager请求指定明确的下载目录:

// 更新download函数的请求配置
val request = DownloadManager.Request(downloadUri)
    .setTitle(getString(R.string.download_updates))
    .setDescription(getString(R.string.app_name))
    // 指定下载到公共下载目录,兼容Android 10+
    .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app_v$version.apk")

4. 注销动态注册的广播接收器

避免内存泄漏,在Activity销毁时注销广播接收器:

// 定义全局变量存储接收器实例
private var downloadReceiver: BroadcastReceiver? = null

// 修改download函数中的接收器赋值
downloadReceiver = object : BroadcastReceiver() {
    // ...原有逻辑...
}
registerReceiver(downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))

// 在Activity的onDestroy方法中注销
override fun onDestroy() {
    super.onDestroy()
    downloadReceiver?.let { unregisterReceiver(it) }
}

5. 校验APK基础兼容性

  • 确保下载的APK与当前应用使用相同签名,否则系统会拒绝安装
  • 确认APK的最低SDK版本不高于当前设备的SDK版本

内容的提问来源于stack exchange,提问作者nope

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:35:50