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"><uses-permission></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() < 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 = <userinput> 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"><uses-permission></a></code> 706 元素来请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。 707如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装。 708 709 710</p> 711<p> 712 以下 713<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> 元素会请求对用户字典提供程序的读取访问权限: 714 715</p> 716<pre> 717 <uses-permission android:name="android.permission.READ_USER_DICTIONARY"> 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/<id_value> 781</pre> 782<p> 783 <code><id_value></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"><provider></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"><provider></a></code> 1018元素的 1019<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission></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