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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 19:53:11