是否可以从 Android API 28 从数据库获取的 Uri 打开 PDF 文件?
Is it possible to open a PDF file from a database-fetched Uri from Android API 28?
我正在为 Android (>= API 28) 编写一个文件共享应用程序,我有以下用例:
- 用户从他们的本地存储上传文件。
- 用户关闭应用程序并在一段时间后重新打开它,导致应用程序向服务器请求文件列表以填充布局。
- 用户单击文件进行预览。
此时,最直观的步骤是,假设应用程序知道文件 URL,只需将其加载到指向 Google Docs' https://docs.google.com/viewerng/viewer?url=$myFileUrl
的 WebView 中或任何其他文件查看服务。
事实是,我不希望我的应用过度依赖外部服务,所以我想在文件上传的那一刻(第 1 步),一个 content://
Uri
附加并存储在数据库中以在步骤 3 中使用它。
这样,如果用户没有重命名和移动他上传的文件,应用程序可以尝试在本地打开文件,从而节省一些带宽使用量。
正如@CommonsWare 在此 article 中所写,应用程序有权访问 Uri
的时间是有限的;在我的例子中,它只会持续到用户关闭应用程序。这意味着使用如下策略:
MainActivity.kt
// Getting the content:// Uri after a call to the server.
viewModel.selectedUrl.observe(this@MainActivity) { uri ->
val newIntent = Intent(Intent.ACTION_VIEW)
try {
newIntent.data = Uri.parse(uri)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
try {
startActivity(newIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show()
}
// ...
}
仅当文件已上传并立即打开时才有效。有没有可能以任何方式解决这个问题,或者我应该坚持不涉及任何 Uri
的 WebView
选项?
更新:文件是如何上传的?
在我的 MainActivity
中,我有一个 FAB,点击它会触发以下片段:
MainActivity.kt
val fileResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
viewModel.uploadFile(result, false) // This is the Uri.
}
fileFab.setOnClickListener {
val intent = Intent()
intent.type = "application/*"
intent.action = Intent.ACTION_OPEN_DOCUMENT
fileResultLauncher.launch(intent)
}
...在各自的 ViewModel.kt
和 Repository.kt
调用之后它最终在 Server
class 中使用以下方法:
Server.kt
override fun uploadFile(uri: Uri, title: String, isSound: Boolean, callback: IFileUploadCallback) {
Timber.d("Uploading document...")
val inputStream = c.contentResolver.openInputStream(uri)
var fileData: ByteArray? = null
inputStream?.buffered()?.use {
fileData = it.readBytes()
}
fileData?: return
// Call to Volley
// ...
感谢@blackapps 和@CommonsWare,我的 ViewModel class 中缺少以下行:
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
ViewModel.kt
fun uploadFile(result: ActivityResult) {
if (result.resultCode == Activity.RESULT_OK) {
val uri: Uri = result.data?.data!!
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
viewModelScope.launch(Dispatchers.IO) {
repository.uploadFile(uri, object: Server.IFileUploadCallback {
override fun onSuccess(fileUrl: String, content: String) {
uploadedFileUrl.postValue(RecyclerViewItem.Document(fileUrl, content, uri))
}
})
}
}
}
我正在为 Android (>= API 28) 编写一个文件共享应用程序,我有以下用例:
- 用户从他们的本地存储上传文件。
- 用户关闭应用程序并在一段时间后重新打开它,导致应用程序向服务器请求文件列表以填充布局。
- 用户单击文件进行预览。
此时,最直观的步骤是,假设应用程序知道文件 URL,只需将其加载到指向 Google Docs' https://docs.google.com/viewerng/viewer?url=$myFileUrl
的 WebView 中或任何其他文件查看服务。
事实是,我不希望我的应用过度依赖外部服务,所以我想在文件上传的那一刻(第 1 步),一个 content://
Uri
附加并存储在数据库中以在步骤 3 中使用它。
这样,如果用户没有重命名和移动他上传的文件,应用程序可以尝试在本地打开文件,从而节省一些带宽使用量。
正如@CommonsWare 在此 article 中所写,应用程序有权访问 Uri
的时间是有限的;在我的例子中,它只会持续到用户关闭应用程序。这意味着使用如下策略:
MainActivity.kt
// Getting the content:// Uri after a call to the server.
viewModel.selectedUrl.observe(this@MainActivity) { uri ->
val newIntent = Intent(Intent.ACTION_VIEW)
try {
newIntent.data = Uri.parse(uri)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
newIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
try {
startActivity(newIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show()
}
// ...
}
仅当文件已上传并立即打开时才有效。有没有可能以任何方式解决这个问题,或者我应该坚持不涉及任何 Uri
的 WebView
选项?
更新:文件是如何上传的?
在我的 MainActivity
中,我有一个 FAB,点击它会触发以下片段:
MainActivity.kt
val fileResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
viewModel.uploadFile(result, false) // This is the Uri.
}
fileFab.setOnClickListener {
val intent = Intent()
intent.type = "application/*"
intent.action = Intent.ACTION_OPEN_DOCUMENT
fileResultLauncher.launch(intent)
}
...在各自的 ViewModel.kt
和 Repository.kt
调用之后它最终在 Server
class 中使用以下方法:
Server.kt
override fun uploadFile(uri: Uri, title: String, isSound: Boolean, callback: IFileUploadCallback) {
Timber.d("Uploading document...")
val inputStream = c.contentResolver.openInputStream(uri)
var fileData: ByteArray? = null
inputStream?.buffered()?.use {
fileData = it.readBytes()
}
fileData?: return
// Call to Volley
// ...
感谢@blackapps 和@CommonsWare,我的 ViewModel class 中缺少以下行:
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
ViewModel.kt
fun uploadFile(result: ActivityResult) {
if (result.resultCode == Activity.RESULT_OK) {
val uri: Uri = result.data?.data!!
context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
viewModelScope.launch(Dispatchers.IO) {
repository.uploadFile(uri, object: Server.IFileUploadCallback {
override fun onSuccess(fileUrl: String, content: String) {
uploadedFileUrl.postValue(RecyclerViewItem.Document(fileUrl, content, uri))
}
})
}
}
}