Android应用自动更新触发SecurityException权限拒绝问题
应用自动更新权限错误修复方案
问题背景
已成功下载APK文件,手动点击可正常完成应用更新,说明APK未损坏且可正常安装。但实现同一应用内自动更新(无需Google Play Store,类似Google应用内更新逻辑)时,触发权限错误,日志如下:
FATAL EXCEPTION: AsyncTask #1 Process: com.google.android.packageinstaller, PID: 15764 java.lang.RuntimeException: An error occurred while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:325) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354) at java.util.concurrent.FutureTask.setException(FutureTask.java:223) at java.util.concurrent.FutureTask.run(FutureTask.java:242) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761) Caused by: java.lang.SecurityException: Permission Denial: opening provider androidx.core.content.FileProvider from ProcessRecord{f4c3105 15764:com.google.android.packageinstaller/u0a18} (pid=15764, uid=10018) that is not exported from uid 10099 at android.os.Parcel.readException(Parcel.java:1684) at android.os.Parcel.readException(Parcel.java:1637) at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:4199) at android.app.ActivityThread.acquireProvider(ActivityThread.java:5508) at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2239) at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1520) at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1133) at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:986) at android.content.ContentResolver.openInputStream(ContentResolver.java:706) at com.android.packageinstaller.PackageInstallerActivity$StagingAsyncTask.doInBackground(PackageInstallerActivity.java:792) at com.android.packageinstaller.PackageInstallerActivity$StagingAsyncTask.doInBackground(PackageInstallerActivity.java:783) at android.os.AsyncTask$2.call(AsyncTask.java:305) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761)
当前实现代码
自动安装触发代码
private void openDownloadedAttachment(final Context context, final long downloadId, Uri uri) { DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); Cursor cursor = downloadManager.query(query); if (cursor.moveToFirst()) { @SuppressLint("Range") int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); @SuppressLint("Range") String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); @SuppressLint("Range") String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)); if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) { try { downloadLocalUri = "/storage/emulated/0/AATv2.apk"; openDownloadedAttachment(context, Uri.parse(downloadLocalUri), downloadMimeType); Intent promptInstall = new Intent(Intent.ACTION_VIEW); promptInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); promptInstall.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); promptInstall.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION ); File file = new File(downloadLocalUri); uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName()+"provider", file); promptInstall.setDataAndType(uri, "application/vnd.android.package-archive"); promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(promptInstall); } catch (ActivityNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } cursor.close(); }
AndroidManifest配置
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.DELETE_PACKAGES" tools:ignore="ProtectedPermissions" /> <application> ..... <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> </application>
provider_paths.xml配置
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
问题原因
- Intent Flag被覆盖:先调用
addFlags()添加权限相关Flag,之后调用setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)会清空之前所有Flag,导致PackageInstaller无法获取APK文件的访问权限。 - 无限递归调用:代码中手动指定
downloadLocalUri后,再次调用openDownloadedAttachment触发无限递归,干扰正常逻辑。 - 无用权限声明:
INSTALL_PACKAGES和DELETE_PACKAGES是系统级权限,普通应用无法获取,声明后无实际作用。
修复方案
1. 修正Intent Flag设置
使用addFlags()一次性添加所有需要的Flag,避免权限Flag被覆盖:
promptInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
2. 删除递归调用代码
移除手动指定路径后的openDownloadedAttachment调用,避免无限递归。
3. 移除无用权限
删除INSTALL_PACKAGES和DELETE_PACKAGES权限声明,普通应用无法使用这些权限。
修正后的完整代码
private void openDownloadedAttachment(final Context context, final long downloadId, Uri uri) { DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); Cursor cursor = downloadManager.query(query); if (cursor.moveToFirst()) { @SuppressLint("Range") int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); @SuppressLint("Range") String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); @SuppressLint("Range") String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)); if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) { try { Intent promptInstall = new Intent(Intent.ACTION_VIEW); // 一次性添加所有必要Flag promptInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); File file = new File(downloadLocalUri); uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file); promptInstall.setDataAndType(uri, "application/vnd.android.package-archive"); startActivity(promptInstall); } catch (ActivityNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } cursor.close(); }
修正后的AndroidManifest权限部分
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application> ..... <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> </application>
内容的提问来源于stack exchange,提问作者Ibanez1408
相关产品推荐
相关产品推荐

