Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store large blobs in an android content provider?

I have some large files (images and video) which I need to store in a content provider. The android documentation indicates...

If you are exposing byte data that's too big to put in the table itself — such as a large bitmap file — the field that exposes the data to clients should actually contain a content: URI string. This is the field that gives clients access to the data file. The record should also have another field, named "_data" that lists the exact file path on the device for that file. This field is not intended to be read by the client, but by the ContentResolver. The client will call ContentResolver.openInputStream() on the user-facing field holding the URI for the item. The ContentResolver will request the "_data" field for that record, and because it has higher permissions than a client, it should be able to access that file directly and return a read wrapper for the file to the client. -- http://developer.android.com/guide/topics/providers/content-providers.html#creating

I am having some difficulty finding an example. In particular I wish to use the bitmap in the context an ImageView. Consider the following code quasi-code (it doesn't work)...

ImageView iv = ....
String iconUri = cursor.getString(cursor.getColumnIndex(Table.ICON));
iv.setImageURI(Uri.parse(iconUri));

Observations/Problems...

  1. How can the stored/recovered uri be reconstructed correctly? (it is text in the table)
  2. The setImageURI implementation makes use of the content resolve openInputStream so this should work.

    String scheme = mUri.getScheme();
    ...
    } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
            || ContentResolver.SCHEME_FILE.equals(scheme)) {
      try {
        d = Drawable.createFromStream(
                mContext.getContentResolver().openInputStream(mUri),
                null);
    

    --frameworks/base/core/java/android/widget/ImageView.java

I got it working. I took a hint from the MediaStore and MediaProvider. The files which contain the data are named based on the content provider (directory), the column name, the row id and the media type. The content resolver then acquires the file descriptor like so...

Uri iconUri = Uri.withAppendedPath(Table.getUri(cursor), Table.ICON);
ib.setImageURI(iconUri);

...and the content provider responds in kind...

@Override
public ParcelFileDescriptor openFile (Uri uri, String mode) {
int imode = 0;
if (mode.contains("w")) imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;
List<String> pseg = uri.getPathSegments();
if (pseg.size() < 3) return null;

try {
    File filePath = filePathFromRecord(pseg.get(2), pseg.get(1));
    return ParcelFileDescriptor.open(filePath, imode);
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
return null;
}
like image 521
phreed Avatar asked Oct 07 '10 15:10

phreed


People also ask

What is ContentResolver in Android?

The ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete) functions of persistent storage. A common pattern for accessing a ContentProvider from your UI uses a CursorLoader to run an asynchronous query in the background.

What is the purpose of ContentProvider class?

A content provider manages access to a central repository of data. You implement a provider as one or more classes in an Android application, along with elements in the manifest file. One of your classes implements a subclass ContentProvider , which is the interface between your provider and other applications.

What does ContentResolver query () return?

The ContentResolver. query() client method always returns a Cursor. This cursor contains the column specified by the query projection. The cursor object provides read access to rows and columns.

Is SQLite a content provider?

Usually, Android uses SQLite database and framework needs to override onCreate() method which will use SQLite Open Helper method to create or open the provider's database. When your application is launched, the onCreate() handler of each of its Content Providers is called on the main application thread.


2 Answers

The solution phreed gives in the bottom half of question is basically correct. I'll try to add some more details here.

When you do getContentResolver().openInputStream(...), content resolver will go to your content provider and call its openFile method. This is how the openFile looks in ContentProvider.java:

public ParcelFileDescriptor openFile(Uri uri, String mode)
     throws FileNotFoundException {
 throw new FileNotFoundException("No files supported by provider at "
         + uri);
}

So this explains where the "No files supported ..." error exactly comes from! You get around this by overriding openFile method in your subclass and providing your own implementation. It's neat: you get perfect control of where your files get placed when any client does openInputStream or openOutputStream.

Code sample in phreed's question gives a hint how the implementation could look like. Here's my slightly modified version which also creates directories and files as needed. I'm novice at this stuff so this might not be the optimal way of doing things, but it gives an idea. For one thing, it should probably check if external storage is available.

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    File root = new File(Environment.getExternalStorageDirectory(), 
            "/Android/data/com.example.myapp/cache");
    root.mkdirs();
    File path = new File(root, uri.getEncodedPath());
    // So, if the uri was content://com.example.myapp/some/data.xml,
    // we'll end up accessing /Android/data/com.example.myapp/cache/some/data.xml

    int imode = 0;
    if (mode.contains("w")) {
            imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
            if (!path.exists()) {
                try {
                    path.createNewFile();
                } catch (IOException e) {
                    // TODO decide what to do about it, whom to notify...
                    e.printStackTrace();
                }
            }
    }
    if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
    if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;        

    return ParcelFileDescriptor.open(path, imode);
}
like image 70
Pēteris Caune Avatar answered Sep 18 '22 12:09

Pēteris Caune


Android provides the helper method openFileHelper() that makes implementing the openFile() method very easy. All you have to do, to use this method, is to provide the location of the file in a column named “_data“.

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
      throws FileNotFoundException {
   if (URI_MATCHER.match(uri) != PHOTO_ID) {
      throw new IllegalArgumentException
            ("URI invalid. Use an id-based URI only.");
   }
   return openFileHelper(uri, mode);
}

Click here for detail

like image 33
solo Avatar answered Sep 18 '22 12:09

solo