If you want to make your data public, you can create (or call) a content provider. This is an object that can store and retrieve data accessible by all applications. This is the only way to share data across packages; there is no common storage area that all packages can share. Android ships with a number of content providers for common data types (audio, video, images, personal contact information, and so on). You can see some of Android's native content providers in the provider package.
How a content provider actually stores its data under the covers is up to the implementation of the content provider, but all content providers must implement a common convention to query for data, and a common convention to return results. However, a content provider can implement custom helper functions to make data storage/retrieval simpler for the specific data that it exposes.
This document covers two topics related to Content Providers:
This section describes how to store and retrieve data using a content provider implemented by you or anyone else. Android exposes a number of content providers for a wide range of data types, from music and image files to phone numbers. You can see a list of content providers exposed through the convenience classes in the android.provider package.
Android's content providers are loosely linked to their clients. Each content provider exposes a unique string (a URI) identifying the type of data that it will handle, and the client must use that string to store or retrieve data of that type. We'll explain this more in Querying for Data.
This section describes the following activities:
Each contact provider exposes a unique public URI (wrapped by android.net.Uri) that is used by a client to query/add/update/delete data on that content provider. This URI has two forms: one to indicate all values of that type (e.g., all personal contacts), and one form to indicate a specific record of that type (e.g., Joe Smith's contact information).
An application sends a query to the device that specifies a general type of item (all phone numbers), or a specific item (Bob's phone number), to retrieve. Android then returns a Cursor over a recordset of results, with a specific set of columns. Let's look at a hypothetical query string and a result set (the results have been trimmed a bit for clarity):
query = content://contacts/people/
Results:
| _ID | _COUNT | NUMBER | NUMBER_KEY | LABEL | NAME | TYPE |
|---|---|---|---|---|---|---|
| 13 | 4 | (425) 555 6677 | 425 555 6677 | California office | Bully Pulpit | Work |
| 44 | 4 | (212) 555-1234 | 212 555 1234 | NY apartment | Alan Vain | Home |
| 45 | 4 | (212) 555-6657 | 212 555 6657 | Downtown office | Alan Vain | Work |
| 53 | 4 | 201.555.4433 | 201 555 4433 | Love Nest | Rex Cars | Home |
Note that the query string isn't a standard SQL query string, but instead a URI string that describes the type of data to return. This URI consists of three parts: the string "content://"; a segment that describes what kind of data to retrieve; and finally an optional ID of a specific item of the specified content type. Here are a few more example query strings:
Although there is a general form, query URIs are somewhat arbitrary and confusing. Therefore, Android provides a list of helper classes in the android.provider package that define these query strings so you should not need to know the actual URI value for different data types. These helper classes define a string (actually, a wrapper class called Uri) called CONTENT_URI for a specific data type. For instance, android.provider.contacts.People.CONTENT_URI defines the query string used to search for people in Android's built-in people content provider.
Typically you will use the defined CONTENT_URI object to make a query. The only time you should need to examine or modify this string is to add an ID value to the end of the URI to retrieve a specific record. So, for example, if you were looking for record 23 in the people contacts, you might run a query as shown here:
// Get the base URI for contacts. Uri myPerson = android.provider.Contacts.People.CONTENT_URI; // Add the ID of the record I'm looking for (I'd have to know this somehow). myPerson.addId(23); // Query for this record. Cursor cur = managedQuery(myPerson, null, null, null);
This query returns a cursor over a database query result set. What columns are returned, what they're called, and what they are named are discussed next. For now, though, know that you can specify that only certain columns be returned, the sort order, and a SQL WHERE clause.
You should use the Activity.managedQuery() method to retrieve a managed cursor. A managed cursor handles all the niceties such as unloading itself when the application pauses, and requerying itself when the application restarts. You can ask Android to manage an unmanaged cursor for you by calling Activity.startManagingCursor().
Let's look at an example query to retrieve a list of contact names, phone numbers, and photos.
// An array specifying which columns to return.
// The provider exposes a list of column names it returns for a specific
// query, or you can get all columns and iterate through them.
string[] projection = new string[] {
android.provider.BaseColumns._ID,
android.provider.Contacts.PeopleColumns.NAME,
android.provider.Contacts.PhonesColumns.NUMBER,
android.provider.Contacts.PeopleColumns.PHOTO
};
// Best way to retrieve a query; returns a managed query.
Cursor managedCursor = managedQuery( android.provider.Contacts.Phones.CONTENT_URI,
projection, //Which columns to return.
null, // WHERE clause--we won't specify.
android.provider.Contacts.PeopleColumns.NAME + " ASC"); // Order-by clause.
This query will retrieve all the phone numbers stored in the phone number contacts provider. It will retrieve the name, number, and unique record ID for each contact. We can then scroll through the result set forward or backward, delete records, add records, and modify records, as described in the next sections.
What the query returns
A query returns a set of zero or more database records. The column names, order, and type are specific to the content provider, but every query includes a column called _id, which is the ID of the item in that row. If a query can return binary data, such as a bitmap or audio file, it will have a column with any name that holds a content:// URI that you can use to get this data (more information on how to get the file will be given later). Here is a tiresome example result set for the previous query:
| _id | name | number | photo |
|---|---|---|---|
| 44 | Alan Vain | 212 555 1234 | content://images/media/123 |
| 13 | Bully Pulpit | 425 555 6677 | content://images/media/128 |
| 53 | Rex Cars | 201 555 4433 | content://images/media/332 |
This result set demonstrates what is returned when we specified a subset of columns to return. The optional subset list is passed in the projection parameter of the query. A content manager should list which columns it supports either by implementing a set of interfaces describing each column (see Contacts.People.Phones, which extends BaseColumns, PhonesColumns, and PeopleColumns), or by listing the column names as constants. Note that you need to know the data type of a column exposed by a content provider in order to be able to read it; the field reading method is specific to the data type, and a column's data type is not exposed programmatically.
The retrieved data is exposed by a Cursor object that can be used to iterate backward or forward through the result set. You can use this cursor to read, modify, or delete rows. Adding new rows requires a different object described later.
Note that by convention, every recordset includes a field named _id, which is the ID of a specific record, and a _count field, which is a count of records in the current result set. These field names are defined by BaseColumns.
Querying for Files
The previous query result demonstrates how a file is returned in a data set. The file field is typically (but not required to be) a string path to the file. However, the caller should never try to read and open the file directly (permissions problems for one thing can make this fail). Instead, you should call ContentResolver.openInputStream() / ContentResolver.openOutputStream(), or one of the helper functions from a content provider.
Reading Retrieved Data
The Cursor object retrieved by the query provides access to a recordset of results. If you have queried for a specific record by ID, this set will contain only one value; otherwise, it can contain multiple values. You can read data from specific fields in the record, but you must know the data type of the field, because reading data requires a specialized method for each type of data. (If you call the string reading method on most types of columns, Android will give you the String representation of the data.) The Cursor lets you request the column name from the index, or the index number from the column name.
If you are reading binary data, such as an image file, you should call ContentResolver.openOutputStream() on the string content:// URI stored in a column name.
The following snippet demonstrates reading the name and phone number from our phone number query:
private void getColumnData(Cursor cur){
if (cur.first()) {
String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(android.provider.Contacts.PeopleColumns.NAME);
int phoneColumn = cur.getColumnIndex(android.provider.Contacts.PhonesColumns.NUMBER);
int pathColumn = cur.getColumnIndex(android.provider.Contacts.PeopleColumns.PHOTO);
String imagePath;
do {
// Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);
imagePath = cur.getString(stringColumn);
InputStream is = getContentResolver().openInputStream(imagePath);
... read the file stream into something...
is.close()
// Do something with the values.
...
} while (cur.next());
}
}
Note that the image field is actually a string path value. Some content provider convenience classes provide helper methods to get specific field values such as files more easily.
Remember, whenever calling updating methods on the Cursor class, you must call commitUpdates() to send the changes to the database.
To update an individual record, set the Cursor to the appropriate object, call the appropriate update... method, and then call commitUpdates().
To batch update a group of records (for example, to change "NY" to "New York" in all contact fields), call the ContentResolver.update() method with the columns and values to change.
Remember, whenever calling updating methods on the Cursor class, you must call commitUpdates() to send the changes to the database.
To add a new record, call ContentResolver.insert() with the URI of the type of item to add, and a Map of any values you want to set immediately on the new record. This will return the full URI of the new record, including record number, which you can then use to query and get a Cursor over the new record.
Remember, whenever calling updating methods on the Cursor class, you must call commitUpdates() to send the changes to the database.
To save a file, you can call ContentResolver().openOutputStream() with the URI as shown in the following snippet:
// Save the name and description in a map. Key is the content provider's // column name, value is the value to save in that record field. HashMap<String, Object> values = new HashMap<String, Object>(); values.put(Media.Images.NAME, "road_trip_1"); values.put(Media.Images.DESCRIPTION, "Day 1, trip to Los Angeles"); // Add a new record without the bitmap, but with the values. // It returns the URI of the new record. Uri uri = getContentResolver().insert(Media.Images.CONTENT_URI, values); // Now get a handle to the file for that record, and save the data into it. // sourceBitmap is a Bitmap object representing the file to save to the database. OutputStream outStream = getContentResolver.openOutputStream(uri); sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); outStream.close();
To delete a single record, call ContentResolver.delete() with the URI of a specific row, or call Cursor.deleteRow().
To delete multiple rows, call ContentResolver.delete() with the URI of the type of record to delete (for example, android.provider.Contacts.People.CONTENT_URI) and a SQL WHERE clause defining which rows to delete (Warning: be sure to include a valid WHERE clause if deleting a general type using ContentResolver.delete(), or else you risk deleting more records than you intended!).
Remember, whenever calling updating methods on the Cursor class, you must call commitUpdates() to send the changes to the database.
Here is how to create your own content provider to act as a public source for reading and writing a new data type:
public static final Uri named
CONTENT_URI. This is the string that represents the full "content://" URI
that your content provider handles. You must define a unique string for this
value; the best solution is to use the fully-qualified class name of your
content provider (lowercase). So, for example: public static final Uri CONTENT_URI = Uri.parse(
"content://com.google.codelab.rssprovider");_id to
define a specific record number. If using the SQLite database, this should
be type INTEGER
PRIMARY KEY AUTOINCREMENT.
The AUTOINCREMENT descriptor
is optional, but by default, SQLite
autoincrements an ID counter field to the next number above the largest
existing number in the table. If you delete the last row, the next row added
will have the same ID as the deleted row. To avoid this by having SQLite
increment to the next largest value whether deleted or not, then assign your
ID column the following type: INTEGER PRIMARY KEY AUTOINCREMENT. (Note You
should have a unique _id field whether or not you have another field (such
as a URL) that is also unique among all records.) Android provides the
SQLiteOpenHelper
class to help you create and manage versions of your database. <provider> tag to AndroidManifest.xml, and use its
authorities attribute to define the authority part of the content type it should
handle. For example, if your content type is content://com.example.autos/auto
to request a list of all autos, then authorities would be com.example.autos.
Set the multiprocess attribute to true if data does not need to
be synchronized between multiple running versions of the content provider. content:// URI
submitted to getType(), which will be one of the content types handled by
the provider. The MIME type for each content type has two forms: one for
a specific record, and one for multiple records. Use the Uri methods to help determine what is being requested. Here is
the general format for each:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype
for a single row. For example, a request for train record 122 using
content://com.example.transportationprovider/trains/122might return the MIME type
vnd.android.cursor.item/vnd.example.rail
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
for multiple rows. For example, a request for all train records using
content://com.example.transportationprovider/trainsmight return the MIME type
vnd.android.cursor.dir/vnd.example.rail
For an example of a private content provider implementation, see the NodePadProvider class in the notepad sample application that ships with the SDK.
Here is a recap of the important parts of a content URI:
<provider> element's authorities attribute:
<provider class="TransportationProvider"
authorities="com.example.transportationprovider"
/> land/bus, land/train, sea/ship,
and sea/submarine" to give four possibilities.content://com.example.transportationprovider/trains