开发者

How to check if resource pointed by Uri is available?

开发者 https://www.devze.com 2023-04-11 03:10 出处:网络
I have resource (music file) pointed by Uri. How can I check if it is available before I try to play it with MediaPlayer?

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?开发者_C百科


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.


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.


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


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.


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.


0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号