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

发布于 2024-12-08 13:59:36 字数 606 浏览 0 评论 0原文

我有 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 - 对于外部存储上的铃声,

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

I have resource (music file) pointed by Uri. How can I check if it is available before I try to play it with MediaPlayer?

Its Uri is stored in database, so when the file is deleted or on external storage that is unmounted, then I just get exception when I call MediaPlayer.prepare().

In above situation I would like to play systems default ringtone. I could of course do that after I catch above exception, but maybe there is some more elegant solution?

edit:
I forgot to mention that music files Uri's are actually acquired by using RingtonePreference. This means that I can get Uri pointing to ringtone on Internal Storage, External Storage or to default systems ringtone.

Uri's examples are:

  • content://settings/system/ringtone - for choosing default ringtone
  • content://media/internal/audio/media/60 - for ringtone on Internal Storage
  • content://media/external/audio/media/192 - for ringtone on External Storage

I was happy with proposed "new File(path).exists() method, as it saved me from mentioned exception, but after some time I noticed that it returns false for all of my ringtone choices...
Any other ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

离不开的别离 2024-12-15 13:59:36

建议的方法不起作用的原因是您使用的是 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 
}

我还没有在声音文件或内部文件中使用过此方法存储,但它应该可以工作。该查询应将一行直接返回到您的文件。

The reason the proposed method doesn't work is because you're using the ContentProvider URI rather than the actual file path. To get the actual file path, you have to use a cursor to get the file.

Assuming String contentUri is equal to the content URI such as 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 
}

I haven't used this method with sound files or internal storage, but it should work. The query should return a single row directly to your file.

昇り龍 2024-12-15 13:59:36

我也遇到了这个问题 - 我真的想在尝试加载 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
}

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

I too had this problem - I really wanted to check if a Uri was available before trying to load it, as unnecessary failures would end up crowding my Crashlytics logs.

Since the arrival of the StorageAccessFramework (SAF), DocumentProviders, etc., dealing with Uris has become more complicated. This is what I eventually used:

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
}

If someone has a more elegant -- or correct -- alternative, please do comment.

寒江雪… 2024-12-15 13:59:36

尝试使用如下函数:

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;
}

Try a function like:

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;
}
你丑哭了我 2024-12-15 13:59:36

对于那些仍在寻找解决方案的人[截至 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 错误地表示该文件存在。

For those still looking out for a solution [works perfectly fine as of Dec 2020] and behaves as expected for all edge cases, the solution is a follows:

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());
            }
        }

If the file corresponding to the URI exists, then you will have an input stream object to work with, else an exception will be thrown.

Do not forget to close the input stream if the file does exist.

NOTE:

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

The above lines of code for DocumentFile, does handle most edge cases, but what I found out was that if a file is created programmatically and stored in some folder, the user then visits the folder and manually deletes the file (while the app is running), DocumentFile.fromSingleUri wrongly says that the file exists.

饮惑 2024-12-15 13:59:36

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

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

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

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

通常,当从 URI 读取文件时,您将使用 ParcelFileDescriptor,因此如果没有可用于该特定 URI 的文件,它将抛出异常,因此您应该使用 try/catch< /代码> 块。

As of Kitkat you can, and you should, persist URIs that your app uses if necessary. As far as I know, there's a 128 URI limit you can persist per app, so it's up to you to maximize usage of those resources.

Personally I wouldn't deal with direct paths in this case, but rather check if persisted URI still exists, since when resource (a file) is deleted from a device, your app loses rights to that URI therefore making that check as simple as the following:

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

Meanwhile you won't need to check for URI permissions when a device is below Kitkat API level.

Usually, when reading files from URIs you're going to use ParcelFileDescriptor, thus it's going to throw if no file is available for that particular URI, therefore you should wrap it with try/catch block.

无可置疑 2024-12-15 13:59:36

GitHub

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

Based on the hottest solution, give me a solution (compatible with 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
}
幻梦 2024-12-15 13:59:36

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

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

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

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

There are few methods of DocumentProvider when applied to uri, return null if there is no document underlying uri. I chose getType(uri) which returns mime type of the document/file. If no document/file exists represented in uri, it returns null. Hence, to detect whether documennt/file exists or not, you can use this method like below.

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

Other methods mentioned like querying the uri to get documentID or opening inputstream/outputstream did not work because they throw filenotfound exception if document/file does not exist, which resulted in crashing of app.

You may attempt other methods which return null instead of throwing filenotfoundexception, if document/file does not exist.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文