How to Consume Content From a Uri
Since it seems even clearer that
the 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 Uri
to
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 file
scheme, that you also have an <intent-filter>
for content
, scoped
for the appropriate MIME type(s). You may get a content
Uri
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 content
Uri
values.
Next, understand what the role is of the Uri
you are being handed.
Some 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.
Other 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
with a ContentResolver
instance:
-
Call
openInputStream()
to get anInputStream
to read in the content -
Call
getType()
to get the MIME type of the data backed by that stream -
Call
query()
, asking for theOpenableColumns
, 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 http
or https
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., new File(uri.getPath())
).
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.
If the Uri
has any other scheme, such as content
, the path is
largely meaningless to you.
Treat the Uri
as an opaque handle. Trying to pick pieces out
of that 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.
Not every content
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 MediaStore
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 content
Uri
and try to derive a filesystem path based upon heuristics worked out for
different authorities.
DO NOT DO THIS.
The fact that this is even possible is partly Google’s fault, for not
making their Uri
values more obscure.
However, it too is not reliable:
-
Apps are welcome to change their
Uri
structures, 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
content
Uri
values, courtesy offile
being banned. You need to support these providers, so you need to use theopenInputStream()
approach anyway.
Fictionally-Asked Questions
Here’s a FAQ list, just with a different definition of the “F”.
How Do I Get the Filename?
You don’t.
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 content
Uri
use a valid filename as the last path segment.
The DISPLAY_NAME
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?
You don’t.
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 foo.bar
is actually a file with a file extension of .bar
, of course.
You can use MimeTypeMap
,
or similar sorts of converters, to try to take the MIME type that you
get from 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 InputStream
variant of the File
-laden method that you are trying to use. If it does,
use the InputStream
one.
Next, consider replacing the library with one that does have an InputStream
option.
Finally, make a local copy of the content, by getting an InputStream
from the 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 OutputStream
.
flush()
, getFD().sync()
, and close()
the FileOutputStream
, and
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?
Not long.
Assuming that nothing happens on the provider’s side, the Uri
should
be valid for the duration of your process, but it may not be valid after
that.
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
any longer.
But, What If I Need the Content for Longer Than That?
You can call
takePersistableUriPermission()
on a ContentResolver
.
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 Uri
.
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 Uri
that you can use to replace the existing local copy with a fresh local
copy.
Why Is This Such a Pain?
Google’s vision is for content
Uri
values to be the Android equivalent
of http
/https
in Web apps: universally understood and used.
After all, a ContentProvider
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)
Using content
Uri
values is much more flexible and offers more
fine-grained security options.