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

文档扫描APP无法获取Manage External Storage权限求助

解决Android文档扫描App无法申请MANAGE_EXTERNAL_STORAGE权限的问题

问题根源

  • Android 11(API 30)及以上版本,MANAGE_EXTERNAL_STORAGE属于特殊权限,不能通过普通的requestPermissions()方法弹出权限请求框,必须引导用户跳转到应用的系统设置页面手动开启。
  • 原代码的权限检查逻辑用了&&,意味着只有当三个权限都未授予时才会触发申请,这会导致部分权限已授予时,未处理剩余未授予的权限,尤其是MANAGE权限的特殊申请流程被忽略。

解决步骤

1. 确保Manifest中声明权限

在AndroidManifest.xml中添加以下权限声明:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" />

注:WRITE_EXTERNAL_STORAGE在API 30+已被废弃,配合maxSdkVersion="29"适配旧版本;tools:ignore="ScopedStorage"用于忽略Lint警告。

2. 修改权限申请逻辑

替换原按钮点击事件中的权限检查与申请代码,区分普通存储权限和特殊的MANAGE权限:

// 在setupScanner()函数内添加辅助方法
private fun hasRequiredPermissions(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        Environment.isExternalStorageManager()
    } else {
        ContextCompat.checkSelfPermission(
            this,
            android.Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(
                    this,
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED
    }
}

private fun launchScanner(scanner: GmsDocumentScanner, scannerLauncher: ActivityResultLauncher<IntentSenderRequest>) {
    scanner.getStartScanIntent(this@MainActivity).addOnSuccessListener {
        scannerLauncher.launch(
            IntentSenderRequest.Builder(it).build()
        )
    }.addOnFailureListener {
        Toast.makeText(
            applicationContext,
            it.message,
            Toast.LENGTH_SHORT,
        ).show()
    }
}

// 替换原按钮点击逻辑
Button(onClick = {
    when {
        hasRequiredPermissions() -> {
            launchScanner(scanner, scannerLauncher)
        }
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager() -> {
            // 引导用户跳转到系统设置开启全文件访问权限
            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
                data = Uri.parse("package:${packageName}")
            }
            startActivity(intent)
        }
        else -> {
            // 处理Android 10及以下的读写权限申请
            ActivityCompat.requestPermissions(
                this@MainActivity,
                arrayOf(
                    android.Manifest.permission.READ_EXTERNAL_STORAGE,
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE
                ),
                1
            )
        }
    }
}, modifier = Modifier.background(Color.White)) {
    Text(text = "Scan PDF")
}

3. 处理权限申请结果

在Activity中重写权限回调方法,或者用Compose的权限Launcher处理:

// 方式1:Activity重写回调
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == 1) {
        if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
            launchScanner(scanner, scannerLauncher)
        } else {
            Toast.makeText(this, "需要存储权限才能保存文档", Toast.LENGTH_SHORT).show()
        }
    }
}

// 方式2:Compose专用权限Launcher(推荐)
// 在setupScanner()函数内添加
val permissionLauncher = rememberLauncherForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    val allGranted = permissions.values.all { it }
    if (allGranted) {
        launchScanner(scanner, scannerLauncher)
    } else {
        Toast.makeText(this@MainActivity, "需要存储权限才能保存文档", Toast.LENGTH_SHORT).show()
    }
}

// 替换按钮点击逻辑中的else分支为:
permissionLauncher.launch(
    arrayOf(
        android.Manifest.permission.READ_EXTERNAL_STORAGE,
        android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    )
)

4. 优化保存逻辑的错误处理

原保存代码未做异常捕获,容易崩溃,添加try-catch并优化目录创建:

result?.pdf.let { pdf ->
    val docName = UUID.randomUUID().toString()
    val folderName = "ScanWush"
    val folder = File(Environment.getExternalStorageDirectory(), folderName)
    try {
        // 用mkdirs()替代mkdir(),确保父目录不存在时也能创建
        if (!folder.exists() && !folder.mkdirs()) {
            throw Exception("无法创建文件夹")
        }
        val file = File(folder, "$docName.pdf")
        FileOutputStream(file).use { fos ->
            pdf?.let { it1 ->
                contentResolver.openInputStream(it1.uri)?.use { input ->
                    input.copyTo(fos)
                    Toast.makeText(
                        this,
                        "已保存至ScanWush文件夹",
                        Toast.LENGTH_SHORT,
                    ).show()
                } ?: throw Exception("无法读取PDF文件")
            } ?: throw Exception("未生成PDF文件")
        }
    } catch (e: Exception) {
        e.printStackTrace()
        Toast.makeText(this, "保存失败:${e.message}", Toast.LENGTH_SHORT).show()
    }
}

关键说明

  • API 30+必须使用Environment.isExternalStorageManager()检查MANAGE权限,且只能跳转到系统设置开启,无法通过代码直接弹出请求弹窗。
  • 优先考虑使用作用域存储(Scoped Storage),避免依赖MANAGE_EXTERNAL_STORAGE权限——该权限需要Google Play审核,非必要场景可能被拒绝。如果只是保存扫描的PDF,可以用MediaStore将文件保存到公共文档目录,无需特殊权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 09:52:07