存储访问框架 - 权限被拒绝的问题,即使在授予权限时也是如此
Storage Access Framework- Permission denied issue even when permissions granted
我正在实施存储访问框架以授予 Android 文件夹访问权限。我已更新 targetSDKVersion=30
并在 Android 11 我遇到了存储权限问题。我想获得存储权限以访问 Android/media/com.whatsapp/WhatsApp/Media
.
中的文件
为了使存储权限正常工作,我实施了存储访问框架以获得 Android/media/com.whatsapp/WhatsApp/Media
访问权限,然后在其子文件夹中获取文件 (即 .Statuses、WhatsAppImages)
下面是代码详情
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
<application
android:name=".application.MyApplication"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true" // also tried removing this
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
Activity代码
//First i checked and requested android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE
if ((ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED)
) {
//further work of opening directory
} else {
TedPermission.with(this)
.setPermissionListener(sdk30PermissionListener)
.setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
.setPermissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
.check()
}
//This is the root path i want to hit to access all subfolders inside this one
public static final String whatsApp_root_path = "Android/media/com.whatsapp/WhatsApp/Media”;
companion object {
const val ANDROID_DOCID = "primary:${Constants.whatsApp_root_path}"
const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
"com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
private val handleIntentActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK)
return@registerForActivityResult
val directoryUri = it.data?.data ?: return@registerForActivityResult
contentResolver.takePersistableUriPermission(
directoryUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
if (checkIfGotAccess()) {
premiumDialogHandling()
onGotAccess()
} else {
Log.d("AppLog", "you didn't grant permission to the correct folder")
tinyDB.putBoolean("SDK30Permissions", false)
showWrongFolderSelection()
}
}
private fun checkIfGotAccess(): Boolean {
return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
}
private fun openDirectory() {
if (checkIfGotAccess()) {
onGotAccess()
} else {
val primaryStorageVolume =
(getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
val intent = primaryStorageVolume.createOpenDocumentTreeIntent()
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidUri)
handleIntentActivityResult.launch(intent)
}
}
private fun onGotAccess() {
tinyDB.putBoolean("SDK30Permissions", true)
//once user chooses ‘Use Folder’ to allow permissions
processStatusFetchExecute()
}
public fun processStatusFetchExecute(completed: () -> Unit) {
Timber.e("processStatusFetch:Init")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@Suppress("DEPRECATION")
var statusesFolder = File(
Environment.getExternalStorageDirectory(),
Constants.whatsApp_root_path
).listFiles(FileFilter { file -> file.name.equals("${Constants.FOLDER_NAME_STATUSES}") })
val docIdStatuses = "${HomeActivity.ANDROID_DOCID}/${statusesFolder.get(0).name}"
val childrenUriStatuses =
DocumentsContract.buildChildDocumentsUriUsingTree(
HomeActivity.androidTreeUri,
docIdStatuses
)
val statusTreeUri = DocumentsContract.buildTreeDocumentUri(
HomeActivity.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, docIdStatuses
)
val hasAccess: Boolean =
contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals( HomeActivity.androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
Timber.e("processStatusFetch:Execute")
val statusImages = arrayListOf<File>()
val statusVideos = arrayListOf<File>()
val png = MimeTypeMap.getSingleton().getMimeTypeFromExtension("png")
val jpg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
val jpeg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpeg")
val args = arrayOf(png, jpg, jpeg)
val where = (MediaStore.Files.FileColumns.MIME_TYPE + "=?"
+ " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?"
+ " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?")
var fileCursorExternal: Cursor? = null
val orderBy = MediaStore.Files.FileColumns.DATE_MODIFIED
val column = arrayOf(
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.DATE_MODIFIED
)
//Please note args,where, orderby are not working with contentResolver.query so i have handled file filter using loop
val selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?"
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
val selectionArgsPdf = arrayOf(mimeType)
fileCursorExternal = contentResolver.query(
childrenUriStatuses!!,
column,
selectionMimeType,
selectionArgsPdf,
"date_modified DESC"
)
GlobalScope.launch(Dispatchers.Main + exceptionHandler) {
async(Dispatchers.IO + exceptionHandler) {
Timber.e("processStatusFetch:InProgress")
while (fileCursorExternal!!.moveToNext()) {
val nameIndex =
fileCursorExternal.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
val displayName = fileCursorExternal.getString(nameIndex)
val path =
"${Constants.whatsApp_root_path}/${Constants.FOLDER_NAME_STATUSES}/$displayName"
@Suppress("DEPRECATION")
if (getFileType(displayName) == FILETYPE.IMAGE) {
statusImages.add(
File(
Environment.getExternalStorageDirectory(),
path
)
)
} else if (getFileType(displayName) == FILETYPE.VIDEO) {
statusVideos.add(
File(
Environment.getExternalStorageDirectory(),
path
)
)
}
}
}.await()
Timber.e("processStatusFetch:Done")
sharedViewModel.statusImages.postValue(statusImages)
sharedViewModel.statusVideos.postValue(statusVideos)
completed()
}
}
}
目前完成的内容:
使用上面的代码我已经成功显示了使用文件夹的媒体屏幕。
而且我也能够成功获取 .Statuses
文件夹中的所有文件
问题:
当我将图像列表设置为 RecyclerViewAdapter
内的 Recyclerview
时。在将文件设置为 Imageview
时,我总是收到 Permissions Denied
异常。
方法1:尝试使用Glide将文件设置为图像视图
Glide.with(itemView.context).load(file.absoluteFile)
.listener(object :
RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
val abc = "yes"
return false
}
}
).into(imgview)
低于异常
com.bumptech.glide.load.engine.GlideException: Failed to load resource
There were 3 root causes:
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(open failed: EACCES (Permission denied))
call GlideException#logRootCauses(String) for more detail
方法 2:尝试使用 Uri 将文件设置为图像视图
imgview.setImageURI(Uri.fromFile(file))
低于异常
resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/25340b4ddcf44eb2a8d6a5d0509feee9.jpg
W/ImageView: Unable to open content: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg
java.io.FileNotFoundException: /storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:492)
at java.io.FileInputStream.<init>(FileInputStream.java:160)
at java.io.FileInputStream.<init>(FileInputStream.java:115)
at android.content.ContentResolver.openInputStream(ContentResolver.java:1498)
at android.graphics.ImageDecoder$ContentResolverSource.createImageDecoder(ImageDecoder.java:286)
at android.graphics.ImageDecoder.decodeDrawableImpl(ImageDecoder.java:1758)
at android.graphics.ImageDecoder.decodeDrawable(ImageDecoder.java:1751)
at android.widget.ImageView.getDrawableFromUri(ImageView.java:1011)
at android.widget.ImageView.resolveUri(ImageView.java:980)
at android.widget.ImageView.setImageURI(ImageView.java:557)
at androidx.appcompat.widget.AppCompatImageView.setImageURI(AppCompatImageView.java:120)
at mypkgname.StatusImageAdapterNew$ImageViewHolder.bindItems(StatusImageAdapterNew.kt:135)
at mypkgname.StatusImageAdapterNew.onBindViewHolder(StatusImageAdapterNew.kt:81)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:561)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.swiperefreshlayout.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:625)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.viewpager.widget.ViewPager.onLayout(ViewPager.java:1775)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1263)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:797)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3694)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3152)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2123)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8601)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:858)
at android.view.Choreographer.doFrame(Choreographer.java:789)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1020)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8051)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7923)
at libcore.io.IoBridge.open(IoBridge.java:478)
... 85 more
resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg
阅读这两个异常,我可以理解我在 Android 中授予媒体文件夹的权限可能不正确,但当我检查它时它 returns 是正确的。
请注意,这仅在 Android 11 上发生,因为我将 SDK 版本更新为 30。
我无法解决这个问题,我想我需要更多地了解存储访问框架才能正确设置这些权限,因为之后我需要处理 writing/copying 个文件。
Reading both exceptions i can understand that the permissions i granted for Media folder inside Android may not be correct
你不能授予任何东西。相反,您被授予访问 Whatsapp Media 文件夹的权限。但仅在使用 SAF 时。
现在您有一堆不相关的代码,您可以在其中定义提供程序和路径。不好。可怕的代码。
虽然您可能成功列出了所有文件,但您并没有使用所有这些文件的 uri(您应该将它们放在 <Uri>
列表中以供回收视图使用)但是再次所有一种技巧建立文件路径。
所以扔掉所有那些提供程序和路径定义。
只需直接query()列出Media文件夹的内容即可。如果您看到列出的“.Statuses”文件夹,则去列出该文件夹。并且..将获取的文件uri保存到Uri列表中。
您从 SAF 开始并转换为文件。不要做这种讨厌的事情。您的 SAF 权限不是文件权限。您不需要清单中的任何权限即可使用 SAF。
我正在实施存储访问框架以授予 Android 文件夹访问权限。我已更新 targetSDKVersion=30
并在 Android 11 我遇到了存储权限问题。我想获得存储权限以访问 Android/media/com.whatsapp/WhatsApp/Media
.
为了使存储权限正常工作,我实施了存储访问框架以获得 Android/media/com.whatsapp/WhatsApp/Media
访问权限,然后在其子文件夹中获取文件 (即 .Statuses、WhatsAppImages)
下面是代码详情
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />
<application
android:name=".application.MyApplication"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true" // also tried removing this
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
Activity代码
//First i checked and requested android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE
if ((ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED)
) {
//further work of opening directory
} else {
TedPermission.with(this)
.setPermissionListener(sdk30PermissionListener)
.setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
.setPermissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
.check()
}
//This is the root path i want to hit to access all subfolders inside this one
public static final String whatsApp_root_path = "Android/media/com.whatsapp/WhatsApp/Media”;
companion object {
const val ANDROID_DOCID = "primary:${Constants.whatsApp_root_path}"
const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
"com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
private val handleIntentActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK)
return@registerForActivityResult
val directoryUri = it.data?.data ?: return@registerForActivityResult
contentResolver.takePersistableUriPermission(
directoryUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
if (checkIfGotAccess()) {
premiumDialogHandling()
onGotAccess()
} else {
Log.d("AppLog", "you didn't grant permission to the correct folder")
tinyDB.putBoolean("SDK30Permissions", false)
showWrongFolderSelection()
}
}
private fun checkIfGotAccess(): Boolean {
return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
}
private fun openDirectory() {
if (checkIfGotAccess()) {
onGotAccess()
} else {
val primaryStorageVolume =
(getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
val intent = primaryStorageVolume.createOpenDocumentTreeIntent()
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidUri)
handleIntentActivityResult.launch(intent)
}
}
private fun onGotAccess() {
tinyDB.putBoolean("SDK30Permissions", true)
//once user chooses ‘Use Folder’ to allow permissions
processStatusFetchExecute()
}
public fun processStatusFetchExecute(completed: () -> Unit) {
Timber.e("processStatusFetch:Init")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@Suppress("DEPRECATION")
var statusesFolder = File(
Environment.getExternalStorageDirectory(),
Constants.whatsApp_root_path
).listFiles(FileFilter { file -> file.name.equals("${Constants.FOLDER_NAME_STATUSES}") })
val docIdStatuses = "${HomeActivity.ANDROID_DOCID}/${statusesFolder.get(0).name}"
val childrenUriStatuses =
DocumentsContract.buildChildDocumentsUriUsingTree(
HomeActivity.androidTreeUri,
docIdStatuses
)
val statusTreeUri = DocumentsContract.buildTreeDocumentUri(
HomeActivity.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, docIdStatuses
)
val hasAccess: Boolean =
contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals( HomeActivity.androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
Timber.e("processStatusFetch:Execute")
val statusImages = arrayListOf<File>()
val statusVideos = arrayListOf<File>()
val png = MimeTypeMap.getSingleton().getMimeTypeFromExtension("png")
val jpg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
val jpeg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpeg")
val args = arrayOf(png, jpg, jpeg)
val where = (MediaStore.Files.FileColumns.MIME_TYPE + "=?"
+ " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?"
+ " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?")
var fileCursorExternal: Cursor? = null
val orderBy = MediaStore.Files.FileColumns.DATE_MODIFIED
val column = arrayOf(
MediaStore.Files.FileColumns.DISPLAY_NAME,
MediaStore.Files.FileColumns.MIME_TYPE,
MediaStore.Files.FileColumns.DATE_MODIFIED
)
//Please note args,where, orderby are not working with contentResolver.query so i have handled file filter using loop
val selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?"
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
val selectionArgsPdf = arrayOf(mimeType)
fileCursorExternal = contentResolver.query(
childrenUriStatuses!!,
column,
selectionMimeType,
selectionArgsPdf,
"date_modified DESC"
)
GlobalScope.launch(Dispatchers.Main + exceptionHandler) {
async(Dispatchers.IO + exceptionHandler) {
Timber.e("processStatusFetch:InProgress")
while (fileCursorExternal!!.moveToNext()) {
val nameIndex =
fileCursorExternal.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)
val displayName = fileCursorExternal.getString(nameIndex)
val path =
"${Constants.whatsApp_root_path}/${Constants.FOLDER_NAME_STATUSES}/$displayName"
@Suppress("DEPRECATION")
if (getFileType(displayName) == FILETYPE.IMAGE) {
statusImages.add(
File(
Environment.getExternalStorageDirectory(),
path
)
)
} else if (getFileType(displayName) == FILETYPE.VIDEO) {
statusVideos.add(
File(
Environment.getExternalStorageDirectory(),
path
)
)
}
}
}.await()
Timber.e("processStatusFetch:Done")
sharedViewModel.statusImages.postValue(statusImages)
sharedViewModel.statusVideos.postValue(statusVideos)
completed()
}
}
}
目前完成的内容:
使用上面的代码我已经成功显示了使用文件夹的媒体屏幕。
而且我也能够成功获取 .Statuses
文件夹中的所有文件
问题:
当我将图像列表设置为 RecyclerViewAdapter
内的 Recyclerview
时。在将文件设置为 Imageview
时,我总是收到 Permissions Denied
异常。
方法1:尝试使用Glide将文件设置为图像视图
Glide.with(itemView.context).load(file.absoluteFile)
.listener(object :
RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
val abc = "yes"
return false
}
}
).into(imgview)
低于异常
com.bumptech.glide.load.engine.GlideException: Failed to load resource
There were 3 root causes:
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(open failed: EACCES (Permission denied))
call GlideException#logRootCauses(String) for more detail
方法 2:尝试使用 Uri 将文件设置为图像视图
imgview.setImageURI(Uri.fromFile(file))
低于异常
resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/25340b4ddcf44eb2a8d6a5d0509feee9.jpg
W/ImageView: Unable to open content: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg
java.io.FileNotFoundException: /storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:492)
at java.io.FileInputStream.<init>(FileInputStream.java:160)
at java.io.FileInputStream.<init>(FileInputStream.java:115)
at android.content.ContentResolver.openInputStream(ContentResolver.java:1498)
at android.graphics.ImageDecoder$ContentResolverSource.createImageDecoder(ImageDecoder.java:286)
at android.graphics.ImageDecoder.decodeDrawableImpl(ImageDecoder.java:1758)
at android.graphics.ImageDecoder.decodeDrawable(ImageDecoder.java:1751)
at android.widget.ImageView.getDrawableFromUri(ImageView.java:1011)
at android.widget.ImageView.resolveUri(ImageView.java:980)
at android.widget.ImageView.setImageURI(ImageView.java:557)
at androidx.appcompat.widget.AppCompatImageView.setImageURI(AppCompatImageView.java:120)
at mypkgname.StatusImageAdapterNew$ImageViewHolder.bindItems(StatusImageAdapterNew.kt:135)
at mypkgname.StatusImageAdapterNew.onBindViewHolder(StatusImageAdapterNew.kt:81)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:561)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.swiperefreshlayout.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:625)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.viewpager.widget.ViewPager.onLayout(ViewPager.java:1775)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1263)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
at com.android.internal.policy.DecorView.onLayout(DecorView.java:797)
at android.view.View.layout(View.java:23242)
at android.view.ViewGroup.layout(ViewGroup.java:6513)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3694)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3152)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2123)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8601)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:858)
at android.view.Choreographer.doFrame(Choreographer.java:789)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1020)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:8051)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7923)
at libcore.io.IoBridge.open(IoBridge.java:478)
... 85 more
resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg
阅读这两个异常,我可以理解我在 Android 中授予媒体文件夹的权限可能不正确,但当我检查它时它 returns 是正确的。 请注意,这仅在 Android 11 上发生,因为我将 SDK 版本更新为 30。
我无法解决这个问题,我想我需要更多地了解存储访问框架才能正确设置这些权限,因为之后我需要处理 writing/copying 个文件。
Reading both exceptions i can understand that the permissions i granted for Media folder inside Android may not be correct
你不能授予任何东西。相反,您被授予访问 Whatsapp Media 文件夹的权限。但仅在使用 SAF 时。
现在您有一堆不相关的代码,您可以在其中定义提供程序和路径。不好。可怕的代码。
虽然您可能成功列出了所有文件,但您并没有使用所有这些文件的 uri(您应该将它们放在 <Uri>
列表中以供回收视图使用)但是再次所有一种技巧建立文件路径。
所以扔掉所有那些提供程序和路径定义。
只需直接query()列出Media文件夹的内容即可。如果您看到列出的“.Statuses”文件夹,则去列出该文件夹。并且..将获取的文件uri保存到Uri列表中。
您从 SAF 开始并转换为文件。不要做这种讨厌的事情。您的 SAF 权限不是文件权限。您不需要清单中的任何权限即可使用 SAF。