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 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, these 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 Uri object) called CONTENT_URI for a specific data type.
Typically you will use the defined CONTENT_URI object to make a query, instead of writing the full URI yourself. So, each of the example query strings listed above (except for the last one that specifies the record ID) can be acquired with the following Uri references:
To query a specific record ID (e.g., content://contacts/people/23), you'll use the same CONTENT_URI, but must append the specific ID value that you want. This is one of the few times you should need to examine or modify the URI string. 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 contact with _ID=23. // This is same as Uri.parse("content://contacts/people/23"); Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23); // Query for this record. Cursor cur = managedQuery(myPerson, null, null, null);
Tip: You can also append a string to a Uri, using withAppendedPath(Uri, String).
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 and their primary phone numbers.
// An array specifying which columns to return. string[] projection = new string[] { People._ID, People.NAME, People.NUMBER, }; // Get the base URI for People table in Contacts content provider. // ie. content://contacts/people/ Uri mContacts = People.CONTENT_URI; // Best way to retrieve a query; returns a managed query. Cursor managedCursor = managedQuery( mContacts, projection, //Which columns to return. null, // WHERE clause--we won't specify. People.NAME + " ASC"); // Order-by clause.
This query will retrieve data from the people table of the Contacts content provider. It will retrieve the name, primary phone number, and unique record ID for each contact.
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 |
---|---|---|
44 | Alan Vain | 212 555 1234 |
13 | Bully Pulpit | 425 555 6677 |
53 | Rex Cars | 201 555 4433 |
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.moveToFirst()) { String name; String phoneNumber; int nameColumn = cur.getColumnIndex(People.NAME); int phoneColumn = cur.getColumnIndex(People.NUMBER); String imagePath; do { // Get the field values name = cur.getString(nameColumn); phoneNumber = cur.getString(phoneColumn); // Do something with the values. ... } while (cur.moveToNext()); } }
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.
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.
ContentValues values = new ContentValues(); Uri phoneUri = null; Uri emailUri = null; values.put(Contacts.People.NAME, "New Contact"); //1 = the new contact is added to favorites //0 = the new contact is not added to favorites values.put(Contacts.People.STARRED,1); //Add Phone Numbers Uri uri = getContentResolver().insert(Contacts.People.CONTENT_URI, values); //The best way to add Contacts data like Phone, email, IM is to //get the CONTENT_URI of the contact just inserted from People's table, //and use withAppendedPath to construct the new Uri to insert into. phoneUri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY); values.clear(); values.put(Contacts.Phones.TYPE, Phones.TYPE_MOBILE); values.put(Contacts.Phones.NUMBER, "1233214567"); getContentResolver().insert(phoneUri, values); //Add Email emailUri = Uri.withAppendedPath(uri, ContactMethods.CONTENT_DIRECTORY); values.clear(); //ContactMethods.KIND is used to distinguish different kinds of //contact data like email, im, etc. values.put(ContactMethods.KIND, Contacts.KIND_EMAIL); values.put(ContactMethods.DATA, "test@example.com"); values.put(ContactMethods.TYPE, ContactMethods.TYPE_HOME); getContentResolver().insert(emailUri, values);
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. ContentValues values = new ContentValues(3); values.put(MediaStore.Images.Media.DISPLAY_NAME, "road_trip_1"); values.put(MediaStore.Images.Media.DESCRIPTION, "Day 1, trip to Los Angeles"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); // Add a new record without the bitmap, but with the values. // It returns the URI of the new record. Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try { // 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(); } catch (Exception e) { Log.e(TAG, "exception while writing image", e); }
To delete a single record, call ContentResolver.delete() with the URI of a specific row.
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!).
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