• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=内容提供程序基础知识
2@jd:body
3<div id="qv-wrapper">
4<div id="qv">
5<!-- In this document -->
6<h2>本文内容</h2>
7<ol>
8    <li>
9        <a href="#Basics">概览</a>
10        <ol>
11            <li>
12                <a href="#ClientProvider">访问提供程序</a>
13            </li>
14            <li>
15                <a href="#ContentURIs">内容 URI</a>
16            </li>
17        </ol>
18    </li>
19    <li>
20        <a href="#SimpleQuery">从提供程序检索数据</a>
21        <ol>
22            <li>
23                <a href="#RequestPermissions">请求读取访问权限</a>
24            </li>
25            <li>
26                <a href="#Query">构建查询</a>
27            </li>
28            <li>
29                <a href="#DisplayResults">显示查询结果</a>
30            </li>
31            <li>
32                <a href="#GettingResults">从查询结果中获取数据</a>
33            </li>
34        </ol>
35    </li>
36    <li>
37        <a href="#Permissions">内容提供程序权限</a>
38    </li>
39    <li>
40        <a href="#Modifications">插入、更新和删除数据</a>
41        <ol>
42            <li>
43                <a href="#Inserting">插入数据</a>
44            </li>
45            <li>
46                <a href="#Updating">更新数据</a>
47            </li>
48            <li>
49                <a href="#Deleting">删除数据</a>
50            </li>
51        </ol>
52    </li>
53    <li>
54        <a href="#DataTypes">提供程序数据类型</a>
55    </li>
56    <li>
57        <a href="#AltForms">提供程序访问的替代形式</a>
58        <ol>
59            <li>
60                <a href="#Batch">批量访问</a>
61            </li>
62            <li>
63                <a href="#Intents">通过 Intent 访问数据</a>
64            </li>
65        </ol>
66    </li>
67    <li>
68        <a href="#ContractClasses">协定类</a>
69    </li>
70    <li>
71        <a href="#MIMETypeReference">MIME 类型引用</a>
72    </li>
73</ol>
74
75    <!-- Key Classes -->
76<h2>关键类</h2>
77    <ol>
78        <li>
79            {@link android.content.ContentProvider}
80        </li>
81        <li>
82            {@link android.content.ContentResolver}
83        </li>
84        <li>
85            {@link android.database.Cursor}
86        </li>
87        <li>
88            {@link android.net.Uri}
89        </li>
90    </ol>
91
92    <!-- Related Samples -->
93<h2>相关示例</h2>
94    <ol>
95        <li>
96        <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html">
97游标(联系人)</a>
98        </li>
99        <li>
100        <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html">
101游标(电话)</a>
102        </li>
103    </ol>
104
105    <!-- See also -->
106<h2>另请参阅</h2>
107    <ol>
108        <li>
109            <a href="{@docRoot}guide/topics/providers/content-provider-creating.html">
110创建内容提供程序</a>
111        </li>
112        <li>
113            <a href="{@docRoot}guide/topics/providers/calendar-provider.html">
114日历提供程序</a>
115        </li>
116    </ol>
117</div>
118</div>
119
120    <!-- Intro paragraphs -->
121<p>
122    内容提供程序管理对中央数据存储库的访问。提供程序是 Android 应用的一部分,通常提供自己的 UI 来使用数据。
123
124但是,内容提供程序主要旨在供其他应用使用,这些应用使用提供程序客户端对象来访问提供程序。
125提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理跨进程通信并保护数据访问的安全性。
126
127
128</p>
129<p>
130    本主题介绍了以下基础知识:
131</p>
132    <ul>
133        <li>内容提供程序的工作方式。</li>
134        <li>用于从内容提供程序检索数据的 API。</li>
135        <li>用于在内容提供程序中插入、更新或删除数据的 API。</li>
136        <li>其他有助于使用提供程序的 API 功能。</li>
137    </ul>
138
139    <!-- Basics -->
140<h2 id="Basics">概览</h2>
141<p>
142    内容提供程序以一个或多个表(与在关系数据库中找到的表类似)的形式将数据呈现给外部应用。
143行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。
144
145
146</p>
147<p>
148    例如,Android 平台的内置提供程序之一是用户字典,它会存储用户想要保存的非标准字词的拼写。
149表 1 描述了数据在此提供程序表中的显示情况:
150
151</p>
152<p class="table-caption">
153    <strong>表 1:</strong>用户字典示例表格。
154</p>
155<table id="table1" style="width: 50%;">
156    <tr>
157        <th style="width:20%" align="center" scope="col">字词</th>
158        <th style="width:20%" align="center" scope="col">应用 id</th>
159        <th style="width:20%" align="center" scope="col">频率</th>
160        <th style="width:20%" align="center" scope="col">区域设置</th>
161        <th style="width:20%" align="center" scope="col">_ID</th>
162    </tr>
163    <tr>
164        <td align="center" scope="row">mapreduce</td>
165        <td align="center">user1</td>
166        <td align="center">100</td>
167        <td align="center">en_US</td>
168        <td align="center">1</td>
169    </tr>
170    <tr>
171        <td align="center" scope="row">precompiler</td>
172        <td align="center">user14</td>
173        <td align="center">200</td>
174        <td align="center">fr_FR</td>
175        <td align="center">2</td>
176    </tr>
177    <tr>
178        <td align="center" scope="row">applet</td>
179        <td align="center">user2</td>
180        <td align="center">225</td>
181        <td align="center">fr_CA</td>
182        <td align="center">3</td>
183    </tr>
184    <tr>
185        <td align="center" scope="row">const</td>
186        <td align="center">user1</td>
187        <td align="center">255</td>
188        <td align="center">pt_BR</td>
189        <td align="center">4</td>
190    </tr>
191    <tr>
192        <td align="center" scope="row">int</td>
193        <td align="center">user5</td>
194        <td align="center">100</td>
195        <td align="center">en_UK</td>
196        <td align="center">5</td>
197    </tr>
198</table>
199<p>
200    在表 1 中,每行表示可能无法在标准词典中找到的字词实例。
201每列表示该字词的某些数据,如该字词首次出现时的区域设置。
202列标题是存储在提供程序中的列名称。
203要引用行的区域设置,需要引用其 <code>locale</code> 列。对于此提供程序,<code>_ID</code> 列充当由提供程序自动维护的“主键”列。
204
205
206</p>
207<p class="note">
208    <strong>注</strong>:提供程序无需具有主键,也无需将 <code>_ID</code> 用作其主键的列名称(如果存在主键)。
209但是,如果您要将来自提供程序的数据与 {@link android.widget.ListView} 绑定,则其中一个列名称必须是 <code>_ID</code>。
210
211<a href="#DisplayResults">显示查询结果</a>部分详细说明了此要求。
212
213</p>
214<h3 id="ClientProvider">访问提供程序</h3>
215<p>
216    应用从具有 {@link android.content.ContentResolver} 客户端对象的内容提供程序访问数据。
217此对象具有调用提供程序对象({@link android.content.ContentProvider} 的某个具体子类的实例)中同名方法的方法。
218
219
220{@link android.content.ContentResolver} 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。
221
222</p>
223<p>
224    客户端应用进程中的 {@link android.content.ContentResolver} 对象和拥有提供程序的应用中的 {@link android.content.ContentProvider} 对象可自动处理跨进程通信。
225{@link android.content.ContentProvider} 还可充当其数据存储库和表格形式的数据外部显示之间的抽象层。
226
227
228
229</p>
230<p class="note">
231    <strong>注</strong>:要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。
232<a href="#Permissions">内容提供程序权限</a>部分详细介绍了此内容。
233
234</p>
235<p>
236    例如,要从用户字典提供程序中获取字词及其区域设置的列表,则需调用 {@link android.content.ContentResolver#query ContentResolver.query()}。
237
238    {@link android.content.ContentResolver#query query()} 方法会调用用户字典提供程序所定义的
239{@link android.content.ContentProvider#query ContentProvider.query()} 方法。
240以下代码行显示了
241{@link android.content.ContentResolver#query ContentResolver.query()} 调用:
242<p>
243<pre>
244// Queries the user dictionary and returns results
245mCursor = getContentResolver().query(
246    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
247    mProjection,                        // The columns to return for each row
248    mSelectionClause                    // Selection criteria
249    mSelectionArgs,                     // Selection criteria
250    mSortOrder);                        // The sort order for the returned rows
251</pre>
252<p>
253    表 2 显示了
254    {@link android.content.ContentResolver#query
255    query(Uri,projection,selection,selectionArgs,sortOrder)} 的参数如何匹配 SQL SELECT 语句:
256</p>
257<p class="table-caption">
258    <strong>表 2:</strong>Query() 与 SQL 查询对比。
259</p>
260<table id="table2" style="width: 75%;">
261    <tr>
262        <th style="width:25%" align="center" scope="col">query() 参数</th>
263        <th style="width:25%" align="center" scope="col">SELECT 关键字/参数</th>
264        <th style="width:50%" align="center" scope="col">备注</th>
265    </tr>
266    <tr>
267        <td align="center"><code>Uri</code></td>
268        <td align="center"><code>FROM <em>table_name</em></code></td>
269        <td><code>Uri</code> 映射至名为 <em>table_name</em> 的提供程序中的表。</td>
270    </tr>
271    <tr>
272        <td align="center"><code>projection</code></td>
273        <td align="center"><code><em>col,col,col,...</em></code></td>
274        <td>
275            <code>projection</code> 是应该为检索到的每个行包含的列的数组。
276
277        </td>
278    </tr>
279    <tr>
280        <td align="center"><code>selection</code></td>
281        <td align="center"><code>WHERE <em>col</em> = <em>value</em></code></td>
282        <td><code>selection</code> 会指定选择行的条件。</td>
283    </tr>
284    <tr>
285        <td align="center"><code>selectionArgs</code></td>
286        <td align="center">
287            (没有完全等效项。选择参数会替换选择子句中 <code>?</code> 的占位符。)
288
289        </td>
290    </tr>
291    <tr>
292        <td align="center"><code>sortOrder</code></td>
293        <td align="center"><code>ORDER BY <em>col,col,...</em></code></td>
294        <td>
295            <code>sortOrder</code> 指定行在返回的
296 {@link android.database.Cursor} 中的显示顺序。
297        </td>
298    </tr>
299</table>
300<h3 id="ContentURIs">内容 URI</h3>
301<p>
302    <strong>内容 URI</strong> 是用于在提供程序中标识数据的 URI。内容 URI    包括整个提供程序的符号名称(其<strong>权限</strong>)和一个指向表的名称(<strong>路径</strong>)。
303
304当您调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。
305
306
307</p>
308<p>
309    在前面的代码行中,常量
310 {@link android.provider.UserDictionary.Words#CONTENT_URI} 包含用户字典的“字词”表的内容 URI。
311{@link android.content.ContentResolver}
312 对象会分析出 URI 的授权,并通过将该授权与已知提供程序的系统表进行比较,来“解析”提供程序。
313然后,
314{@link android.content.ContentResolver} 可以将查询参数分派给正确的提供程序。
315
316</p>
317<p>
318    {@link android.content.ContentProvider} 使用内容 URI 的路径部分来选择要访问的表。
319提供程序通常会为其公开的每个表显示一条<strong>路径</strong>。
320</p>
321<p>
322    在前面的代码行中,“字词”表的完整 URI 是:
323</p>
324<pre>
325content://user_dictionary/words
326</pre>
327<p>
328    其中,<code>user_dictionary</code> 字符串是提供程序的授权,
329<code>words</code> 字符串是表的路径。字符串
330 <code>content://</code>(<strong>架构</strong>)始终显示,并将此标识为内容 URI。
331
332</p>
333<p>
334    许多提供程序都允许您通过将 ID 值追加到 URI 末尾来访问表中的单个行。例如,要从用户字典中检索 <code>_ID</code> 为
335 <code>4</code> 的行,则可使用此内容 URI:
336
337</p>
338<pre>
339Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
340</pre>
341<p>
342    在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。
343
344</p>
345<p class="note">
346    <strong>注</strong>:{@link android.net.Uri} 和 {@link android.net.Uri.Builder} 类
347包含根据字符串构建格式规范的 URI 对象的便利方法。
348{@link android.content.ContentUris} 包含一些可以将 ID 值轻松追加到
349 URI 后的方法。前面的代码段就是使用 {@link android.content.ContentUris#withAppendedId
350    withAppendedId()} 将 ID 追加到用户字典内容 URI 后。
351</p>
352
353
354    <!-- Retrieving Data from the Provider -->
355<h2 id="SimpleQuery">从提供程序检索数据</h2>
356<p>
357    本节将以用户字典提供程序为例,介绍如何从提供程序中检索数据。
358
359</p>
360<p class="note">
361    为了明确进行说明,本节中的代码段将在“UI 线程”上调用
362 {@link android.content.ContentResolver#query ContentResolver.query()}。但在实际代码中,您应该在单独线程上异步执行查询。
363执行此操作的方式之一是使用 {@link android.content.CursorLoader} 类,<a href="{@docRoot}guide/components/loaders.html">加载器</a>指南中对此有更为详细的介绍。
364
365
366此外,前述代码行只是代码段;它们不会显示整个应用。
367
368</p>
369<p>
370    要从提供程序中检索数据,请按照以下基本步骤执行操作:
371</p>
372<ol>
373   <li>
374        请求对提供程序的读取访问权限。
375   </li>
376   <li>
377        定义将查询发送至提供程序的代码。
378   </li>
379</ol>
380<h3 id="RequestPermissions">请求读取访问权限</h3>
381<p>
382    要从提供程序检索数据,您的应需要具备对提供程序的“读取访问”权限。
383您无法在运行时请求此权限;相反,您需要使用<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">&lt;uses-permission&gt;</a></code>元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。
384
385
386
387在您的清单文件中指定此元素后,您将有效地为应用“请求”此权限。
388用户安装您的应用时,会隐式授予允许此请求。
389
390</p>
391<p>
392    要找出您正在使用的提供程序的读取访问权限的准确名称,以及提供程序使用的其他访问权限的名称,请查看提供程序的文档。
393
394
395</p>
396<p>
397    <a href="#Permissions">内容提供程序权限</a>部分详细介绍了权限在访问提供程序过程中的作用。
398
399</p>
400<p>
401    用户字典提供程序在其清单文件中定义了权限
402 <code>android.permission.READ_USER_DICTIONARY</code>,因此希望从提供程序中进行读取的应用必需请求此权限。
403
404</p>
405<!-- Constructing the query -->
406<h3 id="Query">构建查询</h3>
407<p>
408    从提供程序中检索数据的下一步是构建查询。第一个代码段定义某些用于访问用户字典提供程序的变量:
409
410</p>
411<pre class="prettyprint">
412
413// A "projection" defines the columns that will be returned for each row
414String[] mProjection =
415{
416    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
417    UserDictionary.Words.WORD,   // Contract class constant for the word column name
418    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
419};
420
421// Defines a string to contain the selection clause
422String mSelectionClause = null;
423
424// Initializes an array to contain selection arguments
425String[] mSelectionArgs = {""};
426
427</pre>
428<p>
429    下一个代码段以用户字典提供程序为例,显示了如何使用
430 {@link android.content.ContentResolver#query ContentResolver.query()}。
431提供程序客户端查询与 SQL 查询类似,并且包含一组要返回的列、一组选择条件和排序顺序。
432
433</p>
434<p>
435    查询应该返回的列集被称为<strong>投影</strong>(变量 <code>mProjection</code>)。
436
437</p>
438<p>
439    用于指定要检索的行的表达式分割为选择子句和选择参数。
440选择子句是逻辑和布尔表达式、列名称和值(变量 <code>mSelectionClause</code>)的组合。
441如果您指定了可替换参数 <code>?</code> 而非值,则查询方法会从选择参数数组(变量 <code>mSelectionArgs</code>)中检索值。
442
443
444</p>
445<p>
446    在下一个代码段中,如果用户未输入字词,则选择子句将设置为 <code>null</code>,而且查询会返回提供程序中的所有字词。
447如果用户输入了字词,选择子句将设置为 <code>UserDictionary.Words.WORD + " = ?"</code> 且选择参数数组的第一个元素将设置为用户输入的字词。
448
449
450</p>
451<pre class="prettyprint">
452/*
453 * This defines a one-element String array to contain the selection argument.
454 */
455String[] mSelectionArgs = {""};
456
457// Gets a word from the UI
458mSearchString = mSearchWord.getText().toString();
459
460// Remember to insert code here to check for invalid or malicious input.
461
462// If the word is the empty string, gets everything
463if (TextUtils.isEmpty(mSearchString)) {
464    // Setting the selection clause to null will return all words
465    mSelectionClause = null;
466    mSelectionArgs[0] = "";
467
468} else {
469    // Constructs a selection clause that matches the word that the user entered.
470    mSelectionClause = UserDictionary.Words.WORD + " = ?";
471
472    // Moves the user's input string to the selection arguments.
473    mSelectionArgs[0] = mSearchString;
474
475}
476
477// Does a query against the table and returns a Cursor object
478mCursor = getContentResolver().query(
479    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
480    mProjection,                       // The columns to return for each row
481    mSelectionClause                   // Either null, or the word the user entered
482    mSelectionArgs,                    // Either empty, or the string the user entered
483    mSortOrder);                       // The sort order for the returned rows
484
485// Some providers return null if an error occurs, others throw an exception
486if (null == mCursor) {
487    /*
488     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
489     * call android.util.Log.e() to log this error.
490     *
491     */
492// If the Cursor is empty, the provider found no matches
493} else if (mCursor.getCount() &lt; 1) {
494
495    /*
496     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
497     * an error. You may want to offer the user the option to insert a new row, or re-type the
498     * search term.
499     */
500
501} else {
502    // Insert code here to do something with the results
503
504}
505</pre>
506<p>
507    此查询与 SQL 语句相似:
508</p>
509<pre>
510SELECT _ID, word, locale FROM words WHERE word = &lt;userinput&gt; ORDER BY word ASC;
511</pre>
512<p>
513    在此 SQL 语句中,会使用实际的列名称而非协定类常量。
514</p>
515<h4 id="Injection">防止恶意输入</h4>
516<p>
517    如果内容提供程序管理的数据位于 SQL 数据库中,将不受信任的外部数据包括在原始 SQL 语句中可能会导致 SQL 注入。
518
519</p>
520<p>
521    考虑此选择子句:
522</p>
523<pre>
524// Constructs a selection clause by concatenating the user's input to the column name
525String mSelectionClause =  "var = " + mUserInput;
526</pre>
527<p>
528    如果您执行此操作,则会允许用户将恶意 SQL 串连到 SQL 语句上。
529    例如,用户可以为 <code>mUserInput</code> 输入“nothing; DROP TABLE *;”,这会生成选择子句 <code>var = nothing; DROP TABLE *;</code>。
530由于选择子句是作为 SQL 语句处理,因此这可能会导致提供程序擦除基础 SQLite 数据库中的所有表(除非提供程序设置为可捕获 <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL 注入</a>尝试)。
531
532
533
534</p>
535<p>
536    要避免此问题,可使用一个用于将 <code>?</code> 作为可替换参数的选择子句以及一个单独的选择参数数组。
537执行此操作时,用户输入直接受查询约束,而不解释为 SQL 语句的一部分。
538
539    由于用户输入未作为 SQL 处理,因此无法注入恶意 SQL。请使用此选择子句,而不要使用串连来包括用户输入:
540
541</p>
542<pre>
543// Constructs a selection clause with a replaceable parameter
544String mSelectionClause =  "var = ?";
545</pre>
546<p>
547    按如下所示设置选择参数数组:
548</p>
549<pre>
550// Defines an array to contain the selection arguments
551String[] selectionArgs = {""};
552</pre>
553<p>
554    按如下所示将值置于选择参数数组中:
555</p>
556<pre>
557// Sets the selection argument to the user's input
558selectionArgs[0] = mUserInput;
559</pre>
560<p>
561    一个用于将 <code>?</code> 用作可替换参数的选择子句和一个选择参数数组是指定选择的首选方式,即使提供程序并未基于 SQL 数据库。
562
563
564</p>
565<!-- Displaying the results -->
566<h3 id="DisplayResults">显示查询结果</h3>
567<p>
568    {@link android.content.ContentResolver#query ContentResolver.query()} 客户端方法始终会返回符合以下条件的 {@link android.database.Cursor}:包含查询的投影为匹配查询选择条件的行指定的列。
569
570
571{@link android.database.Cursor} 对象为其包含的行和列提供随机读取访问权限。
572通过使用 {@link android.database.Cursor} 方法,您可以循环访问结果中的行、确定每个列的数据类型、从列中获取数据,并检查结果的其他属性。
573
574某些 {@link android.database.Cursor} 实现会在提供程序的数据发生更改时自动更新对象和/或在 {@link android.database.Cursor} 更改时触发观察程序对象中的方法。
575
576
577</p>
578<p class="note">
579    <strong>注</strong>:提供程序可能会根据发出查询的对象的性质来限制对列的访问。
580例如,联系人提供程序会限定只有同步适配器才能访问某些列,因此不会将它们返回至 Activity 或服务。
581
582</p>
583<p>
584    如果没有与选择条件匹配的行,则提供程序会返回 {@link android.database.Cursor#getCount Cursor.getCount()} 为 0(空游标)的 {@link android.database.Cursor} 对象。
585
586
587</p>
588<p>
589    如果出现内部错误,查询结果将取决于具体的提供程序。它可能会选择返回 <code>null</code>,或抛出 {@link java.lang.Exception}。
590
591</p>
592<p>
593    由于 {@link android.database.Cursor} 是行“列表”,因此显示 {@link android.database.Cursor} 内容的良好方式是通过 {@link android.widget.SimpleCursorAdapter} 将其与 {@link android.widget.ListView} 关联。
594
595
596</p>
597<p>
598    以下代码段将延续上一代码段的代码。它会创建一个包含由查询检索到的 {@link android.database.Cursor} 的 {@link android.widget.SimpleCursorAdapter} 对象,并将此对象设置为 {@link android.widget.ListView} 的适配器:
599
600
601
602</p>
603<pre class="prettyprint">
604// Defines a list of columns to retrieve from the Cursor and load into an output row
605String[] mWordListColumns =
606{
607    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
608    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
609};
610
611// Defines a list of View IDs that will receive the Cursor columns for each row
612int[] mWordListItems = { R.id.dictWord, R.id.locale};
613
614// Creates a new SimpleCursorAdapter
615mCursorAdapter = new SimpleCursorAdapter(
616    getApplicationContext(),               // The application's Context object
617    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
618    mCursor,                               // The result from the query
619    mWordListColumns,                      // A string array of column names in the cursor
620    mWordListItems,                        // An integer array of view IDs in the row layout
621    0);                                    // Flags (usually none are needed)
622
623// Sets the adapter for the ListView
624mWordList.setAdapter(mCursorAdapter);
625</pre>
626<p class="note">
627    <strong>注</strong>:要通过 {@link android.database.Cursor} 支持 {@link android.widget.ListView},游标必需包含名为 <code>_ID</code> 的列。
628
629    正因如此,前文显示的查询会为“字词”表检索 <code>_ID</code> 列,即使 {@link android.widget.ListView} 未显示该列。
630
631    此限制也解释了为什么大多数提供程序的每个表都具有 <code>_ID</code> 列。
632
633</p>
634
635        <!-- Getting data from query results -->
636<h3 id="GettingResults">从查询结果中获取数据</h3>
637<p>
638    您可以将查询结果用于其他任务,而不是仅显示它们。例如,您可以从用户字典中检索拼写,然后在其他提供程序中查找它们。
639
640要执行此操作,您需要在 {@link android.database.Cursor} 中循环访问行:
641</p>
642<pre class="prettyprint">
643
644// Determine the column index of the column named "word"
645int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
646
647/*
648 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
649 * an internal error occurs. Other providers may throw an Exception instead of returning null.
650 */
651
652if (mCursor != null) {
653    /*
654     * Moves to the next row in the cursor. Before the first movement in the cursor, the
655     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
656     * exception.
657     */
658    while (mCursor.moveToNext()) {
659
660        // Gets the value from the column.
661        newWord = mCursor.getString(index);
662
663        // Insert code here to process the retrieved word.
664
665        ...
666
667        // end of while loop
668    }
669} else {
670
671    // Insert code here to report an error if the cursor is null or the provider threw an exception.
672}
673</pre>
674<p>
675    {@link android.database.Cursor} 实现包含多个用于从对象中检索不同类型的数据的“获取”方法。
676例如,上一个代码段使用 {@link android.database.Cursor#getString getString()}。
677它们还具有 {@link android.database.Cursor#getType getType()} 方法,该方法会返回指示列的数据类型的值。
678
679
680</p>
681
682
683    <!-- Requesting permissions -->
684<h2 id="Permissions">内容提供程序权限</h2>
685<p>
686    提供程序的应用可以指定其他应用访问提供程序的数据所必需的权限。
687这些权限可确保用户了解应用将尝试访问的数据。
688根据提供程序的要求,其他应用会请求它们访问提供程序所需的权限。
689最终用户会在安装应用时看到所请求的权限。
690
691</p>
692<p>
693    如果提供程序的应用未指定任何权限,则其他应用将无权访问提供程序的数据。
694但是,无论指定权限为何,提供程序的应用中的组件始终具有完整的读取和写入访问权限。
695
696</p>
697<p>
698    如前所述,用户字典提供程序需要
699 <code>android.permission.READ_USER_DICTIONARY</code> 权限才能从中检索数据。
700    提供程序具有用于插入、更新或删除数据的单独 <code>android.permission.WRITE_USER_DICTIONARY</code> 权限。
701
702</p>
703<p>
704    要获取访问提供程序所需的权限,应用将通过其清单文件中的
705<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">&lt;uses-permission&gt;</a></code>
706 元素来请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。
707如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装。
708
709
710</p>
711<p>
712    以下
713<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">&lt;uses-permission&gt;</a></code> 元素会请求对用户字典提供程序的读取访问权限:
714
715</p>
716<pre>
717    &lt;uses-permission android:name="android.permission.READ_USER_DICTIONARY"&gt;
718</pre>
719<p>
720
721<a href="{@docRoot}guide/topics/security/security.html">安全与权限</a>指南中详细介绍了权限对提供程序访问的影响。
722</p>
723
724
725<!-- Inserting, Updating, and Deleting Data -->
726<h2 id="Modifications">插入、更新和删除数据</h2>
727<p>
728    与从提供程序检索数据的方式相同,也可以通过提供程序客户端和提供程序 {@link android.content.ContentProvider} 之间的交互来修改数据。
729
730    您通过传递到 {@link android.content.ContentProvider} 的对应方法的参数来调用 {@link android.content.ContentResolver} 方法。
731提供程序和提供程序客户端会自动处理安全性和跨进程通信。
732
733</p>
734<h3 id="Inserting">插入数据</h3>
735<p>
736    要将数据插入提供程序,可调用
737 {@link android.content.ContentResolver#insert ContentResolver.insert()}
738 方法。此方法会在提供程序中插入新行并为该行返回内容 URI。
739    此代码段显示如何将新字词插入用户字典提供程序:
740</p>
741<pre class="prettyprint">
742// Defines a new Uri object that receives the result of the insertion
743Uri mNewUri;
744
745...
746
747// Defines an object to contain the new values to insert
748ContentValues mNewValues = new ContentValues();
749
750/*
751 * Sets the values of each column and inserts the word. The arguments to the "put"
752 * method are "column name" and "value"
753 */
754mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
755mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
756mNewValues.put(UserDictionary.Words.WORD, "insert");
757mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
758
759mNewUri = getContentResolver().insert(
760    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
761    mNewValues                          // the values to insert
762);
763</pre>
764<p>
765    新行的数据会进入单个 {@link android.content.ContentValues} 对象中,该对象在形式上与单行游标类似。
766此对象中的列不需要具有相同的数据类型,如果您不想指定值,则可以使用 {@link android.content.ContentValues#putNull ContentValues.putNull()} 将列设置为 <code>null</code>。
767
768
769</p>
770<p>
771    代码段不会添加 <code>_ID</code> 列,因为系统会自动维护此列。
772提供程序会向添加的每个行分配唯一的 <code>_ID</code> 值。
773通常,提供程序会将此值用作表的主键。
774</p>
775<p>
776    <code>newUri</code> 中返回的内容 URI 会按照以下格式标识新添加的行:
777
778</p>
779<pre>
780content://user_dictionary/words/&lt;id_value&gt;
781</pre>
782<p>
783    <code>&lt;id_value&gt;</code> 是新行的 <code>_ID</code> 内容。
784    大多数提供程序都能自动检测这种格式的内容 URI,然后在该特定行上执行请求的操作。
785
786</p>
787<p>
788    要从返回的 {@link android.net.Uri} 中获取 <code>_ID</code> 的值,请调用
789 {@link android.content.ContentUris#parseId ContentUris.parseId()}。
790</p>
791<h3 id="Updating">更新数据</h3>
792<p>
793    要更新行,请按照执行插入的方式使用具有更新值的 {@link android.content.ContentValues} 对象,并按照执行查询的方式使用选择条件。
794
795    您使用的客户端方法是
796 {@link android.content.ContentResolver#update ContentResolver.update()}。您只需将值添加至您要更新的列的 {@link android.content.ContentValues} 对象。
797如果您要清除列的内容,请将值设置为 <code>null</code>。
798
799</p>
800<p>
801    以下代码段会将区域设置具有语言“en”的所有行的区域设置更改为 <code>null</code>。
802返回值是已更新的行数:
803</p>
804<pre>
805// Defines an object to contain the updated values
806ContentValues mUpdateValues = new ContentValues();
807
808// Defines selection criteria for the rows you want to update
809String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
810String[] mSelectionArgs = {"en_%"};
811
812// Defines a variable to contain the number of updated rows
813int mRowsUpdated = 0;
814
815...
816
817/*
818 * Sets the updated value and updates the selected words.
819 */
820mUpdateValues.putNull(UserDictionary.Words.LOCALE);
821
822mRowsUpdated = getContentResolver().update(
823    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
824    mUpdateValues                       // the columns to update
825    mSelectionClause                    // the column to select on
826    mSelectionArgs                      // the value to compare to
827);
828</pre>
829<p>
830    您还应该在调用
831 {@link android.content.ContentResolver#update ContentResolver.update()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读<a href="#Injection">防止恶意输入</a>部分。
832
833</p>
834<h3 id="Deleting">删除数据</h3>
835<p>
836    删除行与检索行数据类似:为要删除的行指定选择条件,客户端方法会返回已删除的行数。
837
838    以下代码段会删除应用 ID 与“用户”匹配的行。该方法会返回已删除的行数。
839
840</p>
841<pre>
842
843// Defines selection criteria for the rows you want to delete
844String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
845String[] mSelectionArgs = {"user"};
846
847// Defines a variable to contain the number of rows deleted
848int mRowsDeleted = 0;
849
850...
851
852// Deletes the words that match the selection criteria
853mRowsDeleted = getContentResolver().delete(
854    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
855    mSelectionClause                    // the column to select on
856    mSelectionArgs                      // the value to compare to
857);
858</pre>
859<p>
860    您还应该在调用
861 {@link android.content.ContentResolver#delete ContentResolver.delete()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读<a href="#Injection">防止恶意输入</a>部分。
862
863</p>
864<!-- Provider Data Types -->
865<h2 id="DataTypes">提供程序数据类型</h2>
866<p>
867    内容提供程序可以提供多种不同的数据类型。用户字典提供程序仅提供文本,但提供程序也能提供以下格式:
868
869</p>
870    <ul>
871        <li>
872            整型
873        </li>
874        <li>
875            长整型(长)
876        </li>
877        <li>
878            浮点型
879        </li>
880        <li>
881            长浮点型(双倍)
882        </li>
883    </ul>
884<p>
885    提供程序经常使用的另一种数据类型是作为 64KB 字节的数组实施的二进制大型对象 (BLOB)。
886您可以通过查看
887 {@link android.database.Cursor} 类“获取”方法看到可用数据类型。
888</p>
889<p>
890    提供程序文档中通常都列出了其每个列的数据类型。
891    用户字典提供程序的数据类型列在其协定类 {@link android.provider.UserDictionary.Words} 参考文档中(<a href="#ContractClasses">协定类</a>部分对协定类进行了介绍)。
892
893
894    您也可以通过调用 {@link android.database.Cursor#getType
895 Cursor.getType()} 来确定数据类型。
896</p>
897<p>
898    提供程序还会维护其定义的每个内容 URI 的 MIME(多用途互联网邮件扩展)数据类型信息。您可以使用 MIME 类型信息查明应用是否可以处理提供程序提供的数据,或根据 MIME 类型选择处理类型。
899
900在使用包含复杂数据结构或文件的提供程序时,通常需要 MIME 类型。
901
902例如,联系人提供程序中的 {@link android.provider.ContactsContract.Data}
903 表会使用 MIME 类型来标记每行中存储的联系人数据类型。
904要获取与内容 URI 对应的 MIME 类型,请调用
905 {@link android.content.ContentResolver#getType ContentResolver.getType()}。
906</p>
907<p>
908    <a href="#MIMETypeReference">MIME 类型引用</a>部分介绍了标准和自定义 MIME 类型的语法。
909
910</p>
911
912
913<!-- Alternative Forms of Provider Access -->
914<h2 id="AltForms">提供程序访问的替代形式</h2>
915<p>
916    提供程序访问的三种替代形式在应用开发过程中十分重要:
917</p>
918<ul>
919    <li>
920        <a href="#Batch">批量访问</a>:您可以通过
921 {@link android.content.ContentProviderOperation} 类中的方法创建一批访问调用,然后通过
922 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} 应用它们。
923    </li>
924    <li>
925        异步查询:您应该在单独线程中执行查询。执行此操作的方式之一是使用 {@link android.content.CursorLoader} 对象。
926<a href="{@docRoot}guide/components/loaders.html">加载器</a>指南中的示例展示了如何执行此操作。
927
928
929    </li>
930    <li>
931        <a href="#Intents">通过 Intent 访问数据</a>:尽管您无法直接向提供程序发送 Intent,但可以向提供程序的应用发送 Intent,后者通常具有修改提供程序数据的最佳配置。
932
933
934    </li>
935</ul>
936<p>
937    下文将介绍批量访问和修改。
938</p>
939<h3 id="Batch">批量访问</h3>
940<p>
941    批量访问提供程序适用于插入大量行,或通过同一方法调用在多个表中插入行,或者通常用于跨进程界限将一组操作作为事务处理(原子操作)执行。
942
943
944</p>
945<p>
946    要在“批量模式”下访问提供程序,
947您可以创建 {@link android.content.ContentProviderOperation} 对象数组,然后使用 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}
948将其分派给内容提供程序。
949您需将内容提供程序的<em>授权</em>传递给此方法,而不是特定内容 URI。这样可使数组中的每个 {@link android.content.ContentProviderOperation} 对象都能适用于其他表。
950
951
952调用 {@link android.content.ContentResolver#applyBatch
953 ContentResolver.applyBatch()} 会返回结果数组。
954</p>
955<p>
956    {@link android.provider.ContactsContract.RawContacts} 协定类
957的说明包括展示批量注入的代码段。
958<a href="{@docRoot}resources/samples/ContactManager/index.html">联系人管理器</a>示例应用包含在其 <code>ContactAdder.java</code>
959 源文件中进行批量访问的示例。
960
961</p>
962<div class="sidebox-wrapper">
963<div class="sidebox">
964<h2>使用帮助程序应用显示数据</h2>
965<p>
966    如果您的应用<em>具有</em>访问权限,您可能仍想使用 Intent 对象在其他应用中显示数据。
967例如,日历应用接受
968 {@link android.content.Intent#ACTION_VIEW} Intent 对象,用于显示特定的日期或事件。
969    这样,您可以在不创建自己的 UI 的情况下显示日历信息。如需了解有关此功能的详情,请参见<a href="{@docRoot}guide/topics/providers/calendar-provider.html">日历提供程序</a>指南。
970
971
972</p>
973<p>
974    您向其发送 Intent 对象的应用不一定要是与提供程序关联的应用。
975例如,您可以从联系人提供程序中检索联系人,然后将包含联系人图像的内容 URI 的 {@link android.content.Intent#ACTION_VIEW}  Intent 发送至图像查看器。
976
977
978</p>
979</div>
980</div>
981<h3 id="Intents">通过 Intent 访问数据</h3>
982<p>
983     Intent 可以提供对内容提供程序的间接访问。即使您的应用不具备访问权限,您也可以通过以下方式允许用户访问提供程序中的数据:从具有权限的应用中获取回结果 Intent,或者通过激活具有权限的应用,然后让用户在其中工作。
984
985
986
987</p>
988<h4>通过临时权限获取访问权限</h4>
989<p>
990    即使您没有适当的访问权限,也可以通过以下方式访问内容提供程序中的数据:将 Intent 发送至具有权限的应用,然后接收回包含“URI”权限的结果 Intent。
991
992
993    这些是特定内容 URI 的权限,将持续至接收该权限的 Activity 结束。
994具有永久权限的应用将通过在结果 Intent 中设置标志来授予临时权限:
995
996</p>
997<ul>
998    <li>
999        <strong>读取权限:</strong>
1000{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}
1001    </li>
1002    <li>
1003        <strong>写入权限:</strong>
1004{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
1005    </li>
1006</ul>
1007<p class="note">
1008    <strong>注</strong>:这些标志不会为其授权包含在内容 URI 中的提供程序
1009提供常规的读取或写入访问权限。访问权限仅适用于 URI 本身。
1010</p>
1011<p>
1012    提供程序使用
1013<code><a href="{@docRoot}guide/topics/manifest/provider-element.html">&lt;provider&gt;</a></code>
1014元素的
1015<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">android:grantUriPermission</a></code>
1016属性以及
1017<code><a href="{@docRoot}guide/topics/manifest/provider-element.html">&lt;provider&gt;</a></code>
1018元素的
1019<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">&lt;grant-uri-permission&gt;</a></code>
1020子元素在其清单文件中定义内容 URI 的 URI 权限。<a href="{@docRoot}guide/topics/security/security.html">安全与权限</a>指南中“URI 权限”部分更加详细地说明了 URI 权限机制。
1021
1022
1023</p>
1024<p>
1025    例如,即使您没有
1026 {@link android.Manifest.permission#READ_CONTACTS} 权限,也可以在联系人提供程序中检索联系人的数据。您可能希望在向联系人发送电子生日祝福的应用中执行此操作。
1027您更愿意让用户控制应用所使用的联系人,而不是请求 {@link android.Manifest.permission#READ_CONTACTS},让您能够访问用户的所有联系人及其信息。
1028
1029
1030要执行此操作,您需要使用以下进程:
1031</p>
1032<ol>
1033    <li>
1034        您的应用会使用方法 {@link android.app.Activity#startActivityForResult
1035  startActivityForResult()} 发送包含操作
1036 {@link android.content.Intent#ACTION_PICK} 和“联系人”MIME 类型
1037 {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} 的 Intent 对象。
1038
1039    </li>
1040    <li>
1041        由于此 Intent 与“联系人”应用的“选择” Activity 的 Intent 过滤器相匹配,因此 Activity 会显示在前台。
1042
1043    </li>
1044    <li>
1045        在选择 Activity 中,用户选择要更新的联系人。
1046发生此情况时,选择 Activity 会调用
1047 {@link android.app.Activity#setResult setResult(resultcode, intent)}
1048 以设置用于返回至应用的 Intent。 Intent 包含用户选择的联系人的内容 URI,以及“extras”标志
1049 {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}。
1050这些标志会为您的应用授予读取内容 URI 所指向的联系人的数据的 URI
1051 权限。然后,选择 Activity 会调用 {@link android.app.Activity#finish()} 以返回对应用的控制。
1052
1053
1054    </li>
1055    <li>
1056        您的 Activity 会返回至前台,系统会调用您的 Activity 的
1057 {@link android.app.Activity#onActivityResult onActivityResult()}
1058 方法。此方法会收到“联系人”应用中选择 Activity 所创建的结果 Intent。
1059
1060    </li>
1061    <li>
1062        通过来自结果 Intent 的内容 URI,您可以读取来自联系人提供程序的联系人数据,即使您未在清单文件中请求对该提供程序的永久读取访问权限。
1063
1064您可以获取联系人的生日信息或其电子邮件地址,然后发送电子祝福。
1065
1066    </li>
1067</ol>
1068<h4>使用其他应用</h4>
1069<p>
1070    允许用户修改您无权访问的数据的简单方法是激活具有权限的应用,让用户在其中执行工作。
1071
1072</p>
1073<p>
1074    例如,日历应用接受
1075 {@link android.content.Intent#ACTION_INSERT} Intent,这让您可以激活应用的插入 UI。您可以在此 Intent(应用将使用该 Intent 来预先填充 UI)中传递“额外”数据,由于定期事件具有复杂的语法,因此将事件插入日历提供程序的首选方式是激活具有
1076 {@link android.content.Intent#ACTION_INSERT} 的日历应用,然后让用户在其中插入事件。
1077
1078
1079
1080</p>
1081<!-- Contract Classes -->
1082<h2 id="ContractClasses">协定类</h2>
1083<p>
1084    协定类定义帮助应用使用内容 URI、列名称、 Intent 操作以及内容提供程序的其他功能的常量。
1085协定类未自动包含在提供程序中;提供程序的开发者需要定义它们,然后使其可用于其他开发者。
1086
1087Android
1088 平台中包含的许多提供程序都在软件包 {@link android.provider} 中具有对应的协定类。
1089</p>
1090<p>
1091    例如,用户字典提供程序具有包含内容 URI 和列名称常量的协定类 {@link android.provider.UserDictionary}。
1092
1093“字词”表的内容 URI 在常量
1094 {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI} 中定义。
1095    {@link android.provider.UserDictionary.Words} 类也包含列名称常量,本指南的示例代码段中就使用了该常量。
1096例如,查询投影可以定义为:
1097
1098</p>
1099<pre>
1100String[] mProjection =
1101{
1102    UserDictionary.Words._ID,
1103    UserDictionary.Words.WORD,
1104    UserDictionary.Words.LOCALE
1105};
1106</pre>
1107<p>
1108    联系人提供程序的 {@link android.provider.ContactsContract} 也是一个协定类。
1109    此类的参考文档包括示例代码段。其子类之一 {@link android.provider.ContactsContract.Intents.Insert} 是包含 Intent 和 Intent 数据的协定类。
1110
1111
1112</p>
1113
1114
1115<!-- MIME Type Reference -->
1116<h2 id="MIMETypeReference">MIME 类型引用</h2>
1117<p>
1118    内容提供程序可以返回标准 MIME 媒体类型和/或自定义 MIME 类型字符串。
1119</p>
1120<p>
1121    MIME 类型具有格式
1122</p>
1123<pre>
1124<em>type</em>/<em>subtype</em>
1125</pre>
1126<p>
1127    例如,众所周知的 MIME 类型 <code>text/html</code> 具有 <code>text</code> 类型和
1128 <code>html</code> 子类型。如果提供程序为 URI 返回此类型,则意味着使用该 URI 的查询会返回包含 HTML 标记的文本。
1129
1130</p>
1131<p>
1132    自定义 MIME 类型字符串(也称为“特定于供应商”的 MIME 类型)具有更加复杂的<em>类型</em>和<em>子类型</em>值。
1133<em>类型</em>值始终为
1134</p>
1135<pre>
1136vnd.android.cursor.<strong>dir</strong>
1137</pre>
1138<p>
1139    (多行)或
1140</p>
1141<pre>
1142vnd.android.cursor.<strong>item</strong>
1143</pre>
1144<p>
1145    (单行)。
1146</p>
1147<p>
1148    <em>子类型</em>特定于提供程序。Android 内置提供程序通常具有简单的子类型。
1149例如,当联系人应用为电话号码创建行时,它会在行中设置以下 MIME 类型:
1150
1151</p>
1152<pre>
1153vnd.android.cursor.item/phone_v2
1154</pre>
1155<p>
1156    请注意,子类型值只是 <code>phone_v2</code>。
1157</p>
1158<p>
1159    其他提供程序开发者可能会根据提供程序的授权和表名称创建自己的子类型模式。
1160例如,假设提供程序包含列车时刻表。
1161    提供程序的授权是 <code>com.example.trains</code>,并包含表
1162 Line1、Line2 和 Line3。在响应表 Line1 的内容 URI
1163</p>
1164<p>
1165<pre>
1166content://com.example.trains/Line1
1167</pre>
1168<p>
1169    时,提供程序会返回 MIME 类型
1170</p>
1171<pre>
1172vnd.android.cursor.<strong>dir</strong>/vnd.example.line1
1173</pre>
1174<p>
1175     在响应表 Line2 中第 5 行的内容 URI
1176</p>
1177<pre>
1178content://com.example.trains/Line2/5
1179</pre>
1180<p>
1181    时,提供程序会返回 MIME 类型
1182</p>
1183<pre>
1184vnd.android.cursor.<strong>item</strong>/vnd.example.line2
1185</pre>
1186<p>
1187    大多数内容提供程序都会为其使用的 MIME 类型定义协定类常量。例如,联系人提供程序协定类 {@link android.provider.ContactsContract.RawContacts} 会为单个原始联系人行的 MIME 类型定义常量
1188 {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}。
1189
1190
1191
1192</p>
1193<p>
1194
1195<a href="#ContentURIs">内容 URI</a> 部分介绍了单个行的内容 URI。
1196</p>
1197