【翻译】(9)内容提供者
see
http://developer.android.com/guide/topics/providers/content-providers.html
原文见
http://developer.android.com/guide/topics/providers/content-providers.html
-------------------------------
Content Providers
内容提供者
-------------------------------
In this document
本文目录
* Content provider basics 内容提供者基础
* Querying a content provider 查询内容提供者
* Modifying data in a provider 修改提供者内的数据
* Creating a content provider 创建内容提供者
* Content URI summary 内容URI概要
Key classes
关键类
ContentProvider
ContentResolver
Cursor
See also
另见
Calendar Provider 日历提供者
-------------------------------
Content providers store and retrieve data and make it accessible to all applications. They're the only way to share data across applications; there's no common storage area that all Android packages can access.
内容提供者存储和检索数据并让所有应用程序可以访问它。它们是跨应用程序共享数据的唯一方式;没有公共的存储区域让所有Android包都可以访问。
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 them listed in the android.provider package. You can query these providers for the data they contain (although, for some, you must acquire the proper permission to read the data).
Android自带大量公共数据类型(音频、视频、图像、个人通讯录信息,等等)的内容提供者。你可以看到它们中的一些列举在android.provider包内。你可以通过查询这些提供者得到它们包含的数据(虽然,对于其中一些,你必须取得读数据的正确权限)。
-------------------------------
Note: Android 4.0 introduces the Calendar Provider. For more information, see Calendar Provider.
注意:Android 4.0引入日历提供者。想获取更多信息,请参见日历提供者。
-------------------------------
If you want to make your own data public, you have two options: You can create your own content provider (a ContentProvider subclass) or you can add the data to an existing provider — if there's one that controls the same type of data and you have permission to write to it.
如果你希望让你自己的数据成为公有,那么你有两个选择:你可以创建你自己的内容提供者(ContentProvider的子类)或者你可以添加数据到现存的提供者中——如果存在控制相同类型数据的提供者,而你拥有写入它的权限的话。
This document is an introduction to using content providers. After a brief discussion of the fundamentals, it explores how to query a content provider, how to modify data controlled by a provider, and how to create a content provider of your own.
本文是使用内容提供者的介绍。在一个简短的基本要素讨论后,将探讨如何查询一个内容提供者,如何修改被提供者控制的数据,以及如何创建你自己的内容提供者。
-------------------------------
Content Provider Basics
内容提供者基础
How a content provider actually stores its data under the covers is up to its designer. But all content providers implement a common interface for querying the provider and returning results — as well as for adding, altering, and deleting data.
内容提供者的底层实际上如何存储它的数据是由它的设计者决定的。但是所有内容提供者实现一个用于查询提供者的公共接口并返回结果——以及用于添加、修改、删除数据。
It's an interface that clients use indirectly, most generally through ContentResolver objects. You get a ContentResolver by calling getContentResolver() from within the implementation of an Activity or other application component:
它是客户端间接使用的接口,通常大多数是通过ContentResolver对象获得。你通过调用活动的实现中的或其它应用程序组件中的getContentResolver()获取一个ContentResolver对象。
-------------------------------
ContentResolver cr = getContentResolver();
-------------------------------
You can then use the ContentResolver's methods to interact with whatever content providers you're interested in.
然后你可以使用ContentResolver的方法与你感兴趣的任意内容提供者交互。
When a query is initiated, the Android system identifies the content provider that's the target of the query and makes sure that it is up and running. The system instantiates all ContentProvider objects; you never need to do it on your own. In fact, you never deal directly with ContentProvider objects at all. Typically, there's just a single instance of each type of ContentProvider. But it can communicate with multiple ContentResolver objects in different applications and processes. The interaction between processes is handled by the ContentResolver and ContentProvider classes.
当一个查询被初始化时,Android系统标识查询目标的内容提供者并确保它开启和正在运行。系统实例化所有ContentProvider对象;你从不需要自己实例化它。事实上,你从不直接处理ContentProvider对象。通常,每个类型的ContentProvider只有一个单一实例。但它可以与不同应用程序和进程内的多个ContentResolver对象通信。进程间的交互由ContentResolver和ContentProvider类处理。
The data model
数据模型
Content providers expose their data as a simple table on a database model, where each row is a record and each column is data of a particular type and meaning. For example, information about people and their phone numbers might be exposed as follows:
内容提供者暴露它们的数据作为数据库模型中的简单表,每一行是一条记录而每一列是特定类型和含义的数据。例如,关于联系人和他们的电话号码的信息可能被暴露如下:
-------------------------------
_ID NUMBER NUMBER_KEY LABEL NAME TYPE
13 (425) 555 6677 425 555 6677 Kirkland office Bully Pulpit TYPE_WORK
44 (212) 555-1234 212 555 1234 NY apartment Alan Vain TYPE_HOME
45 (212) 555-6657 212 555 6657 Downtown office Alan Vain TYPE_MOBILE
53 201.555.4433 201 555 4433 Love Nest Rex Cars TYPE_HOME
-------------------------------
Every record includes a numeric _ID field that uniquely identifies the record within the table. IDs can be used to match records in related tables — for example, to find a person's phone number in one table and pictures of that person in another.
每条记录包含一个数字的_ID域,它是表内记录的唯一标识符。可以使用ID匹配相关表的记录——例如,在一个表内查找联系人的电话号码和另一个表中那个人的照片。
A query returns a Cursor object that can move from record to record and column to column to read the contents of each field. It has specialized methods for reading each type of data. So, to read a field, you must know what type of data the field contains. (There's more on query results and Cursor objects later.)
一个查询返回一个Cursor对象,它可以从行到行以及从列到列移动,读取每个域的内容。它有专门读取每种类型数据的方法。所以,为了读取一个域,你必须知道那个域包含什么类型的数据。(后面会更详细讨论查询结果和Cursor对象。)
URIs
URI(注:统一资源标识)
Each content provider exposes a public URI (wrapped as a Uri object) that uniquely identifies its data set. A content provider that controls multiple data sets (multiple tables) exposes a separate URI for each one. All URIs for providers begin with the string "content://". The content: scheme identifies the data as being controlled by a content provider.
每个组件提供者暴露一个公开的URI(封装为Uri对象),它唯一地标识它的数据集。一个控制多个数据集(多个表)的内容提供者分别暴露各自的URI。提供者的所有URI以字符串"content://"开头。content:的模式表示数据是被内容提供者控制。
If you're defining a content provider, it's a good idea to also define a constant for its URI, to simplify client code and make future updates cleaner. Android defines CONTENT_URI constants for all the providers that come with the platform. For example, the URI for the table that matches phone numbers to people and the URI for the table that holds pictures of people (both controlled by the Contacts content provider) are:
如果你正在定义一个内容提供者,那么同时定义它的URI常量是个好主意,以简化客户端代码并使将来的更改更干净。Android为所有来自平台的提供者定义CONTENT_URI常量。例如,匹配联系人的电话号码的表的URI和持有联系人的照片的表的URI(它们都被Contacts内容提供者控制)是:
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
The URI constant is used in all interactions with the content provider. Every ContentResolver method takes the URI as its first argument. It's what identifies which provider the ContentResolver should talk to and which table of the provider is being targeted.
URI常量用于所有与内容提供者的交互。每个ContentResolver方法把URI作为它的第一参数。它标识应该与ContentResolver对话的提供者和提供者的目标是哪个表。
-------------------------------
Querying a Content Provider
查询内容提供者
You need three pieces of information to query a content provider:
你需要三种信息来查询内容提供者:
* The URI that identifies the provider
* 标识提供者的URI
* The names of the data fields you want to receive
* 你想接收的数据域的名称
* The data types for those fields
* 那些域的数据类型
If you're querying a particular record, you also need the ID for that record.
如果你正在查询一个特定的记录,那么你还需要记录的ID。
Making the query
进行查询
To query a content provider, you can use either the ContentResolver.query() method or the Activity.managedQuery() method. Both methods take the same set of arguments, and both return a Cursor object. However, managedQuery() causes the activity to manage the life cycle of the Cursor. A managed Cursor handles all of the niceties, such as unloading itself when the activity pauses, and requerying itself when the activity restarts. You can ask an Activity to begin managing an unmanaged Cursor object for you by calling Activity.startManagingCursor().
为了查询一个内容提供者,你可以使用ContentResolver.query()方法或Activity.managedQuery()方法。两个方法带有相同的参数集,而且都返回Cursor对象。然而managedQuery()导致活动管理Cursor的生命周期。一个托管Cursor处理所有细节,诸如当活动暂停时卸载自身,以及当活动重启时重新查询它自己。你可以通过调用Activity.startManagingCursor(),叫一个Activity对象开始为你托管一个未托管的Cursor对象。
The first argument to either query() or managedQuery() is the provider URI — the CONTENT_URI constant that identifies a particular ContentProvider and data set (see URIs earlier).
query()或managedQuery()的第一个参数是提供者的URI——CONTENT_URI常量,标识一个特定ContentProvider和数据集(见前面的URI)。
To restrict a query to just one record, you can append the _ID value for that record to the URI — that is, place a string matching the ID as the last segment of the path part of the URI. For example, if the ID is 23, the URI would be:
为了读取仅一条记录的查询,你可以在那条记录的URI后面尾加_ID值——就是说,放置一个匹配ID的字符串,作为URI路径部分的最后一段。例如,如果ID是23,URI将是:
content://. . . ./23
There are some helper methods, particularly ContentUris.withAppendedId() and Uri.withAppendedPath(), that make it easy to append an ID to a URI. Both are static methods that return a Uri object with the ID added. So, for example, if you were looking for record 23 in the database of people contacts, you might construct a query as follows:
有一些辅助方法,特别是ContentUris.withAppendedId()和Uri.withAppendedPath(),简化添加ID到URI的工作。它们都是静态方法,返回添加了ID的Uri对象。所以,例如如果你正在查找联系人通讯录数据库中的记录23,你可能构造如下的查询:
-------------------------------
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;
// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
// 使用ContentUris的方法,用_ID == 23生成通讯录的基本URI
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Alternatively, use the Uri method to produce the base URI.
// It takes a string rather than an integer.
// 或者,使用Uri的方法生成基本URI。
// 它传入一个字符串而非整型。
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
// Then query for this specific record:
// 然后查询这条特定的记录。
Cursor cur = managedQuery(myPerson, null, null, null, null);
-------------------------------
The other arguments to the query() and managedQuery() methods delimit the query in more detail. They are:
query()和managedQuery()方法的其它参数更详细地划分查询。它们是:
* The names of the data columns that should be returned. A null value returns all columns. Otherwise, only columns that are listed by name are returned. All the content providers that come with the platform define constants for their columns. For example, the android.provider.Contacts.Phones class defines constants for the names of the columns in the phone table illustrated earlier — _ID, NUMBER, NUMBER_KEY, NAME, and so on.
* 应该返回的数据列的名称。null值返回所有列。否则,只有用名称列出的列才返回。所有平台自带的内容提供者定义它们的列的常量。例如android.provider.Contacts.Phones类定义了前面描述的电话表内的列的常量——_ID,NUMBER,NUMBER_KEY,NAME,等等。
* A filter detailing which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). A null value returns all rows (unless the URI limits the query to a single record).
* 详细表明返回哪个行的过滤器,作为一个SQL WHERE子句格式化(不包括WHERE自身)。一个null值返回所有行(除非URI限制查询单一记录)。
* Selection arguments.
* 选择的参数
* A sorting order for the rows that are returned, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). A null value returns the records in the default order for the table, which may be unordered.
* 返回行的排列顺序,作为SQL ORDER BY子句格式化(不包括ORDER BY自身)。一个null值以表的默认顺序返回记录,它可能未排序。
Let's look at an example query to retrieve a list of contact names and their primary phone numbers:
让我们看看一个示例查询,它检索一列通讯录名称以及他们的主电话号码。
-------------------------------
import android.provider.Contacts.People;
import android.database.Cursor;
// Form an array specifying which columns to return.
// 形成一个数组,指示要返回的列
String[] projection = new String[] {
People._ID,
People._COUNT,
People.NAME,
People.NUMBER
};
// Get the base URI for the People table in the Contacts content provider.
// 获取在Contacts内容提供者中People表的基本URI
Uri contacts = People.CONTENT_URI;
// Make the query.
// 进行查询
Cursor managedCursor = managedQuery(contacts,
projection, // Which columns to return 要返回的列
null, // Which rows to return (all rows) 要返回的行(所有行)
null, // Selection arguments (none) 选择参数(无)
// Put the results in ascending order by name
// 结果根据名称次序升序排列
People.NAME + " ASC");
-------------------------------
This query retrieves data from the People table of the Contacts content provider. It gets the name, primary phone number, and unique record ID for each contact. It also reports the number of records that are returned as the _COUNT field of each record.
这个查询检索来自Contacts内容提供者People表的数据。它获取每个通讯录的名称,主电话号码,以及唯一记录ID。它还报告返回的记录条数作为每条记录的_COUNT域。
The constants for the names of the columns are defined in various interfaces — _ID and _COUNT in BaseColumns, NAME in PeopleColumns, and NUMBER in PhoneColumns. The Contacts.People class implements each of these interfaces, which is why the code example above could refer to them using just the class name.
列常量被定义在不同的接口——_ID和_COUNT定义在BaseColumns,NAME定义在PeopleColumns中,而NUMBER定义在PhoneColumns中。Contacts.People类实现这些接口的每一个,这就是上面可能引用它们的示例代码只使用类名的原因。
What a query returns
查询返回什么
A query returns a set of zero or more database records. The names of the columns, their default order, and their data types are specific to each content provider. But every provider has an _ID column, which holds a unique numeric ID for each record. Every provider can also report the number of records returned as the _COUNT column; its value is the same for all rows.
查询返回零个或以上数据库记录的集合。列名,它们的默认顺序,以及它们的数据类型特定于每个内容提供者。但每个提供者拥有一个_ID列,每条记录持有一个唯一的数字ID。每个提供者还可能报告记录数,作为_COUNT列返回;它的值对于所有行是相同的。
Here is an example result set for the query in the previous section:
这里是前一章节的查询的一个示例结果集:
-------------------------------
_ID _COUNT NAME NUMBER
44 3 Alan Vain 212 555 1234
13 3 Bully Pulpit 425 555 6677
53 3 Rex Cars 201 555 4433
-------------------------------
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 object only to read the data. To add, modify, or delete data, you must use a ContentResolver object.
检索的数据被Cursor对象暴露,那个Cursor对象可以用于向后或向前地迭代结果集。你只能使用这个对象读数据。为了添加、修改、或删除数据,你必须使用ContentResolver对象。
Reading retrieved data
读取检索的数据
The Cursor object returned by a 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. (If there are no matches, it can also be empty.) You can read data from specific fields in the record, but you must know the data type of the field, because the Cursor object has a separate method for reading each type of data — such as getString(), getInt(), and getFloat(). (However, for most types, if you call the method for reading strings, the Cursor object will give you the String representation of the data.) The Cursor lets you request the column name from the index of the column, or the index number from the column name.
查询返回的Cursor对象访问结果的记录集合。如果你用ID查询一个特定记录,这个集合将只包含一个值。否则,它可能包含多个值。(如果没有匹配,它还可能是空的。)你可以从记录的特定域中读取数据,但你必须知道域的数据类型,因为Cursor对象拥有单独读取每种数据的方法——诸如getString(),getInt(),以及getFloat()。(然而,对于大多数类型,如果你调用读取字符串的方法,那么Cursor对象将给你数据的String表示。)Cursor让你从列名的索引中,或从列名的索引数,请求列名。
The following snippet demonstrates reading names and phone numbers from the query illustrated earlier:
下面的代码片段演示从前面描述的查询中读取名称和电话号码。
-------------------------------
import android.provider.Contacts.People;
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());
}
}
-------------------------------
If a query can return binary data, such as an image or sound, the data may be directly entered in the table or the table entry for that data may be a string specifying a content: URI that you can use to get the data. In general, smaller amounts of data (say, from 20 to 50K or less) are most often directly entered in the table and can be read by calling Cursor.getBlob(). It returns a byte array.
如果查询可以返回二进制数据,诸如图片或声音,那么数据可以是直接地在表格里输入,或数据的表格记录可以是一个指定内容的字符串:你可以用来取得数据的URI。
If the table entry is a content: URI, you should never try to open and read the file directly (for one thing, permissions problems can make this fail). Instead, you should call ContentResolver.openInputStream() to get an InputStream object that you can use to read the data.
如果表格记录是一个content: URI,那么你应该从不尝试直接打开和读取文件(首先,权限问题可以导致它失败)。相反,你应该调用ContentResolver.openInputStream()获取一个InputStream对象,你可以用它读取数据。
-------------------------------
Modifying Data
修改数据
Data kept by a content provider can be modified by:
被内容提供者持有的数据可以用以下方法修改:
* Adding new records
* 添加新记录
* Adding new values to existing records
* 添加新值到现存的记录
* Batch updating existing records
* 批量更新现存记录
* Deleting records
* 删除记录
All data modification is accomplished using ContentResolver methods. Some content providers require a more restrictive permission for writing data than they do for reading it. If you don't have permission to write to a content provider, the ContentResolver methods will fail.
所有数据修改通过ContentResolver方法来完成。一些内容提供者需要一个比读数据更受限的写数据权限。如果你没有权限写内容提供者,那么ContentResolver的方法将失败。
Adding records
添加记录
To add a new record to a content provider, first set up a map of key-value pairs in a ContentValues object, where each key matches the name of a column in the content provider and the value is the desired value for the new record in that column. Then call ContentResolver.insert() and pass it the URI of the provider and the ContentValues map. This method returns the full URI of the new record — that is, the provider's URI with the appended ID for the new record. You can then use this URI to query and get a Cursor over the new record, and to further modify the record. Here's an example:
为了添加一个新记录到内容提供者,首先在ContentValues对象中配置键值对的映射表,那里每个键匹配内容提供者的列名以及在那个列中新记录的期待值。然后调用ContentResolver.insert()并把提供者的URI和ContentValues映射表传递给它。这个方法返回新记录的完全URI——即,末尾添加新记录ID的提供者URI。然后你可以使用这个URI查询和获取新记录上的Cursor对象。并进一步修改记录。这里有一个示例:
-------------------------------
import android.provider.Contacts.People;
import android.content.ContentResolver;
import android.content.ContentValues;
ContentValues values = new ContentValues();
// Add Abraham Lincoln to contacts and make him a favorite.
// 添加Abraham Lincoln到通讯录并把它标记为收藏
values.put(People.NAME, "Abraham Lincoln");
// 1 = the new contact is added to favorites
// 0 = the new contact is not added to favorites
// 1 = 新的通讯录添加到收藏
// 0 = 新的通讯录不添加到收藏
values.put(People.STARRED, 1);
Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
-------------------------------
Adding new values
添加新值
Once a record exists, you can add new information to it or modify existing information. For example, the next step in the example above would be to add contact information — like a phone number or an IM or e-mail address — to the new entry.
一旦记录存在,你可以添加新信息给它或修改现存信息。例如,上面示例的下一步将添加通讯信息——像电话号码或一个IM或电子邮箱地址——到新的记录。
The best way to add to a record in the Contacts database is to append the name of the table where the new data goes to the URI for the record, then use the amended URI to add the new data values. Each Contacts table exposes a name for this purpose as a CONTENT_DIRECTORY constant. The following code continues the previous example by adding a phone number and e-mail address for the record just created:
添加记录进Contacts数据库的最佳方式是把新数据要放进的表的名称尾加到记录的URI,然后使用修改后的URI去添加新的数据值。每个Contacts表为这个目的暴露一个名称作为CONTENT_DIRECTORY常量。下面的代码继续前面的示例,为刚创建的记录添加一个电话号码和电子邮箱地址。
-------------------------------
Uri phoneUri = null;
Uri emailUri = null;
// Add a phone number for Abraham Lincoln. Begin with the URI for
// the new record just returned by insert(); it ends with the _ID
// of the new record, so we don't have to add the ID ourselves.
// Then append the designation for the phone table to this URI,
// and use the resulting URI to insert the phone number.
// 添加Abraham Lincoln的电话号码。开始部分是刚才由insert()返回的新
// 记录的URI;它的结束部分是新记录的_ID,所以我们不必自己添加ID。
// 然后在这个URI后面添加电话表的称呼,并使用
// 结果URI插入电话号码
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
values.clear();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);
// Now add an email address in the same way.
// 现在以相同方式添加一个电子邮箱地址
emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
values.clear();
// ContactMethods.KIND is used to distinguish different kinds of
// contact methods, such as email, IM, etc.
// ContactMethods.KIND用于区分不同种类的
// 通讯方法,诸如电子邮件,IM(注:即时通讯的缩写),等等
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "[email protected]");
values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
-------------------------------
You can place small amounts of binary data into a table by calling the version of ContentValues.put() that takes a byte array. That would work for a small icon-like image or a short audio clip, for example. However, if you have a large amount of binary data to add, such as a photograph or a complete song, put a content: URI for the data in the table and call ContentResolver.openOutputStream() with the file's URI. (That causes the content provider to store the data in a file and record the file path in a hidden field of the record.)
你可以通过调用传递字节数组的ContentValues.put()版本放置少量二进制数据进表中。例如,这应该对于一个像图标的图片或短音频剪辑来说是可行的。然而,如果你有大量二进制数据要添加,诸如一张照片或一首完整的歌曲,那么请在表中放置一个content: URI代表数据,并用文件的URI调用ContentResolver.openOutputStream()。(那导致内容提供者存储数据在文件中并把文件路径记录在记录的一个隐藏域中。)
In this regard, the MediaStore content provider, the main provider that dispenses image, audio, and video data, employs a special convention: The same URI that is used with query() or managedQuery() to get meta-information about the binary data (such as, the caption of a photograph or the date it was taken) is used with openInputStream() to get the data itself. Similarly, the same URI that is used with insert() to put meta-information into a MediaStore record is used with openOutputStream() to place the binary data there. The following code snippet illustrates this convention:
就这点来说,MediaStore内容提供者,分配照片、音频、和视频数据的主提供者,使用一个特殊的约定:query()或managedQuery()获取关于二进制数据的元信息(诸如,相片标题或它携带的数据),与用openInputStream()获取数据自身,使用的URI是相同的。类似地,用insert()放置元信息进MediaStore记录,和使用openOutputStream()放置那里的二进制数据,使用的URI也是相同的。下面的代码片段描述这个约定:
-------------------------------
import android.provider.MediaStore.Images.Media;
import android.content.ContentValues;
import java.io.OutputStream;
// Save the name and description of an image in a ContentValues map.
// 保存ContentValues映射表中图片的名称和描述。
ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");
// Add a new record without the bitmap, but with the values just set.
// insert() returns the URI of the new record.
// 添加不带位图,但使用刚才设置的值的新记录。
// insert()返回新记录的URI。
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
// Now get a handle to the file for that record, and save the data into it.
// Here, sourceBitmap is a Bitmap object representing the file to save to the database.
// 现在获取那个记录的文件句柄,并保存数据进它。
// 这里sourceBitmap是一个Bitmap对象,代表要保存到数据库中的文件
try {
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);
}
-------------------------------
Batch updating records
批量更新记录
To batch update a group of records (for example, to change "NY" to "New York" in all fields), call the ContentResolver.update() method with the columns and values to change.
为了批量更新一组记录(例如,为了在所有域中修改"NY"为"New York"),用要修改的列和值调用ContentResolver.update()方法。
Deleting a record
删除一条记录
To delete a single record, call {ContentResolver.delete() with the URI of a specific row.
为了删除单一记录,用指定行的URI调用ContentResolver.delete()。
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 an SQL WHERE clause defining which rows to delete. (Caution: Be sure to include a valid WHERE clause if you're deleting a general type, or you risk deleting more records than you intended!).
为了删除多行,用要删除记录类型的URI调用ContentResolver.delete()(例如,android.provider.Contacts.People.CONTENT_URI)以及一个定义要删除行的SQL WHERE子句。(警告:如果你正在删除一个普通的类型,或者你冒险删除超出你的打算的记录,请确保包含一个合法的WHERE子句!)。
-------------------------------
Creating a Content Provider
创建一个内容提供者
To create a content provider, you must:
为了创建一个内容提供者,你必须:
* Set up a system for storing the data. Most content providers store their data using Android's file storage methods or SQLite databases, but you can store your data any way you want. Android provides the SQLiteOpenHelper class to help you create a database and SQLiteDatabase to manage it.
* 为存储数据配置一个系统。大多数内容提供者使用Android文件存储方法或SQLite数据库存储它们的数据,但你可以用你希望的任意方式存储你的数据。Android提供SQLiteOpenHelper类帮助你创建一个数据库,以及用SQLiteDatabase管理它。
* Extend the ContentProvider class to provide access to the data.
* 扩展ContentProvider以提供对数据的访问。
* Declare the content provider in the manifest file for your application (AndroidManifest.xml).
* 在清单中(AndroidManifest.xml)为你的应用程序定义内容提供者。
The following sections have notes on the last two of these tasks.
以下章节有关于最后两个任务的注意事项。
Extending the ContentProvider class
扩展ContentProvider类
You define a ContentProvider subclass to expose your data to others using the conventions expected by ContentResolver and Cursor objects. Principally, this means implementing six abstract methods declared in the ContentProvider class:
你定义一个ContentProvider子类去暴露你的数据给其它正在使用ContentResolver和Cursor对象预期便利的组件。主要地,这意味着要实现定义在ContentProvider类内的6个抽象方法:
query()
insert()
update()
delete()
getType()
onCreate()
The query() method must return a Cursor object that can iterate over the requested data. Cursor itself is an interface, but Android provides some ready-made Cursor objects that you can use. For example, SQLiteCursor can iterate over data stored in an SQLite database. You get the Cursor object by calling any of the SQLiteDatabase class's query() methods. There are other Cursor implementations — such as MatrixCursor — for data not stored in a database.
query()方法必须返回一个Cursor对象,它可以迭代请求的数据,Cursor自身是一个接口,但Android提供一些你可以使用的现成Cursor对象。例如,SQLiteCursor可以迭代存储在SQLite数据库上的数据。你通过调用SQLiteDatabase类的任意query()方法获取Cursor对象。还有其它Cursor实现——诸如MatrixCursor——获取不是存储在数据库里的数据。
Because these ContentProvider methods can be called from various ContentResolver objects in different processes and threads, they must be implemented in a thread-safe manner.
因为这些ContentProvider方法可以从不同进程和线程中不同的ContentResolver对象中调用,所以它们必须以线程安全的方式实现。
As a courtesy, you might also want to call ContentResolver.notifyChange() to notify listeners when there are modifications to the data.
作为一种礼貌,你还可能希望调用ContentResolver.notifyChange()在数据被修改时通知监听器。
Beyond defining the subclass itself, there are other steps you should take to simplify the work of clients and make the class more accessible:
除了定义子类自身,你还应该采取其它步骤以简化客户端的工作并使这个类更可访问:
* Define a 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 the content provider (made lowercase). So, for example, the URI for a TransportationProvider class could be defined as follows:
* 定义一个public static final Uri名为CONTENT_URI。它是字符串,表示完全内容:你的内容提供者处理的URI。你必须对这个值定义一个唯一字符串。最佳方案是使用内容提供者的完全标准类名(把它小写化)。所以,例如,一个TransportationProvider类的URI可能定义如下:
-------------------------------
public static final Uri CONTENT_URI =
Uri.parse("content://com.example.codelab.transportationprovider");
-------------------------------
If the provider has subtables, also define CONTENT_URI constants for each of the subtables. These URIs should all have the same authority (since that identifies the content provider), and be distinguished only by their paths. For example:
如果提供者有子表,还为字表定义CONTENT_URI常量。这些URI应该都拥有相同的权限(因为它标识内容提供者),并且只用它们的路径区分。例如:
content://com.example.codelab.transportationprovider/train
content://com.example.codelab.transportationprovider/air/domestic
content://com.example.codelab.transportationprovider/air/international
For an overview of content: URIs, see the Content URI Summary at the end of this document.
想获取content: URI的概览,请参见本文末尾的内容URI概要。
* Define the column names that the content provider will return to clients. If you are using an underlying database, these column names are typically identical to the SQL database column names they represent. Also define public static String constants that clients can use to specify the columns in queries and other instructions.
* 定义内容提供者将返回给客户端的列名。如果你正在使用一个底层数据库,这些列名通常和它们代表的SQL数据库的列名相同。还要定义客户端可以用来指定查询的列和其它指令的public static String常量。
Be sure to include an integer column named "_id" (with the constant _ID) for the IDs of the records. You should have this field whether or not you have another field (such as a URL) that is also unique among all records. If you're using the SQLite database, the _ID field should be the following type:
确保包含一个名为"_id"的整型列(带有常量_ID)以代表记录的ID。你应该拥有这个域,不管你是否拥有另一个也是在所有记录中唯一的域(诸如一个URL)。如果你正在使用SQLite数据库,那么_ID域应该是如下类型:
INTEGER PRIMARY KEY AUTOINCREMENT
The AUTOINCREMENT descriptor is optional. But without it, SQLite increments an ID counter field to the next number above the largest existing number in the column. If you delete the last row, the next row added will have the same ID as the deleted row. AUTOINCREMENT avoids this by having SQLite increment to the next largest value whether deleted or not.
AUTOINCREMENT描述符是可选的。但没有它的话,SQLite增加ID计数器域到下一个数字,大于该列的现存最大值。如果你删除最后的行,下一个添加的行将拥有与删除行相同的ID。AUTOINCREMENT能避免这种情况,它通过让SQLite增加到下一个最大值,不管它是否被删除。
* Carefully document the data type of each column. Clients need this information to read the data.
* 请小心地用文档注释每个列的数据类型。客户端需要这些信息来读取数据。
* If you are handling a new data type, you must define a new MIME type to return in your implementation of ContentProvider.getType(). The type depends in part on whether or not the content: URI submitted to getType() limits the request to a specific record. There's one form of the MIME type for a single record and another for multiple records. Use the Uri methods to help determine what is being requested. Here is the general format for each type:
* 如果你正在处理一个新数据类型,那么你必须在你的ContentProvider.getType()实现中定义一个要返回的新MIME(注:多用途互联网邮件扩展的缩写)类型。类型部分依赖于提交给getType()的content: URI是否限制只请求一个特定的记录。有一种MIME类型的形式用于单一记录,而另一种形式用于多记录。使用Uri的方法有助于决定正在被请求的东西。这里是每种类型的通用格式:
* For a single record: vnd.android.cursor.item/vnd.yourcompanyname.contenttype
* 对于单条记录:vnd.android.cursor.item/vnd.yourcompanyname.contenttype
For example, a request for train record 122, like this URI,
例如,一个获取列车记录122的请求,像这个URI,
content://com.example.transportationprovider/trains/122
might return this MIME type:
可能返回这种MIME类型
vnd.android.cursor.item/vnd.example.rail
* For multiple records: vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
* 对于多条记录:vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
For example, a request for all train records, like the following URI,
例如,一个获取所有列车记录的请求,像如下URI,
content://com.example.transportationprovider/trains
might return this MIME type:
可能返回这个MIME类型:
vnd.android.cursor.dir/vnd.example.rail
* 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.
* 如果你正在暴露字节数据,而它自己太大不能放进表——诸如一个大的位图文件——那么暴露给客户端的域实际上应该包含content: URI字符串。这是让客户端访问数据文件的域。记录还应该有另一个域,命名为"_data",列举那个文件在设备上准确的文件路径。这个域将不打算被客户端读取,而是被ContentResolver读取。客户端将在面向用户的,持有条目URI的域调用ContentResolver.openInputStream()。ContentResolver将请求"_data"域获取那个记录,而且因为它拥有比客户端较高的权限,所以它应该有能力直接访问文件并返回文件的读封装器给客户端。
For an example of a private content provider implementation, see the NodePadProvider class in the Notepad sample application that ships with the SDK.
想获取一个私有内容提供者实现的示例,请参见SDK携带的Notepad示例应用程序的NodePadProvider类。
Declaring the content provider
声明内容提供者
To let the Android system know about the content provider you've developed, declare it with a <provider> element in the application's AndroidManifest.xml file. Content providers that are not declared in the manifest are not visible to the Android system
为了让Android系统了解你开发的内容提供者,请在应用程序的AndroidManifest.xml文件中用一个<provider>元素声明它。没有在清单中声明的内容提供者对于Android系统是不可用的。
The name attribute is the fully qualified name of the ContentProvider subclass. The authorities attribute is the authority part of the content: URI that identifies the provider. For example if the ContentProvider subclass is AutoInfoProvider, the <provider> element might look like this:
name属性是ContentProvider子类的完全标准名称。authorities属性是标识提供者的content: URI的权限部分。例如如果ContentProvider的子类是AutoInfoProvider,那么<provider>元素可能看起来像这样:
-------------------------------
<provider android:name="com.example.autos.AutoInfoProvider"
android:authorities="com.example.autos.autoinfoprovider"
. . . />
</provider>
-------------------------------
Note that the authorities attribute omits the path part of a content: URI. For example, if AutoInfoProvider controlled subtables for different types of autos or different manufacturers,
注意authorities属性忽略content: URI的路径部分。例如,如果AutoInfoProvider控制每种类型的汽车或生产商的子表。
content://com.example.autos.autoinfoprovider/honda
content://com.example.autos.autoinfoprovider/gm/compact
content://com.example.autos.autoinfoprovider/gm/suv
those paths would not be declared in the manifest. The authority is what identifies the provider, not the path; your provider can interpret the path part of the URI in any way you choose.
那些路径将不会在清单中声明。权限标识提供者,而非标识路径;你的提供者可以用你选择的任意方式解析URI的路径部分。
Other <provider> attributes can set permissions to read and write data, provide for an icon and text that can be displayed to users, enable and disable the provider, and so on. Set the multiprocess attribute to "true" if data does not need to be synchronized between multiple running versions of the content provider. This permits an instance of the provider to be created in each client process, eliminating the need to perform IPC.
其它<provider>属性可以设置读写数据的权限,提供显示给用户的图标和文本,使能和屏蔽提供者,等等。如果数据不需要在多个运行版本的内容提供者之间同步,请设置multiprocess属性为true。这允许一个提供者实例创建在每个客户端进程,消除执行IPC的需要。
-------------------------------
Content URI Summary
内容URI概要
Here is a recap of the important parts of a content URI:
这里是一个内容URI的重要部分的概括:
(图略:
A: content://
B: com.example.transportationprovider/
C: trains/
D: 122
)
A. Standard prefix indicating that the data is controlled by a content provider. It's never modified.
A. 标准前缀表示数据被内容提供者控制。它从不被修改。
B. The authority part of the URI; it identifies the content provider. For third-party applications, this should be a fully-qualified class name (reduced to lowercase) to ensure uniqueness. The authority is declared in the <provider> element's authorities attribute:
B. URI的权限部分;它标识内容提供者。对于第三方的应用程序,它应该是一个完全标准的类名(简化为小写)以确保唯一性。权限被定义在<provider>元素的authorities属性中:
-------------------------------
<provider android:name=".TransportationProvider"
android:authorities="com.example.transportationprovider"
. . . >
-------------------------------
C. The path that the content provider uses to determine what kind of data is being requested. This can be zero or more segments long. If the content provider exposes only one type of data (only trains, for example), it can be absent. If the provider exposes several types, including subtypes, it can be several segments long — for example, "land/bus", "land/train", "sea/ship", and "sea/submarine" to give four possibilities.
C. 内容提供者的路径用于决定请求数据是何种数据。它可能为零或更长的段。如果内容提供者暴露只有一种类型的数据(例如,只有列车),它可以缺省。如果提供者暴露几个类型,包括子类型,那么它可以有几个段长——例如"land/bus","land/train","sea/ship",和"sea/submarine"以给出四种可能。
D. The ID of the specific record being requested, if any. This is the _ID value of the requested record. If the request is not limited to a single record, this segment and the trailing slash are omitted:
D. 正在请求的特定记录的ID,如果有。它是请求记录的_ID值。如果请求不限于单一记录,那么这个段和结尾的斜杠被忽略:
content://com.example.transportationprovider/trains
Except as noted, this content is licensed under Apache 2.0. For details and restrictions, see the Content License.
除特别说明外,本文在Apache 2.0下许可。细节和限制请参考内容许可证。
Android 4.0 r1 - 04 Nov 2011 0:15
Android 4.0 r1 - 21 Dec 2011 3:15
Android 4.0 r1 - 27 Jan 2012 1:49
-------------------------------
patch:
1. 2011-12-28
++
See also
Calendar Provider
++
Note: Android 4.0 introduces the Calendar Provider. For more information, see Calendar Provider.
2. 2012-01-30
过时,
分割成两章
Content Provider Basics
Creating a Content Provider
原来的标题保留,但内容已经不同了
-------------------------------
Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
(此页部分内容基于Android开源项目,以及使用根据创作公共2.5来源许可证描述的条款进行修改)