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

如何实现PDF文件的本地保存与多应用分享(URL下载场景)

解决方案:实现PDF多应用分享与本地保存功能

看了你的代码,问题主要出在两个地方:一是分享逻辑没给其他应用授予文件读取权限,导致ShareIt这类应用无法访问PDF;二是完全缺少将文件保存到设备公共存储的逻辑。下面分两部分帮你修复:


一、修复多应用分享功能(支持ShareIt等)

你的分享代码需要添加临时读取权限,确保其他应用能通过FileProvider访问到PDF文件。修改shareImageView.setOnClickListener里的逻辑:

shareImageView.setOnClickListener {
    destinationPath?.let { path ->
        val file = File(path)
        val uri = context?.let { ctx ->
            FileProvider.getUriForFile(
                ctx,
                "com.app.project.fileprovider",
                file
            )
        } ?: run {
            ToastClass().show(activity, "File URI invalid")
            return@setOnClickListener
        }

        val shareIntent = Intent(Intent.ACTION_SEND).apply {
            type = "application/pdf"
            putExtra(Intent.EXTRA_STREAM, uri)
            putExtra(Intent.EXTRA_SUBJECT, file.name)
            // 关键:给所有接收应用授予临时读取权限
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }

        // 显示系统分享选择器,适配所有分享类应用
        val chooserIntent = Intent.createChooser(shareIntent, "Share PDF via")
        if (chooserIntent.resolveActivity(requireActivity().packageManager) != null) {
            startActivity(chooserIntent)
        } else {
            ToastClass().show(activity, "No apps available to share")
        }
    } ?: run {
        ToastClass().show(activity, "PDF file not ready yet")
    }
}

核心改动说明:

  • 新增Intent.FLAG_GRANT_READ_URI_PERMISSION,解决第三方应用无法访问FileProvider URI的问题
  • 用Intent.createChooser替代ShareCompat,更直接适配所有分享类应用
  • 增加空安全判断,避免空指针异常

二、添加保存到设备存储的功能

要把PDF保存到设备公共下载目录,需要分Android版本处理权限:

1. 先在AndroidManifest.xml中声明权限

<!-- Android 10及以下需要的存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<!-- Android 11+不需要WRITE权限,如需访问全目录可添加(可选) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />

2. 实现保存按钮逻辑(假设你有saveImageView按钮)

在onViewCreated中添加保存按钮的点击事件:

saveImageView.setOnClickListener {
    destinationPath?.let { sourcePath ->
        val sourceFile = File(sourcePath)
        if (!sourceFile.exists()) {
            ToastClass().show(activity, "PDF file not found")
            return@setOnClickListener
        }

        // Android 10及以下需要申请存储权限
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
            if (ContextCompat.checkSelfPermission(
                    requireContext(),
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(
                    requireActivity(),
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    1001
                )
                return@setOnClickListener
            }
        }

        // 复制文件到公共Download目录
        val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val destFile = File(downloadDir, sourceFile.name)

        try {
            // 流式复制文件,避免内存溢出
            FileInputStream(sourceFile).use { inputStream ->
                FileOutputStream(destFile).use { outputStream ->
                    inputStream.copyTo(outputStream)
                }
            }

            // 通知系统扫描文件,让文件在文件管理器中显示
            MediaScannerConnection.scanFile(
                requireContext(),
                arrayOf(destFile.absolutePath),
                arrayOf("application/pdf")
            ) { _, _ ->
                ToastClass().show(activity, "PDF saved to Downloads folder")
            }
        } catch (e: Exception) {
            e.printStackTrace()
            ToastClass().show(activity, "Failed to save PDF: ${e.message}")
        }
    } ?: run {
        ToastClass().show(activity, "PDF file not ready yet")
    }
}

3. 处理权限请求结果

在Fragment中重写权限回调方法:

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == 1001) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限授予后自动触发保存
            saveImageView.performClick()
        } else {
            ToastClass().show(activity, "Storage permission required to save file")
        }
    }
}

三、整合后的完整关键代码

把上面的修改整合到你的PdfReaderFragment中,最终的分享和保存部分代码如下:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    setTitleAtCentre(view)

    // 分享按钮逻辑
    shareImageView.setOnClickListener {
        destinationPath?.let { path ->
            val file = File(path)
            val uri = context?.let { ctx ->
                FileProvider.getUriForFile(
                    ctx,
                    "com.app.project.fileprovider",
                    file
                )
            } ?: run {
                ToastClass().show(activity, "File URI invalid")
                return@setOnClickListener
            }

            val shareIntent = Intent(Intent.ACTION_SEND).apply {
                type = "application/pdf"
                putExtra(Intent.EXTRA_STREAM, uri)
                putExtra(Intent.EXTRA_SUBJECT, file.name)
                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            }

            val chooserIntent = Intent.createChooser(shareIntent, "Share PDF via")
            if (chooserIntent.resolveActivity(requireActivity().packageManager) != null) {
                startActivity(chooserIntent)
            } else {
                ToastClass().show(activity, "No apps available to share")
            }
        } ?: run {
            ToastClass().show(activity, "PDF file not ready yet")
        }
    }

    // 保存按钮逻辑
    saveImageView.setOnClickListener {
        destinationPath?.let { sourcePath ->
            val sourceFile = File(sourcePath)
            if (!sourceFile.exists()) {
                ToastClass().show(activity, "PDF file not found")
                return@setOnClickListener
            }

            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                if (ContextCompat.checkSelfPermission(
                        requireContext(),
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    ActivityCompat.requestPermissions(
                        requireActivity(),
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        1001
                    )
                    return@setOnClickListener
                }
            }

            val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
            val destFile = File(downloadDir, sourceFile.name)

            try {
                FileInputStream(sourceFile).use { inputStream ->
                    FileOutputStream(destFile).use { outputStream ->
                        inputStream.copyTo(outputStream)
                    }
                }

                MediaScannerConnection.scanFile(
                    requireContext(),
                    arrayOf(destFile.absolutePath),
                    arrayOf("application/pdf")
                ) { _, _ ->
                    ToastClass().show(activity, "PDF saved to Downloads folder")
                }
            } catch (e: Exception) {
                e.printStackTrace()
                ToastClass().show(activity, "Failed to save PDF: ${e.message}")
            }
        } ?: run {
            ToastClass().show(activity, "PDF file not ready yet")
        }
    }

    RemotePDFViewPager(context, arguments.getString("url"), this)
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == 1001) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            saveImageView.performClick()
        } else {
            ToastClass().show(activity, "Storage permission required to save file")
        }
    }
}

注意事项:

  1. 确保你的FileProvider在AndroidManifest.xml中配置正确,有对应的<provider>节点
  2. Android 11+采用Scoped Storage,保存到公共Download目录无需额外权限
  3. 测试时要保证PDF已经下载完成(destinationPath不为空)

内容的提问来源于stack exchange,提问作者The DevRoid

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 10:06:06