Consuming Content? Be Flexible!
Many times, when we work with a content
Uri
, we get an InputStream
,
read in the content, and we’re good to go.
Sometimes, though, consumers of content make some unfortunate assumptions.
One assumption is that you can pass rw
in for the mode on
openFileDescriptor()
and kin. First, this assumes that you have write access, which may or
may not be true. Plus, rw
requires that the content that you are
working with be stored as a plain file on the filesystem somewhere.
Another related assumption is that the InputStream
is seekable,
supporting mark()
and reset()
. Once again, this only works
if the content in question is backed by a plain file on the filesystem.
However, increasingly, that will not be the case, as more and more
developers turn to publishing content, to get around things like
the file
Uri
ban in Android 7.0.
While content may be backed by a plain file on the filesystem,
it might also be backed by:
-
A file, but one that needs to be transformed on the fly by the provider (e.g., decrypted)
-
A piece of a file (e.g., an asset packaged in the app’s APK)
-
Something in memory (e.g., the contents of a
BLOB
column that was read in from a database) -
Something being streamed from the Internet (though this is kinda risky IMHO, on the provider’s part)
The ContentProvider
publishing this content will be using something
other than a file-based ParcelFileDescriptor
, usually in the form
of a pipe or socket pair. Those do not support rw
mode, nor do they
support seeking.
In particular, if your app is usable with read-only non-seekable content,
but perhaps with a subset of functionality, make sure that you support
it. For example, a couple of Android PDF viewers — including Google’s
Drive PDF Viewer — seem to require rw
access and crash in situations
where other apps (e.g., Adobe Reader) work fine. Gracefully degrade in
these cases: try for rw
access, and then try for r
access if the rw
attempt fails.
If seekability is the issue, more so than rw
access, you can try
calling getStatSize()
on the ParcelFileDescriptor
that you get
back from openFileDescriptor()
on the ContentResolver
. If this
returns -1, then there is a good chance that the stream is not file-backed
and will not support seeking. If this returns 0 or a positive value,
that should be the size of the file that the stream is delivering to you.
If you need a test case for these cases, toss
my StreamProvider
in your test suite and test against an asset served via a content
Uri
. This will be a non-seekable stream on which rw
is not an
option (as you cannot modify an asset).
The majority of the time, when you get a content
Uri
, it will
be served by a provider that is using a file for the content. In that
case, rw
mode may be available, and seeking should work. Just do
not assume that you can always do that for every Uri
. Make
sure that you test cases where the Uri
is something else, and that
you handle those cases as best as you can.