ContentProvider 和ContentResolver内容提供者和内容解析者
Posted 和平world
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ContentProvider 和ContentResolver内容提供者和内容解析者相关的知识,希望对你有一定的参考价值。
内容提供程序管理对中央数据存储库的访问。提供程序是 android 应用的一部分,通常提供自己的 UI 来使用数据。 但是,内容提供程序主要旨在供其他应用使用,这些应用使用提供程序客户端对象来访问提供程序。 提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理跨进程通信并保护数据访问的安全性。
本主题介绍了以下基础知识:
- 内容提供程序的工作方式。
- 用于从内容提供程序检索数据的 API。
- 用于在内容提供程序中插入、更新或删除数据的 API。
- 其他有助于使用提供程序的 API 功能。
概览
内容提供程序以一个或多个表(与在关系数据库中找到的表类似)的形式将数据呈现给外部应用。 行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。
例如,Android 平台的内置提供程序之一是用户字典,它会存储用户想要保存的非标准字词的拼写。 表 1 描述了数据在此提供程序表中的显示情况:
字词 | 应用 id | 频率 | 区域设置 | _ID |
---|---|---|---|---|
mapreduce | user1 | 100 | en_US | 1 |
precompiler | user14 | 200 | fr_FR | 2 |
applet | user2 | 225 | fr_CA | 3 |
const | user1 | 255 | pt_BR | 4 |
int | user5 | 100 | en_UK | 5 |
在表 1 中,每行表示可能无法在标准词典中找到的字词实例。 每列表示该字词的某些数据,如该字词首次出现时的区域设置。 列标题是存储在提供程序中的列名称。 要引用行的区域设置,需要引用其 locale
列。对于此提供程序,_ID
列充当由提供程序自动维护的“主键”列。
注:提供程序无需具有主键,也无需将 _ID
用作其主键的列名称(如果存在主键)。 但是,如果您要将来自提供程序的数据与 ListView
绑定,则其中一个列名称必须是 _ID
。 显示查询结果部分详细说明了此要求。
访问提供程序
应用从具有 ContentResolver
客户端对象的内容提供程序访问数据。 此对象具有调用提供程序对象(ContentProvider
的某个具体子类的实例)中同名方法的方法。 ContentResolver
方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。
客户端应用进程中的 ContentResolver
对象和拥有提供程序的应用中的 ContentProvider
对象可自动处理跨进程通信。 ContentProvider
还可充当其数据存储库和表格形式的数据外部显示之间的抽象层。
注:要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。 内容提供程序权限部分详细介绍了此内容。
例如,要从用户字典提供程序中获取字词及其区域设置的列表,则需调用 ContentResolver.query()
。 query()
方法会调用用户字典提供程序所定义的ContentProvider.query()
方法。 以下代码行显示了 ContentResolver.query()
调用:
// Queries the user dictionary and returns results mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table mProjection, // The columns to return for each row mSelectionClause // Selection criteria mSelectionArgs, // Selection criteria mSortOrder); // The sort order for the returned rows
表 2 显示了 query(Uri,projection,selection,selectionArgs,sortOrder)
的参数如何匹配 SQL SELECT 语句:
query() 参数 | SELECT 关键字/参数 | 备注 |
---|---|---|
Uri | FROM table_name | Uri 映射至名为 table_name 的提供程序中的表。 |
projection | col,col,col,... | projection 是应该为检索到的每个行包含的列的数组。 |
selection | WHERE col = value | selection 会指定选择行的条件。 |
selectionArgs | (没有完全等效项。选择参数会替换选择子句中 ? 的占位符。) | |
sortOrder | ORDER BY col,col,... | sortOrder 指定行在返回的 Cursor 中的显示顺序。 |
内容 URI
内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其权限)和一个指向表的名称(路径)。 当您调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。
在前面的代码行中,常量 CONTENT_URI
包含用户字典的“字词”表的内容 URI。 ContentResolver
对象会分析出 URI 的授权,并通过将该授权与已知提供程序的系统表进行比较,来“解析”提供程序。 然后, ContentResolver
可以将查询参数分派给正确的提供程序。
ContentProvider
使用内容 URI 的路径部分来选择要访问的表。 提供程序通常会为其公开的每个表显示一条路径。
在前面的代码行中,“字词”表的完整 URI 是:
content://user_dictionary/words
其中,user_dictionary
字符串是提供程序的授权, words
字符串是表的路径。字符串 content://
(架构)始终显示,并将此标识为内容 URI。
许多提供程序都允许您通过将 ID 值追加到 URI 末尾来访问表中的单个行。例如,要从用户字典中检索 _ID
为 4
的行,则可使用此内容 URI:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。
注:Uri
和 Uri.Builder
类 包含根据字符串构建格式规范的 URI 对象的便利方法。 ContentUris
包含一些可以将 ID 值轻松追加到 URI 后的方法。前面的代码段就是使用 withAppendedId()
将 ID 追加到用户字典内容 URI 后。
从提供程序检索数据
本节将以用户字典提供程序为例,介绍如何从提供程序中检索数据。
为了明确进行说明,本节中的代码段将在“UI 线程”上调用 ContentResolver.query()
。但在实际代码中,您应该在单独线程上异步执行查询。 执行此操作的方式之一是使用 CursorLoader
类,加载器指南中对此有更为详细的介绍。 此外,前述代码行只是代码段;它们不会显示整个应用。
要从提供程序中检索数据,请按照以下基本步骤执行操作:
- 请求对提供程序的读取访问权限。
- 定义将查询发送至提供程序的代码。
请求读取访问权限
要从提供程序检索数据,您的应需要具备对提供程序的“读取访问”权限。 您无法在运行时请求此权限;相反,您需要使用<uses-permission>
元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。 在您的清单文件中指定此元素后,您将有效地为应用“请求”此权限。 用户安装您的应用时,会隐式授予允许此请求。
要找出您正在使用的提供程序的读取访问权限的准确名称,以及提供程序使用的其他访问权限的名称,请查看提供程序的文档。
内容提供程序权限部分详细介绍了权限在访问提供程序过程中的作用。
用户字典提供程序在其清单文件中定义了权限 android.permission.READ_USER_DICTIONARY
,因此希望从提供程序中进行读取的应用必需请求此权限。
构建查询
从提供程序中检索数据的下一步是构建查询。第一个代码段定义某些用于访问用户字典提供程序的变量:
// A "projection" defines the columns that will be returned for each row String[] mProjection = 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 mSelectionClause = null; // Initializes an array to contain selection arguments String[] mSelectionArgs = "";
下一个代码段以用户字典提供程序为例,显示了如何使用 ContentResolver.query()
。 提供程序客户端查询与 SQL 查询类似,并且包含一组要返回的列、一组选择条件和排序顺序。
查询应该返回的列集被称为投影(变量 mProjection
)。
用于指定要检索的行的表达式分割为选择子句和选择参数。 选择子句是逻辑和布尔表达式、列名称和值(变量 mSelectionClause
)的组合。 如果您指定了可替换参数 ?
而非值,则查询方法会从选择参数数组(变量 mSelectionArgs
)中检索值。
在下一个代码段中,如果用户未输入字词,则选择子句将设置为 null
,而且查询会返回提供程序中的所有字词。 如果用户输入了字词,选择子句将设置为 UserDictionary.Words.WORD + " = ?"
且选择参数数组的第一个元素将设置为用户输入的字词。
/* * This defines a one-element String array to contain the selection argument. */ String[] mSelectionArgs = ""; // Gets a word from the UI mSearchString = mSearchWord.getText().toString(); // Remember to insert code here to check for invalid or malicious input. // If the word is the empty string, gets everything if (TextUtils.isEmpty(mSearchString)) // Setting the selection clause to null will return all words mSelectionClause = null; mSelectionArgs[0] = ""; else // Constructs a selection clause that matches the word that the user entered. mSelectionClause = UserDictionary.Words.WORD + " = ?"; // Moves the user's input string to the selection arguments. mSelectionArgs[0] = mSearchString; // 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 mProjection, // The columns to return for each row mSelectionClause // Either null, or the word the user entered mSelectionArgs, // Either empty, or the string the user entered mSortOrder); // 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. Be sure not to use the cursor! You may want to * call android.util.Log.e() to log this error. * */ // If the Cursor is empty, the provider found no matches else if (mCursor.getCount() < 1) /* * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily * an error. You may want to offer the user the option to insert a new row, or re-type the * search term. */ else // Insert code here to do something with the results
此查询与 SQL 语句相似:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
在此 SQL 语句中,会使用实际的列名称而非协定类常量。
防止恶意输入
如果内容提供程序管理的数据位于 SQL 数据库中,将不受信任的外部数据包括在原始 SQL 语句中可能会导致 SQL 注入。
考虑此选择子句:
// Constructs a selection clause by concatenating the user's input to the column name String mSelectionClause = "var = " + mUserInput;
如果您执行此操作,则会允许用户将恶意 SQL 串连到 SQL 语句上。 例如,用户可以为 mUserInput
输入“nothing; DROP TABLE *;”,这会生成选择子句var = nothing; DROP TABLE *;
。 由于选择子句是作为 SQL 语句处理,因此这可能会导致提供程序擦除基础 SQLite 数据库中的所有表(除非提供程序设置为可捕获 SQL 注入尝试)。
要避免此问题,可使用一个用于将 ?
作为可替换参数的选择子句以及一个单独的选择参数数组。 执行此操作时,用户输入直接受查询约束,而不解释为 SQL 语句的一部分。 由于用户输入未作为 SQL 处理,因此无法注入恶意 SQL。请使用此选择子句,而不要使用串连来包括用户输入:
// Constructs a selection clause with a replaceable parameter String mSelectionClause = "var = ?";
按如下所示设置选择参数数组:
// Defines an array to contain the selection arguments String[] selectionArgs = "";
按如下所示将值置于选择参数数组中:
// Sets the selection argument to the user's input selectionArgs[0] = mUserInput;
一个用于将 ?
用作可替换参数的选择子句和一个选择参数数组是指定选择的首选方式,即使提供程序并未基于 SQL 数据库。
显示查询结果
ContentResolver.query()
客户端方法始终会返回符合以下条件的 Cursor
:包含查询的投影为匹配查询选择条件的行指定的列。 Cursor
对象为其包含的行和列提供随机读取访问权限。 通过使用 Cursor
方法,您可以循环访问结果中的行、确定每个列的数据类型、从列中获取数据,并检查结果的其他属性。 某些 Cursor
实现会在提供程序的数据发生更改时自动更新对象和/或在 Cursor
更改时触发观察程序对象中的方法。
注:提供程序可能会根据发出查询的对象的性质来限制对列的访问。 例如,联系人提供程序会限定只有同步适配器才能访问某些列,因此不会将它们返回至 Activity 或服务。
如果没有与选择条件匹配的行,则提供程序会返回 Cursor.getCount()
为 0(空游标)的 Cursor
对象。
如果出现内部错误,查询结果将取决于具体的提供程序。它可能会选择返回 null
,或抛出 Exception
。
由于 Cursor
是行“列表”,因此显示 Cursor
内容的良好方式是通过 SimpleCursorAdapter
将其与 ListView
关联。
以下代码段将延续上一代码段的代码。它会创建一个包含由查询检索到的 Cursor
的 SimpleCursorAdapter
对象,并将此对象设置为 ListView
的适配器:
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] mWordListColumns = UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name ; // Defines a list of View IDs that will receive the Cursor columns for each row int[] mWordListItems = R.id.dictWord, R.id.locale; // Creates a new SimpleCursorAdapter mCursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query mWordListColumns, // A string array of column names in the cursor mWordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView mWordList.setAdapter(mCursorAdapter);
注:要通过 Cursor
支持 ListView
,游标必需包含名为 _ID
的列。 正因如此,前文显示的查询会为“字词”表检索 _ID
列,即使 ListView
未显示该列。 此限制也解释了为什么大多数提供程序的每个表都具有 _ID
列。
从查询结果中获取数据
您可以将查询结果用于其他任务,而不是仅显示它们。例如,您可以从用户字典中检索拼写,然后在其他提供程序中查找它们。 要执行此操作,您需要在 Cursor
中循环访问行:
// Determine the column index of the column named "word" int index = mCursor.getColumnIndex(UserDictionary.Words.WORD以上是关于ContentProvider 和ContentResolver内容提供者和内容解析者的主要内容,如果未能解决你的问题,请参考以下文章内容提供者ContentProvider和内容解析者ContentResolver
android之ContentProvider和Uri具体解释