Android Content Provider Tutorial--安卓内容提供者系列2--内容提供者用法

上一篇链接:Android Content Provider Tutorial--安卓内容提供者系列1--内容提供者介绍

Using a Content Provider(如何使用Content Provider)

Objectives(学习目标)

After this section, you will be able to:

  • Write client code that can read and modify the data managed by a content provider
  • Find and use contract classes documenting the constants exposed by system content providers
  • Use batch access to perform more efficient interaction with a content provider

通过这一部分的学习,你将能够:

  • 编写可以读取和修改由content provider管理的数据的客户端代码
  • 找到并使用那些记录了content providers暴露出来的常量的合约类
  • 使用分批访问的方式使得与content provider的交互更有效率

Content Provider Overview(Content Provider概述)

A content provider is an application component that shares data with other applications.

  • Various system content providers manage the user’s contacts, call log, calendar, and other collections of information.
  • User-installed applications can expose their own custom data collections.

content provider是一种与其他应用程序共享数据的应用组件。

  • 各种系统的content providers管理着用户的联系人信息、通话记录、日历、以及其他信息集。
  • 用户自己装的app可以把自定义的数据集合暴露出去。

Typically, a content provider presents data as one or more tables, similar to tables in a database.

  • Each row represents one record, such as a single calendar event.
  • Each column represents a particular attribute of the records, such as an event start time.

通常情况下,一个content provider提供的数据就像数据库中的一个或多个表一样。

  • 每一行代表一个记录,例如一个日程表事件。
  • 每一列代表该条记录的一个特定的属性,例如事件的开始时间。

Occasionally, a content provider might expose file data.

  • For example, the system contacts content provider can share a contact’s photo.

一个content provider偶尔也会暴露文件数据。

  • 例如,系统联系人的content provider可以共享一个联系人的照片。

