如何从 URL 打开使用身份验证的 PDF?

How to open a PDF from URL that uses authentication?

我必须从需要访问令牌的 URL 打开 PDF(该 PDF 未公开提供)。 PDF 不应永久存储在设备上。

如果不需要访问令牌,解决方案如下所示:

val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(url), "application/pdf") }
startActivity(intent)

但是,需要访问令牌,因此这不起作用。

我试过在应用程序中下载文件,然后打开文件的本地副本,如下所示:

        val inputStream = /*get PDF as an InputStream*/
        
        val file = File.createTempFile(
            fileName,
            ".pdf",
            context?.externalCacheDir
        ).apply { deleteOnExit() }
        
        stream.use { input ->
            file.outputStream().use { output ->
                input.copyTo(output)
            }
        }
        
        val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.fromFile(file), "application/pdf") }
        startActivity(intent)

但这给了我 android.os.FileUriExposedException 消息 <myFilePath> exposed beyond app through Intent.getData()
我也使用 context?.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) 尝试了同样的事情并得到了相同的结果。

到目前为止,我得到的最接近的是这样的:

        val inputStream = /*get PDF as an InputStream*/
        
        val file = File.createTempFile(
            fileName,
            ".pdf",
            context?.filesDir
        ).apply { deleteOnExit() }
        
        stream.use { input ->
            file.outputStream().use { output ->
                input.copyTo(output)
            }
        }
        
        val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse(file.absolutePath), "application/pdf") }
        startActivity(intent)

这样做,我的三星设备向我提供了三星笔记和 Microsoft Word 来打开 PDF,但实际上它们都无法打开。 (它刚刚打开这些应用程序,它们显示错误,找不到它们应该打开的文件)
已安装的 PDF reader - 使用我在最顶部的两行代码时通常会打开 PDF 的那个 post - 甚至没有列出。

那么,如何正确显示此 PDF?

编辑:

找到后,我添加了一个文件提供程序,现在我的代码如下所示:

        val inputStream = /*get PDF as an InputStream*/
        
        val file = File.createTempFile(
            fileName,
            ".pdf",
            context?.getExternalFilesDir(
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                    Environment.DIRECTORY_DOCUMENTS
                else
                    Environment.DIRECTORY_DOWNLOADS
        ).apply { deleteOnExit() }
        
        stream.use { input ->
            file.outputStream().use { output ->
                input.copyTo(output)
            }
        }
        
        val documentUri: Uri = FileProvider.getUriForFile(
            requireContext(),
            getString(R.string.file_provider_authority),
            file
        )
        
        val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(documentUri, "application/pdf") }
        startActivity(intent)

现在PDF可以正常打开了。但是 PDF 是空的。

这是我将 PDF 加载到 InputStream 的方式:

interface DocumentsClient {
    
    @GET("/api/something/pdf/{pdf_id}")
    @Headers("Accept: application/pdf")
    @Streaming
    fun loadSomethingPDF(@Path("pdf_id") pdfId: Long): Call<ResponseBody>
    
}

fun loadSomethingPDF(accessToken: String?, pdfId: Long) =
    createDocumentsClient(accessToken).loadSomethingPDF(pdfId)

private fun createDocumentsClient(accessToken: String?): DocumentsClient {
        val okClientBuilder = OkHttpClient.Builder()
                
        // add headers
        okClientBuilder.addInterceptor { chain ->
            val original = chain.request()
            
            val requestBuilder = original.newBuilder()
            
            if (accessToken != null) {
                requestBuilder.header("Authorization", "Bearer $accessToken")
            }
            
            val request = requestBuilder.build()
            
            chain.proceed(request)
        }
        
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okClientBuilder.build())
            .build()
        
        return retrofit.create(DocumentsClient::class.java)
    }

loadSomethingPdf 然后被这样调用:

    @Throws(IOException::class, IllegalStateException::class)
    suspend fun loadSomethingPdf(accessToken: String?, pdfId: Long) = withContext(Dispatchers.IO) {
        val call = remoteManager.loadSomethingPDF(accessToken, pdfId)
        
        try {
            val response = call.execute()
            
            if (!response.isSuccessful) {
                return@withContext null
            }
            
            return@withContext response.body()?.byteStream()
            
        } catch (e: IOException) {
            throw e
        } catch (e: IllegalStateException) {
            throw e
        }
    }

这就是我从中获取 inputStream 的地方。

好的,我的问题完全不同。

我需要授予 Uri 权限以允许其他应用程序读取我发送给它们的 Uri。

代码只需要这个:

intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION