如何检查Uri指向的资源是否可用?

问题描述 投票:0回答:7

我有 Uri 指向的资源(音乐文件)。在尝试使用 MediaPlayer 播放之前如何检查它是否可用?

它的 Uri 存储在数据库中,因此当文件被删除或在卸载的外部存储上时,当我调用 MediaPlayer.prepare() 时,我只会收到异常。

在上述情况下我想播放系统默认铃声。我当然可以在捕获上述异常后这样做,但也许有一些更优雅的解决方案?

编辑: 我忘了提及 Uri 的音乐文件实际上是通过使用 RingtonePreference 获取的。这意味着我可以让 Uri 指向内部存储、外部存储上的铃声或默认系统铃声。

Uri 的例子是:

  • content://settings/system/ringtone - 用于选择默认铃声
  • content://media/internal/audio/media/60 - 用于内部存储上的铃声
  • content://media/external/audio/media/192 - 用于外部存储上的铃声

我对提议的“new File(path).exists() 方法感到满意,因为它使我免于提到的异常,但一段时间后我注意到它对我所有的铃声选择都返回 false... 还有其他想法吗?

android uri
7个回答
33
投票

所提出的方法不起作用的原因是您使用的是

ContentProvider
URI 而不是实际的文件路径。 要获取实际的文件路径,您必须使用光标来获取文件。

假设

String contentUri
等于内容 URI,例如
content://media/external/audio/media/192

ContentResolver cr = getContentResolver();
String[] projection = {MediaStore.MediaColumns.DATA}
Cursor cur = cr.query(Uri.parse(contentUri), projection, null, null, null);
if (cur != null) {
  if (cur.moveToFirst()) {
    String filePath = cur.getString(0);

    if (new File(filePath).exists()) {
      // do something if it exists
    } else {
      // File was not found
    }
  } else {
     // Uri was ok but no entry found. 
  }
  cur.close();
} else {
  // content Uri was invalid or some other error occurred 
}

我还没有将这种方法用于声音文件或内部存储,但它应该有效。 查询应将单行直接返回到您的文件。


10
投票

我也遇到了这个问题 - 我真的想在尝试加载 Uri 之前检查它是否可用,因为不必要的失败最终会挤满我的 Crashlytics 日志。

自从StorageAccessFramework(SAF)、DocumentProviders等出现后,处理Uris变得更加复杂。这就是我最终使用的:

fun yourFunction() {

    val uriToLoad = ...

    val validUris = contentResolver.persistedUriPermissions.map { uri }

    if (isLoadable(uriToLoad, validUris) != UriLoadable.NO) {
        // Attempt to load the uri
    }
}

enum class UriLoadable {
    YES, NO, MAYBE
}

fun isLoadable(uri: Uri, granted: List<Uri>): UriLoadable {

    return when(uri.scheme) {
        "content" -> {
            if (DocumentsContract.isDocumentUri(this, uri))
                if (documentUriExists(uri) && granted.contains(uri))
                    UriLoadable.YES
                else
                    UriLoadable.NO
            else // Content URI is not from a document provider
                if (contentUriExists(uri))
                    UriLoadable.YES
                else
                    UriLoadable.NO
        }

        "file" -> if (File(uri.path).exists()) UriLoadable.YES else UriLoadable.NO

        // http, https, etc. No inexpensive way to test existence.
        else -> UriLoadable.MAYBE
    }
}

// All DocumentProviders should support the COLUMN_DOCUMENT_ID column
fun documentUriExists(uri: Uri): Boolean =
        resolveUri(uri, DocumentsContract.Document.COLUMN_DOCUMENT_ID)

// All ContentProviders should support the BaseColumns._ID column
fun contentUriExists(uri: Uri): Boolean =
        resolveUri(uri, BaseColumns._ID)

fun resolveUri(uri: Uri, column: String): Boolean {

    val cursor = contentResolver.query(uri,
            arrayOf(column), // Empty projections are bad for performance
            null,
            null,
            null)

    val result = cursor?.moveToFirst() ?: false

    cursor?.close()

    return result
}

如果有人有更优雅或正确的替代方案,请发表评论。


8
投票

尝试如下功能:

public static boolean checkURIResource(Context context, Uri uri) {
    Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    boolean doesExist= (cursor != null && cursor.moveToFirst());
    if (cursor != null) {
        cursor.close();
    }
    return doesExist;
}

6
投票

对于那些仍在寻找解决方案的人[截至 2020 年 12 月,工作完美]并且在所有边缘情况下都按预期运行,解决方案如下:

boolean bool = false;
        if(null != uri) {
            try {
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                inputStream.close();
                bool = true;
            } catch (Exception e) {
                Log.w(MY_TAG, "File corresponding to the uri does not exist " + uri.toString());
            }
        }

如果与 URI 对应的文件存在,那么您将有一个输入流对象可以使用,否则将抛出异常。

如果文件确实存在,请不要忘记关闭输入流。

注意:

DocumentFile sourceFile = DocumentFile.fromSingleUri(context, uri);
boolean bool = sourceFile.exists();

上面的 DocumentFile 代码行确实处理了大多数边缘情况,但我发现,如果以编程方式创建文件并将其存储在某个文件夹中,则用户然后访问该文件夹并手动删除该文件(当应用程序处于运行状态时)运行),DocumentFile.fromSingleUri 错误地表示该文件存在。


3
投票

从 Kitkat 开始,您可以而且应该在必要时保留应用程序使用的 URI。据我所知,每个应用程序可以保留 128 个 URI 限制,因此您可以最大限度地利用这些资源。

就我个人而言,在这种情况下我不会处理直接路径,而是检查持久化 URI 是否仍然存在,因为当从设备中删除资源(文件)时,您的应用程序将失去对该 URI 的权限,因此使检查变得简单以下:

getContext().getContentResolver().getPersistedUriPermissions().forEach( {element -> element.uri == yourUri});

同时,当设备低于 Kitkat API 级别时,您无需检查 URI 权限。

通常,当从 URI 读取文件时,您将使用

ParcelFileDescriptor
,因此如果没有可用于该特定 URI 的文件,它将抛出异常,因此您应该用
try/catch
块将其包装起来。


0
投票

GitHub 👉 https://github.com/javakam/FileOperator/blob/master/library_core/src/main/java/ando/file/core/FileUtils.kt#L317

根据最热门的解决方案,给我一个解决方案(兼容Q、P):

/**
 * 1. Check if Uri is correct
 * 2. Whether the file pointed to by Uri exists (It may be deleted, it is also possible that the system db has Uri related records, but the file is invalid or damaged)
 *
 * https://stackoverflow.com/questions/7645951/how-to-check-if-resource-pointed-by-uri-is-available
 */
fun checkRight(uri: Uri?): Boolean {
    if (uri == null) return false
    val resolver = FileOperator.getContext().contentResolver
    //1. Check Uri
    var cursor: Cursor? = null
    val isUriExist: Boolean = try {
        cursor = resolver.query(uri, null, null, null, null)
        //cursor null: content Uri was invalid or some other error occurred
        //cursor.moveToFirst() false: Uri was ok but no entry found.
        (cursor != null && cursor.moveToFirst())
    } catch (t: Throwable) {
        FileLogger.e("1.Check Uri Error: ${t.message}")
        false
    } finally {
        try {
            cursor?.close()
        } catch (t: Throwable) {
        }
    }
    //2. Check File Exist
    //如果系统 db 存有 Uri 相关记录, 但是文件失效或者损坏 (If the system db has Uri related records, but the file is invalid or damaged)
    var ins: InputStream? = null
    val isFileExist: Boolean = try {
        ins = resolver.openInputStream(uri)
        // file exists
        true
    } catch (t: Throwable) {
        // File was not found eg: open failed: ENOENT (No such file or directory)
        FileLogger.e("2. Check File Exist Error: ${t.message}")
        false
    } finally {
        try {
            ins?.close()
        } catch (t: Throwable) {
        }
    }
    return isUriExist && isFileExist
}

-1
投票

DocumentProvider 应用于 uri 的方法很少,如果 uri 下没有文档则返回 null。我选择了 getType(uri),它返回文档/文件的 mime 类型。如果 uri 中不存在表示的文档/文件,则返回 null。因此,要检测文档/文件是否存在,您可以使用如下方法。

public static boolean exists(Context context, Uri uri)
{
    return context.getContentResolver().getType(uri) !=null;
    
}

提到的其他方法,例如查询 uri 来获取 documentID 或打开 inputstream/outputstream 不起作用,因为如果文档/文件不存在,它们会抛出 filenotfound 异常,从而导致应用程序崩溃。

如果文档/文件不存在,您可以尝试其他返回 null 而不是抛出 filenotfoundException 的方法。

© www.soinside.com 2019 - 2024. All rights reserved.