Accessing a Content Provider(访问Content Provider

A client application accesses the data from a content provider with a ContentResolver object.

  • The ContentResolver object provides query(), insert(), update(), and delete() methods for accessing data from a content provider.
  • The ContentResolver object invokes identically-named methods on an instance of a concrete subclass of ContentProvider, which typically resides in a separate application process.
  • The ContentProvider acts as an abstraction layer between its data store and the external presentation of data.
  • The ContentResolver object and the ContentProvider object automatically handle the details of inter-process communication.

一个客户端app想访问Content provider 提供的数据,需要用到Content Resolver对象。

  • ContentResolver对象提供了增删改查的方法来操作content provider中的数据。
  • ContentResolver对象调用一个通常存在于另一个单独app进程中的一个ContentProvider的具体子类对象的同名方法
  • ContentProvider充当了介于数据存储和数据外部表现之间的一个抽象层。
  • ContentResolver对象和ContentProvider对象自动处理进程间通信的细节。
In most cases, the ContentProvider does not reside in the same application process as the client’s ContentResolver. However, if you implement a content provider for your application, other components in your application can access it through a ContentResolver in exactly the same way as they would a content provider in a different application.

在大多数情况下,(提供数据的app的)ContentProvider和(操作数据的)客户端app的ContentResolver不在同一个进程中。但是,如果您为您的app实现了一个ContentProvider,你的应用内的其他组件也可以通过ContentResolver来访问它,这跟访问其他应用内的ContentProvider的方式完全一样。

For example, to get a list of the words and their locales from the User Dictionary Provider, you call ContentResolver.query():

举个栗子,为了从User Dictionary Provider中获取所有words的列表和他们的locals,你需要调用ContentResolver.query():

// Queries the user dictionary and returns results
Cursor cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause                    // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows


Content URIs(内容uri)

content URI is a URI that identifies data in a provider. It consists of:

  • The scheme, which is always content:// for a content URI
  • The authority, which is a unique string identifying the specific content provider
  • The path, which identifies a particular record or collection of records managed by the provider
  • In the preceding example, the full URI for the "words" table is:

内容uri是用来标识content provider中数据的uri。它包括:

  • scheme,格式总是这样的: content://
  • authority,这是一个标识特定的ContentProvider的唯一的字符串
  • path,它标识了一个由ContentProvider管理的特定的记录或记录的集合
  • 在前面的例子中,“words”表对应的完整URI是:
content://user_dictionary/words
  • content:// — the scheme identifying this as a content URI
  • user_dictionary — the authority of the system user dictionary provider
  • words — the path corresponding to the “words” table

The ContentResolver uses the authority to identify the content provider to contact.

  • An application implementing a ContentProvider specifies the provider’s authority in the application manifest.

ContentResolver使用授权去识别想要联系的ContentProvider。

  • 一个实现了ContentProvider的应用程序要在它的清单文件中指定该ContentProvider的权限。

The ContentProvider uses the path to choose the table to access.

  • A provider usually has a path for each table it exposes.
  • Many providers allow you to access a single row in a table by appending an ID value to the end of the URI.
  • For example, to retrieve a row whose _ID is 4 from user dictionary, you can use this content URI:

ContentProvider使用路径去选择要访问的表。

  • ContentProvider对于它暴露的每一张表都有一个访问路径。
  • 许多ContentProvider都允许你通过在uri后面附加一个ID值的方式去访问表中的某一行。
  • 举个栗子,在user dictionary中你想检索出_ID = 4的那一行,你可以使用下面这个内容URI:

  • Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
The Uri and Uri.Builder classes contain convenience methods for constructing well-formed Uri objects from strings. The ContentUris class contains convenience methods for appending id values to a URI. The previous snippet uses withAppendedId() to append an id to the UserDictionary content URI.

Uri类和Uri.Builder类中封装了一些比较方便的方法用于把字符串构造成格式良好的Uri对象。ContentUris类中封装了一些可以将id值附加到URI中的便捷方法。前面的代码片段就是使用了它的withAppendedId()方法来添加一个id到UserDictionary这个内容URI中。

Contract Classes(合约类)

contract class defines constants that help applications work with the content URIs, column names, and other features of a content provider.

  • Contract classes are not included automatically with a provider
  • The provider’s developer has to define them and then make them available to other developers.

合约类定义了一些常量,这些常量使应用程序与内容uri,列名还有content provider的一些其他特性能更好的交互。

  • 合约类不是自动包含在content provider中的。
  • content provider的开发人员必须定义他们,然后让其他开发人员也能访问到。

Many of the providers included with the Android platform have corresponding contract classes in the package android.provider.

  • For example, the User Dictionary Provider has a contract class UserDictionary containing content URI and column name constants.
  • The content URI for the "words" table is defined in the constant UserDictionary.Words.CONTENT_URI. The UserDictionary.Words class also contains column name constants.

Android平台中的许多ContentProvider都有相应的合约类,这些类就在android.provider包里。

  • 例如,User Dictionary Provider有一个合同类叫UserDictionary,它包含内容URI常量和列名常量。
  • “words”表的内容URI就定义在UserDictionary.Words.CONTENT_URI这个常量中。UserDictionary.Words类也包含了列名常量。

Requesting Access Permission(请求访问权限)

Many content providers require clients to hold a custom access permission to access data from the provider.

  • Your client application’s manifest must include a <uses-permission> element with the permission name defined by the provider.
  • The provider may define separate permissions for "read access" (queries) and "write access" (inserts, updates, and deletes).
  • For example, the User Dictionary Provider defines the permission android.permission.READ_USER_DICTIONARY for applications that want to retrieve data, and a separate android.permission.WRITE_USER_DICTIONARY permission for inserting, updating, or deleting data.

许多content provider要求客户端持有一个自定义访问权限来访问它的数据。

  • 你的客户端app的清单文件中必须包含一个< uses-permission >元素,其中包含了由Content provider定义的权限名。
  • content provider可能定义单独的“读取”权限(查询)和“写入”(插入、更新和删除)。
  • 例如,User Dictionary Provider给那些想检索数据的应用程序定义了android.permission.READ_USER_DICTIONARY权限对于想插入、更新或删除数据的,又单独定义了一个android.permission。WRITE_USER_DICTIONARY权限。

Constructing a Query(构建查询语句)

The ContentResolver.query() method requires several arguments:

ContentResolver.query()方法需要以下参数:

uri

The URI, using the content:// scheme, for the content to retrieve.

URI,格式如 content:// scheme,是为了标识要检索的内容。


projection

A list of which columns to return. Passing null returns all columns, which can be inefficient.

该返回的数据列的集合。如果传递空值则返回所有列,当然这样的话效率会比较低下。


selection

A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null returns all rows for the given URI.

这部分相当于一个过滤器,它声明了哪些行该被返回,格式类似于一个SQL语言中的WHERE从句(不包括WHERE这个单词本身)。如果传null的话,则返回满足URI条件的所有行。


selectionArgs

You may include ?s in selection, which are replaced by the values from selectionArgs, in the order that they appear in the selection.

你可能会在selection参数中包含一些筛选条件,这些条件会被selectionArgs参数中的值所替代,selectionArgs参数顺序和seletion中的参数顺序一一对应。


sortOrder

How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null uses the default sort order, which may be unordered.

这个参数表示如何给返回的行排序,它的格式类似于一个SQL语言中的ORDER BY从句(不含ORDER BY这个关键词本身)。如果传null的话就使用默认的排序顺序,这可能是无序的。


The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query’s projection for the rows that match the query’s selection criteria.

ContentResolver.query()这个客户端方法总是会返回一个游标,该游标包含了projection参数指定的列,这些列和满足查询条件的行相匹配。


If an internal error occurs, the results of the query depend on the particular provider. It may choose to return null, or it may throw an Exception.

如果发生内部错误,查询的结果将取决于特定的Content Provider。它可能会返回null,也可能会抛出异常。


Some Cursor implementations automatically update the object when the provider’s data changes, or trigger methods in an observer object when the Cursor changes, or both.

一些游标会在ContentProvider的数据发生变化时自动更新,或者在游标变化时会触发观察者对象中的方法,又或者两种情况兼而有之。

A Query Example(一个查询示例)

// A "projection" defines the columns that will be returned for each row
String[] projection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// An array to contain selection arguments
String[] selectionArgs = null;

// Gets a word from the UI
String searchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, get everything. Otherwise...
if (!TextUtils.isEmpty(searchString)) {
    // Construct a selection clause that matches the word that the user entered.
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Use the user's input string as the (only) selection argument.
    selectionArgs = new String[]{ searchString };
}

// An ORDER BY clause, or null to get results in the default sort order
String sortOrder = null;

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause                   // Either null, or the word the user entered
    selectionArgs,                    // Either empty, or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
     // Insert code here to handle the error.
} else if (mCursor.getCount() < 1) {
        // If the Cursor is empty, the provider found no matches
} else {
    // Insert code here to do something with the results
}

