Android 12+华为设备下载PDF报EACCESS权限拒绝问题求助
解决华为API32设备PDF下载权限拒绝问题
问题原因
你的代码使用Environment.getExternalStoragePublicDirectory直接写入公共Download目录,在API29+的作用域存储机制下,华为API32设备严格限制了这种直接文件操作的权限,从而触发EACCESS(Permission Denied)错误。
解决方案
改用Android官方推荐的MediaStore API写入公共Download目录,同时做好权限适配:
1. 配置Manifest权限
在AndroidManifest.xml中添加权限声明:
<!-- 适配API23-28的写入权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- API33+ 可选:避免下载通知无法显示 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" android:maxSdkVersion="33" />
2. 替换文件写入逻辑
用MediaStore替代直接的FileOutputStream操作,确保符合作用域存储规范:
fun savePdfToDownloads(context: Context, base64Pdf: String, applicationId: String) { val fileName = context.getString( R.string.product_webview_file_name, DateUtils.getTodayDate(DATE_PATTERN_NO_SPACE) ) // 解码Base64格式的PDF数据 val pdfBytes = Base64.decode( base64Pdf.replaceFirst("^data:application/pdf;base64,".toRegex(), ""), Base64.DEFAULT ) // 配置文件元数据 val contentValues = ContentValues().apply { put(MediaStore.Downloads.DISPLAY_NAME, fileName) put(MediaStore.Downloads.MIME_TYPE, "application/pdf") put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) put(MediaStore.Downloads.IS_PENDING, 1) // 标记为待处理,避免媒体扫描干扰写入 } val resolver = context.contentResolver val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) uri?.let { // 写入PDF内容 resolver.openOutputStream(it)?.use { outputStream -> outputStream.write(pdfBytes) outputStream.flush() } // 标记文件为已完成,让系统识别到该文件 contentValues.clear() contentValues.put(MediaStore.Downloads.IS_PENDING, 0) resolver.update(it, contentValues, null, null) Toast.makeText( context, context.getString(R.string.product_webview_file_downloaded_label), Toast.LENGTH_SHORT ).show() // 获取文件路径用于分享 val filePath = getRealPathFromUri(context, uri) filePath?.let { path -> val pdfFile = File(path) FileUtils.sharePdfFile(context = context, applicationId = applicationId, file = pdfFile) } } ?: run { Toast.makeText(context, "PDF保存失败", Toast.LENGTH_SHORT).show() } } // 辅助方法:从MediaStore Uri获取真实文件路径 private fun getRealPathFromUri(context: Context, uri: Uri): String? { var filePath: String? = null val projection = arrayOf(MediaStore.Downloads.DATA) context.contentResolver.query(uri, projection, null, null, null)?.use { cursor -> if (cursor.moveToFirst()) { val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Downloads.DATA) filePath = cursor.getString(columnIndex) } } return filePath }
3. 动态权限适配(API23-28)
针对Android 6.0到10的设备,需要动态申请写入权限:
fun checkAndRequestWritePermission(context: Context, requestCode: Int) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { if (ContextCompat.checkSelfPermission( context, Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( context as Activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), requestCode ) } } }
说明
MediaStore是API29+官方推荐的公共目录文件操作方式,兼容性更强,能绕过厂商的权限限制。- 避免使用
Environment.getExternalStoragePublicDirectory这类已废弃的API,减少适配问题。
内容的提问来源于stack exchange,提问作者IndianBackpacker
相关产品推荐
相关产品推荐

