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
相关产品推荐
相关产品推荐