This query is analogous to the SQL statement:

上面那个查询方法类似于下面这个SQL查询语句:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

Directly concatenating external untrusted data into raw SQL statements can lead to SQL injectionattacks.For example, in the following selection clause:

直接连接外部不可信的数据,写入到原始SQL语句会有发生SQL 注入的风险。例如下面的selection从句:

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause =  "var = " + userInput;

the user could enter "nothing; DROP TABLE *;" for mUserInput, which would result in the selection clause var = nothing; DROP TABLE *;. Since the selection clause is treated as an SQL statement, this might cause the provider to erase all of the tables in the underlying SQLite database (unless the provider is set up to catch SQL injection attempts).

用户可以输入"nothing; DROP TABLE *;",赋值到mUserInput变量中,这将导致selection从句中的var = nothing; DROP TABLE *;。由于这个selection从句被视为一个SQL语句,这样拼接起来很可能会导致ContentProvider去清除底层SQLite数据库中所有的表(除非ContentProvider设置成能捕获到SQL注入的尝试)。

When incorporating untrusted data — such as user input — into a query, you should always use a selection clause that with ? as a replaceable parameter and a separate array of selection arguments. The selection argument is incorporated into the query as a single argument rather than being directly concatenated into the selection string.

当将不受信任的数据——比如用户的输入,拼接到一个查询语句中时,你应该始终使用一个带有问号作为占位符和单独的参数数组的selection从句。宁愿将selection参数作为单个参数拼接进查询语句中,也不要直接连接到SQL查询字符串中。

Inserting Data(插入数据)

To insert data into a provider, call the ContentResolver.insert() method.

  • This method inserts a new row into the provider and returns a content URI for that row.

This example shows how to insert a new word into the User Dictionary Provider:

你想将数据插入到ContentProvider中,那就调用ContentResolver.insert()方法。

  • 该方法将一个新行插入到ContentProvider中,并返回该行的内容URI。

下面这个例子示范了如何将一个单词插入到User Dictionary Provider中:

// Defines a new Uri object that receives the result of the insertion
Uri newUri;

// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

// Sets the values of each column and inserts the word.
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    newValues                          // the values to insert
);

The content URI returned in newUri identifies the newly-added row, with the following format:

存在newUri变量中的返回的内容URI标明了新添加的行,格式像下面这样:

content://user_dictionary/words/<id_value>
To get the value of _ID from the returned Uri, call ContentUris.parseId().
想从返回的Uri中得到_ID字段的值,那就要调用ContentUris.parseId()方法。

Updating Data(修改数据)

To update one or more rows, use a ContentValues object with the updated value and selection criteria.

  • Invoke ContentResolver.update() to perform the update.
  • You need to add values to the ContentValues object for only the columns you’re updating.
  • If you want to clear the contents of a column, set the value to null.

想更新一行或多行数据,需要将ContentValues对象与更新后的值还有selection条件一起使用。

  • 调用ContentResolver.update()来执行更新。
  • 对于你要更新的那些列,你需要添加值到ContentValues对象中。
  • 如果你想清空的一个列的内容,请将值设为null。

For example, the following snippet changes all the rows whose locale has the language "en" to a have a locale of null:

举个栗子,下面的代码片段把所有locale的值是“en”的行都改成了locale为null:

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;

// Sets the updated value and updates the selected words.
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // the user dictionary content URI
    updateValues                       // the columns to update
    selectionClause                    // the column to select on
    selectionArgs                      // the value to compare to
);

Deleting Data(删除数据)

