How to Consume Content From a Uri
Since it seems even clearer that
file scheme is going away,
let’s review how this is all supposed to work now.
Today, let’s look at the consumer side: you are handed a
some content, in a situation where you might have previously expected a
file path. How do you get that content?
What To Do
First, ensure that anywhere you have an
<intent-filter> for a
scheme, that you also have an
for the appropriate MIME type(s). You may get a
by other means (e.g.,
onActivityResult()), but if you are supporting
file-like content for actions like
ACTION_VIEW, be sure to support
both files and
Next, understand what the role is of the
Uri you are being handed.
Uri values point to database records, such as if you let the user
pick a contact. That’s fine, but that’s not what I am covering here.
Uri values point to stuff that might be some sort of file —
that’s what this blog post is for.
Then, if the
Uri has a
content scheme, you can do the following
openInputStream()to get an
InputStreamto read in the content
getType()to get the MIME type of the data backed by that stream
query(), asking for the
OpenableColumns, where you can get the size of the content and some form of human-recognizable “display name” associated with the content
The first two of these are reminiscent of how you consume an
Uri, and so if you already have code for that, you may be
able to refactor to limit code duplication.
And that’s pretty much it.
What Not To Do
Unfortunately, not everyone likes that solution, even though it’s really the only supported option.
Just Grab the Path
Some developers call
getPath() on the
Uri, then try to open that
as a file (e.g.,
DO NOT DO THIS.
This was never reliable,
as it assumes a
file scheme and further assumes that you have direct
filesystem access to the location pointed to by the path.
Uri has any other scheme, such as
content, the path is
largely meaningless to you.
Uri as an opaque handle. Trying to pick pieces out
Uri and work with the pieces is unlikely to work out well for you.
Pretend That the MediaStore Knows What You’re Talking About
Some developers try to query the
MediaStore for its
DATA column, in
hopes that they can convert a
Uri to a filesystem path.
DO NOT DO THIS.
Uri comes from the
MediaStore. In fact, nowadays,
relatively few do.
MediaStore knows nothing about
Uri values from
other providers. Plus even if you get a
Uri, that might
be indexing media on removable storage, and you have no direct filesystem
access to that.
Try to Derive a Path
Some developers try a more sophisticated variant on the above approaches,
with two tons of code to look at the authority of the
and try to derive a filesystem path based upon heuristics worked out for
DO NOT DO THIS.
The fact that this is even possible is partly Google’s fault, for not
Uri values more obscure.
However, it too is not reliable:
Apps are welcome to change their
Uristructures, and so the heuristics that work today may not work tomorrow.
You may not have access to the file even if you can come up with the path.
There is about to be a Cambrian explosion of apps publishing their own content using their own providers and their own
Urivalues, courtesy of
filebeing banned. You need to support these providers, so you need to use the
Here’s a FAQ list, just with a different definition of the “F”.
How Do I Get the Filename?
You’re welcome to look at the last path segment of the
Uri. It might
be a filename. It might not. There is no requirement that a
Uri use a valid filename as the last path segment.
that you can get from the aforementioned
OpenableColumns might be
a filename, but it might not. The term “display name” does not necessarily
imply a filename.
Besides, depending on where this content is coming from, there might
not be an actual file that the user created that ties to this content.
Suppose somebody enters a long-form note on a note-taking app, and that
app makes that available via a
Uri for other apps to access. The user
did not create a file, upload a file, or have anything else file-esque to
do with this note. Even if there is a genuine filename for it, that
filename was app-generated and will not have meaning for the user.
OK, Then How Do I Get the File Extension?
Once again, you’re welcome to see if the
Uri ends in what looks like
a filename. There is no guarantee that something of the form
is actually a file with a file extension of
.bar, of course.
You can use
or similar sorts of converters, to try to take the MIME type that you
getType() and derive a file extension. However, those converters
only handle popular MIME types and cannot handle arbitrary ones.
What If I Need to Pass a File to a Library?
First, double-check the library to see whether it has an
variant of the
File-laden method that you are trying to use. If it does,
Next, consider replacing the library with one that does have an
Finally, make a local copy of the content, by getting an
ContentResolver, getting a
FileOutputStream on some local
file (e.g., inside
getCacheDir() for internal storage), and using
Java file I/O to copy from the
InputStream to the
you now have a file that you can pass to the library.
Of course, this is a copy of the content, and so it may become stale. This approach is fine for a one-time operation, and fine for cases where you legitimately need a local copy (more on this below).
How Long Can I Use the Uri?
Assuming that nothing happens on the provider’s side, the
be valid for the duration of your process, but it may not be valid after
Think of a
Uri as being akin to a deep-link URL to some content that
requires authentication. The
URL that you get may be good right now,
because the user has an authenticated session. The
URL may be good
for a little while longer, so long as the session is still around.
But eventually that session will time out, and the
URL will not be useful
But, What If I Need the Content for Longer Than That?
You can call
takePersistableUriPermission() on a
If the provider offered persistable permissions, and you take them,
the system will remember that you have access to the
Uri. Done carefully,
you can use this to have access to the content indefinitely, or until
the user moves, deletes, or otherwise renders the content inaccessible
via the old
The problem is that
takePersistableUriPermission() does not tell you
if it worked. You have to call
getPersistedUriPermissions() and see
if you got it. There may have been no persistable permissions to take.
Your other option is to make a local copy, as noted earlier in the FAQ.
Now, you are in control over your copy of the content. This may require
some adjustment to your UI and terminology. Instead of using verbs like
“link” (suggesting that the content is resident elsewhere), use
verbs like “import”, “attach”, or “copy”, to emphasize the fact that
the content is being copied. You might offer some sort of “refresh”,
“replace”, or “update” option, where the user can get you a fresh
that you can use to replace the existing local copy with a fresh local
Why Is This Such a Pain?
Google’s vision is for
Uri values to be the Android equivalent
https in Web apps: universally understood and used.
After all, a
can make this content available from:
- files, including ones that other parties cannot access
- BLOB columns in a database
- encrypted material that gets decrypted on the fly
- material that is stored over a network and needs to be downloaded and cached
- material that is generated on the fly (think Web service-style JSON publishing)
Uri values is much more flexible and offers more
fine-grained security options.
Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!