Deleting rows is similar to retrieving row data.

  • Invoke ContentResolver.delete() to perform the update.
  • Specify selection criteria for the rows you want to delete.
  • The method returns the number of rows deleted.
  • For example, the following snippet deletes rows whose appid matches "user".

删除行数据类似于检索行数据。

  • 调用ContentResolver.delete()来执行删除操作。
  • 为你想删除行指明的筛选条件。
  • 该方法返回的是被删除的行的数目。
  • 举个栗子,下面的代码片段演示了删除所有appid是“user”的行。
// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;

// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    selectionClause                    // the column to select on
    selectionArgs                      // the value to compare to
);

Batch Access(批量访问)

Batch access allows you to perform multiple operations with a content provider in a single ContentResolver call.

  • The request is much more efficient, as it requires only one IPC call.
  • Depending on the specific content provider implementation, a content provider might implement a batch access as a single atomic transaction.

批量访问的意思是你可以在调用一个ContentResolver的方法中对一个ContentProvider执行多个操作。

  • 这样请求起来更有效率,因为它只需要一个IPC调用。
  • 根据content provider的具体实现,content provider可能会执行一些批量访问,就像一个单个的原子事务一样。

To access a content provider in batch mode:

  1. Create an ArrayList of ContentProviderOperation objects.
  2. Invoke ContentResolver.applyBatch() to send the operations to the specified content provider, supplying the provider’s authority string and the ContentProviderOperation array as arguments.
  3. The return value is an array of ContentProviderResult objects, each one representing the result of the corresponding ContentProviderOperation request.

以批处理模式访问content provider:

  1. 创建一个装ContentProviderOperation对象的ArrayList 
  2. 调用ContentResolver.applyBatch()方法来给指定的ContentProvider发送操作指令,要传递的参数是ContentProvider的授权字符串和ContentProviderOperation集合。
  3. 返回值是一个装有ContentProviderResult对象的数组,数组中的每个元素都代表着对应的ContentProviderOperation请求的结果。

Batch Access, the ContentProviderOperation and ContentProviderResult Classes(这两个类的介绍)

The ContentProviderOperation class has a set of static methods that return builders for each type of operation.

  • To create a ContentProviderOperation object:
    1. Obtain an appropriate builder.
    2. Use the builder methods to configure the parameters of the operation.
    3. Invoke the build() method to create the final ContentProviderOperation object.

ContentProviderOperation类有一组静态方法,返回值是每种操作对应的builder.

  • 创建一个ContentProviderOperation对象:
    1. 获得一个适当的builder。
    2. 使用builder里的方法来配置操作的参数。
    3. 调用build()方法来创建最终的ContentProviderOperation对象。

The ContentProviderResult object has two public fields, one of which is set depending on the corresponding operation:

ContentProviderResult对象有下面两个公共字段,其中一个的设置取决于相应的操作:

Integer count

The count of rows affects by a delete or update operation

删除或更新操作涉及到的行的数量

Uri uri

The Uri of a newly inserted row

新插入行的Uri

Batch Access, Example(批量访问示例)

This shows an example of batch inserts into the User Dictionary Provider:

这是一个批量插入User Dictionary Provider的示例:

// Declare the operations ArrayList
ArrayList<ContentProviderOperation> batchOps = new ArrayList<ContentProviderOperation>();

// Declare an array of new terms
String[] words = {"foo", "bar", "wibble"};

// Declare a new array will contain the Uris of the new records
Uri newUris[words.length];

// Create a set of insert ContentProviderOperations
for (int index; words.length; index++) {
        batchOps.add(ContentProviderOperation.newInsert(UserDictionary.Word.CONTENT_URI)
                .withValue(UserDictionary.Words.APP_ID, "example.user")
                .withValue(UserDictionary.Words.LOCALE, "en_US")
                .withValue(UserDictionary.Words.WORD, words[index])
                .withValue(UserDictionary.Words.FREQUENCY, "100")
                .build());
}

// Invoke the batch insertion
ContentProviderResult[] opResults
        = getContentResolver().applyBatch(UserDictionary.AUTHORITY, batchOps);

// Extract the Uris of the new records
for (int index; opResults.length; index++) {
        newUris[index] = opResults[index].uri;
}

Topic Summary(主题总结)

You should now be able to:

  • Write client code that can read and modify the data managed by a content provider
  • Find and use contract classes documenting the constants exposed by system content providers
  • Use batch access to perform more efficient interaction with a content provider

现在你应该可以做到以下这些:

  • 编写可以读取和修改content provider管理的数据的客户端代码
  • 找到并使用记录了系统暴露出的常量的合约类
  • 使用批量访问的方式更有效地与ContentProvider交互


下一篇链接:Android Content Provider Tutorial--安卓内容提供者系列3--操作安卓联系人

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值