博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved !
Android的四大数据存储方式:文件,Sqlite,SharedPreference,ContentProvider。
文件可以是txt,也可以是xml,或者其他;可以保存在asset里,这样只有本项目可以访问,保存在内存卡或者SD卡中,其他项目也可以访问的到;
Sqlite主要用来规范化字段存储,本质是个文件管理系统;可以设置权限world_writeable,让所有项目都可以访问,也可以不设,默认只有本项目可以使用。
SharedPreference是个轻量级的存储器,只有本项目可以访问其中的数据。
ContentProvider使用系统常量主要用来对数据进行操作,如短信,图片,音频,联系人等等,也可以自定义URI,所有项目可用。
以下是ContentProvider的API翻译内容。
content provider 提供一套结构化数据访问。提供封装数据和数据安全机制。content provider是一个进程与另一个进程进行数据连接的确标准接口。
当你要访问content provider里的数据,你需要使用应用里的上下文对象作为访问者来与provider进行连接,它们之间是通过一个ContentProvider的实行例化对象进行通信的。这个provider对象接收client数据请求,执行操作,返回结果。
如果你不想与其他应用共享数据,你就不用开发自己的provider。但是,你需要在自己的应用里提供一套搜索建议机制。如果你想从你的应用复制或粘贴复杂的数据到其他应用,那你需要一个自己的provider。
android自定义了一些content provider来管理如音频、视频、图像、个人信息。可以通过android.provider.package来查找相关文档。有了这些约束,android应用都可以任意访问provider。
下列主题描述更详细的content provider信息。
Content Provider Basics
How to access data in a content provider when the data is organized in tables.
Creating a Content Provider
How to create your own content provider.
Calendar Provider
How to access the Calendar Provider that is part of the Android platform.
Contacts Provider
How to access the Contacts Provider that is part of the Android platform.
内容提供程序管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。
如果您想要访问内容提供程序中的数据,可以将应用的 Context
中的 ContentResolver
对象用作客户端来与提供程序通信。 ContentResolver
对象会与提供程序对象(即实现 ContentProvider
的类实例)通信。 提供程序对象从客户端接收数据请求,执行请求的操作并返回结果。
如果您不打算与其他应用共享数据,则无需开发自己的提供程序。 不过,您需要通过自己的提供程序在您自己的应用中提供自定义搜索建议。 如果您想将复杂的数据或文件从您的应用复制并粘贴到其他应用中,也需要创建您自己的提供程序。
Android 本身包括的内容提供程序可管理音频、视频、图像和个人联系信息等数据。 android.provider
软件包参考文档中列出了部分提供程序。 任何 Android 应用都可以访问这些提供程序,但会受到某些限制。
以下主题对内容提供程序做了更详尽的描述:
ContentProvider
ContentResolver
Cursor
Uri
内容提供程序管理对中央数据存储区的访问。提供程序是 Android 应用的一部分,通常提供自己的 UI 来使用数据。 但是,内容提供程序主要旨在供其他应用使用,这些应用使用提供程序客户端对象来访问提供程序。 提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理跨进程通信并保护数据访问的安全性。
本主题介绍了以下基础知识:
内容提供程序以一个或多个表(与在关系型数据库中找到的表类似)的形式将数据呈现给外部应用。 行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。
例如,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 将是其参数之一。
在前面的代码行中,常量 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 追加到 UserDictionary 内容 URI 后。
本节将以用户字典提供程序为例,介绍如何从提供程序中检索数据。
为了明确进行说明,本节中的代码段将在“UI 线程”上调用 ContentResolver.query()
。但在实际代码中,您应该在单独线程上异步执行查询。 执行此操作的方式之一是使用 CursorLoader
类,加载器指南中对此有更为详细的介绍。 此外,前述代码行只是片段;它们不会显示整个应用。
要从提供程序中检索数据,请按照以下基本步骤执行操作:
要从提供程序检索数据,您的应需要具备对提供程序的“读取访问”权限。 您无法在运行时请求此权限;相反,您需要使用
元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。 在您的清单文件中指定此元素后,您将有效地为应用“请求”此权限。 用户安装您的应用时,会隐式授予允许此请求。
要找出您正在使用的提供程序的读取访问权限的准确名称,以及提供程序使用的其他访问权限的名称,请查看提供程序的文档。
内容提供程序权限部分详细介绍了权限在访问提供程序过程中的作用。
用户字典提供程序在其清单文件中定义了权限 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 =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); /* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers may throw an Exception instead of returning null. */ if (mCursor != null) { /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you will get an * exception. */ while (mCursor.moveToNext()) { // Gets the value from the column. newWord = mCursor.getString(index); // Insert code here to process the retrieved word. ... // end of while loop } } else { // Insert code here to report an error if the cursor is null or the provider threw an exception. }
Cursor
实现包含多个用于从对象中检索不同类型的数据的“获取”方法。 例如,上一个代码段使用 getString()
。 它们还具有 getType()
方法,该方法会返回指示列的数据类型的值。
提供程序的应用可以指定其他应用访问提供程序的数据所必需的权限。 这些权限可确保用户了解应用将尝试访问的数据。 根据提供程序的要求,其他应用会请求它们访问提供程序所需的权限。 最终用户会在安装应用时看到所请求的权限。
如果提供程序的应用未指定任何权限,则其他应用将无权访问提供程序的数据。 但是,无论指定权限为何,提供程序的应用中的组件始终具有完整的读取和写入访问权限。
如前所述,用户字典提供程序需要 android.permission.READ_USER_DICTIONARY
权限才能从中检索数据。 提供程序具有用于插入、更新或删除数据的单独 android.permission.WRITE_USER_DICTIONARY
权限。
要获取访问提供程序所需的权限,应用将通过其清单文件中的
元素来请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。 如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装。
以下
元素会请求对用户字典提供程序的读取访问权限:
android:name="android.permission.READ_USER_DICTIONARY">
安全与权限指南中详细介绍了权限对提供程序访问的影响。
与从提供程序检索数据的方式相同,也可以通过提供程序客户端和提供程序 ContentProvider
之间的交互来修改数据。 您通过传递到 ContentProvider
的对应方法的参数来调用 ContentResolver
方法。 提供程序和提供程序客户端会自动处理安全性和跨进程通信。
要将数据插入提供程序,可调用 ContentResolver.insert()
方法。此方法会在提供程序中插入新行并为该行返回内容 URI。 此代码段显示如何将新字词插入用户字典提供程序:
// Defines a new Uri object that receives the result of the insertion Uri mNewUri; ... // Defines an object to contain the new values to insert ContentValues mNewValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value" */ mNewValues.put(UserDictionary.Words.APP_ID, "example.user"); mNewValues.put(UserDictionary.Words.LOCALE, "en_US"); mNewValues.put(UserDictionary.Words.WORD, "insert"); mNewValues.put(UserDictionary.Words.FREQUENCY, "100"); mNewUri = getContentResolver().insert( UserDictionary.Word.CONTENT_URI, // the user dictionary content URI mNewValues // the values to insert );
新行的数据会进入单个 ContentValues
对象中,该对象在形式上与单行游标类似。 此对象中的列不需要具有相同的数据类型,如果您不想指定值,则可以使用 ContentValues.putNull()
将列设置为 null
。
代码段不会添加 _ID
列,因为系统会自动维护此列。 提供程序会向添加的每个行分配唯一的 _ID
值。 通常,提供程序会将此值用作表的主键。
newUri
中返回的内容 URI 会按照以下格式标识新添加的行:
content://user_dictionary/words/
是新行的 _ID
内容。 大多数提供程序都能自动检测这种格式的内容 URI,然后在该特定行上执行请求的操作。
要从返回的 Uri
中获取 _ID
的值,请调用 ContentUris.parseId()
。
要更新行,请按照执行插入的方式使用具有更新值的 ContentValues
对象,并按照执行查询的方式使用选择条件。 您使用的客户端方法是ContentResolver.update()
。您只需将值添加至您要更新的列的 ContentValues
对象。 如果您要清除列的内容,请将值设置为 null
。
以下代码段会将语言区域具有语言“en”的所有行的语言区域更改为 null
。 返回值是已更新的行数:
// Defines an object to contain the updated values ContentValues mUpdateValues = new ContentValues(); // Defines selection criteria for the rows you want to update String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; String[] mSelectionArgs = {"en_%"}; // Defines a variable to contain the number of updated rows int mRowsUpdated = 0; ... /* * Sets the updated value and updates the selected words. */ mUpdateValues.putNull(UserDictionary.Words.LOCALE); mRowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // the user dictionary content URI mUpdateValues // the columns to update mSelectionClause // the column to select on mSelectionArgs // the value to compare to );
您还应该在调用 ContentResolver.update()
时检查用户输入。如需了解有关此内容的更多详情,请阅读防止恶意输入部分。
删除行与检索行数据类似:为要删除的行指定选择条件,客户端方法会返回已删除的行数。 以下代码段会删除应用 ID 与“用户”匹配的行。该方法会返回已删除的行数。
// Defines selection criteria for the rows you want to delete String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] mSelectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int mRowsDeleted = 0; ... // Deletes the words that match the selection criteria mRowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // the user dictionary content URI mSelectionClause // the column to select on mSelectionArgs // the value to compare to );
您还应该在调用 ContentResolver.delete()
时检查用户输入。如需了解有关此内容的更多详情,请阅读防止恶意输入部分。
内容提供程序可以提供多种不同的数据类型。用户字典提供程序仅提供文本,但提供程序也能提供以下格式:
提供程序经常使用的另一种数据类型是作为 64KB 字节的数组实施的二进制大型对象 (BLOB)。 您可以通过查看 Cursor
类“获取”方法看到可用数据类型。
提供程序文档中通常都列出了其每个列的数据类型。 用户字典提供程序的数据类型列在其协定类 UserDictionary.Words
参考文档中(协定类部分对协定类进行了介绍)。 您也可以通过调用 Cursor.getType()
来确定数据类型。
提供程序还会维护其定义的每个内容 URI 的 MIME(多用途互联网邮件扩展)数据类型信息。您可以使用 MIME 类型信息查明应用是否可以处理提供程序提供的数据,或根据 MIME 类型选择处理类型。 在使用包含复杂数据结构或文件的提供程序时,通常需要 MIME 类型。 例如,联系人提供程序中的 ContactsContract.Data
表会使用 MIME 类型来标记每行中存储的联系人数据类型。 要获取与内容 URI 对应的 MIME 类型,请调用ContentResolver.getType()
。
MIME 类型引用部分介绍了标准和自定义 MIME 类型的语法。
提供程序访问的三种替代形式在应用开发过程中十分重要:
ContentProviderOperation
类中的方法创建一批访问调用,然后通过 ContentResolver.applyBatch()
应用它们。CursorLoader
对象。 加载器指南中的示例展示了如何执行此操作。下文将介绍通过 Intent 进行的批量访问和修改。
批量访问提供程序适用于插入大量行,或通过同一方法调用在多个表中插入行,或者通常用于跨进程界限将一组操作作为事务处理(原子操作)执行。
要在“批量模式”下访问提供程序, 您可以创建 ContentProviderOperation
对象数组,然后使用 ContentResolver.applyBatch()
将其分派给内容提供程序。您需将内容提供程序的授权传递给此方法,而不是特定内容 URI。 这样可使数组中的每个 ContentProviderOperation
对象都能适用于其他表。 调用 ContentResolver.applyBatch()
会返回结果数组。
ContactsContract.RawContacts
协定类 的说明包括展示批量注入的代码段。 联系人管理器示例应用包含在其 ContactAdder.java
源文件中进行批量访问的示例。
如果您的应用具有访问权限,您可能仍想使用 Intent 对象在其他应用中显示数据。 例如,日历应用接受ACTION_VIEW
Intent 对象,用于显示特定的日期或事件。 这样,您可以在不创建自己的 UI 的情况下显示日历信息。 如需了解有关此功能的更多信息,请参见日历提供程序指南。
您向其发送 Intent 对象的应用不必是与提供程序关联的应用。 例如,您可以从联系人提供程序中检索联系人,然后将包含联系人图像的内容 URI 的 ACTION_VIEW
Intent 发送至图像查看器。
Intent 可以提供对内容提供程序的间接访问。即使您的应用不具备访问权限,您也可以通过以下方式允许用户访问提供程序中的数据:从具有权限的应用中获取回结果 Intent,或者通过激活具有权限的应用,然后让用户在其中工作。
即使您没有适当的访问权限,也可以通过以下方式访问内容提供程序中的数据:将 Intent 发送至具有权限的应用,然后接收回包含“URI”权限的结果 Intent。 这些是特定内容 URI 的权限,将持续至接收该权限的 Activity 结束。 具有永久权限的应用将通过在结果 Intent 中设置标志来授予临时权限:
FLAG_GRANT_READ_URI_PERMISSION
FLAG_GRANT_WRITE_URI_PERMISSION
注:这些标志不会为其授权包含在内容 URI 中的提供程序提供常规的读取或写入访问权限。 访问权限仅适用于 URI 本身。
提供程序使用
元素的 android:grantUriPermission
属性以及
元素的
子元素在其清单文件中定义内容 URI 的 URI 权限。安全与权限指南中“URI 权限”部分更加详细地说明了 URI 权限机制。
例如,即使您没有 READ_CONTACTS
权限,也可以在联系人提供程序中检索联系人的数据。您可能希望在向联系人发送电子生日祝福的应用中执行此操作。 您更愿意让用户控制应用所使用的联系人,而不是请求 READ_CONTACTS
,让您能够访问用户的所有联系人及其信息。 要执行此操作,您需要使用以下进程:
startActivityForResult()
发送包含操作 ACTION_PICK
和“联系人”MIME 类型 CONTENT_ITEM_TYPE
的 Intent 对象。setResult(resultcode, intent)
以设置用于返回至应用的 Intent。Intent 包含用户选择的联系人的内容 URI,以及“extra”标志 FLAG_GRANT_READ_URI_PERMISSION
。这些标志会为您的应用授予读取内容 URI 所指向联系人数据的 URI 权限。 然后,选择 Activity 会调用 finish()
, 将控制权交还给您的应用。onActivityResult()
方法。此方法会收到“联系人”应用中选择 Activity 所创建的结果 Intent。允许用户修改您无权访问的数据的简单方法是激活具有权限的应用,让用户在其中执行工作。
例如,日历应用接受 ACTION_INSERT
Intent 对象,这让您可以激活 应用的插入 UI。您可以在此 Intent(应用将使用该 Intent 来预先填充 UI)中传递“extra”数据。 由于定期事件具有复杂的语法,因此将事件插入日历提供程序的首选方式是激活具有 ACTION_INSERT
的日历应用,然后让用户在其中插入事件。
协定类定义帮助应用使用内容 URI、列名称、 Intent 操作以及内容提供程序的其他功能的常量。 协定类未自动包含在提供程序中;提供程序的开发者需要定义它们,然后使其可用于其他开发者。 Android 平台中包含的许多提供程序都在软件包 android.provider
中具有对应的协定类。
例如,用户字典提供程序具有包含内容 URI 和列名称常量的协定类 UserDictionary
。 “字词”表的内容 URI 在常量 UserDictionary.Words.CONTENT_URI
中定义。 UserDictionary.Words
类也包含列名称常量,本指南的示例代码段中就使用了该常量。 例如,查询投影可以定义为:
String[] mProjection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
联系人提供程序的 ContactsContract
也是一个协定类。 此类的参考文档包括示例代码段。其子类之一 ContactsContract.Intents.Insert
是包含 Intent 和 Intent 数据的协定类。
内容提供程序可以返回标准 MIME 媒体类型和/或自定义 MIME 类型字符串。
MIME 类型具有格式
type/subtype
例如,众所周知的 MIME 类型 text/html
具有 text
类型和 html
子类型。如果提供程序为 URI 返回此类型,则意味着使用该 URI 的查询会返回包含 HTML 标记的文本。
自定义 MIME 类型字符串(也称为“特定于供应商”的 MIME 类型)具有更加复杂的类型和子类型值。 类型值始终为
vnd.android.cursor.dir
(多行)或
vnd.android.cursor.item
(单行)。
子类型特定于提供程序。Android 内置提供程序通常具有简单的子类型。 例如,当“通讯录”应用为电话号码创建行时,它会在行中设置以下 MIME 类型:
vnd.android.cursor.item/phone_v2
请注意,子类型值只是 phone_v2
。
其他提供程序开发者可能会根据提供程序的授权和表名称创建自己的子类型模式。 例如,假设提供程序包含列车时刻表。 提供程序的授权是 com.example.trains
,并包含表 Line1、Line2 和 Line3。在响应表 Line1 的内容 URI
content://com.example.trains/Line1
时,提供程序会返回 MIME 类型
vnd.android.cursor.dir/vnd.example.line1
在响应表 Line2 中第 5 行的内容 URI
content://com.example.trains/Line2/5
时,提供程序会返回 MIME 类型
vnd.android.cursor.item/vnd.example.line2
大多数内容提供程序都会为其使用的 MIME 类型定义协定类常量。例如,联系人提供程序协定类 ContactsContract.RawContacts
会为单个原始联系人行的 MIME 类型定义常量 CONTENT_ITEM_TYPE
。
内容 URI 部分介绍了单个行的内容 URI。
ContentProvider
Cursor
Uri
内容提供程序管理对中央数据存储区的访问。您将提供程序作为 Android 应用中的一个或多个类(连同清单文件中的元素)实现。 其中一个类会实现子类 ContentProvider
,即您的提供程序与其他应用之间的接口。 尽管内容提供程序旨在向其他应用提供数据,但您的应用中必定有这样一些 Activity,它们允许用户查询和修改由提供程序管理的数据。
本主题的其余部分列出了开发内容提供程序的基本步骤和需要使用的 API。
请在着手开发提供程序之前执行以下操作:
如果完全是在您自己的应用中使用,则不需要提供程序即可使用 SQLite 数据库。
接下来,请按照以下步骤开发您的提供程序:
ContentProvider
类及其所需方法的具体实现。 此类是您的数据与 Android 系统其余部分之间的接口。 如需了解有关此类的详细信息,请参阅实现 ContentProvider 类部分。AbstractThreadedSyncAdapter
实现。内容提供程序是用于访问以结构化格式保存的数据的接口。在您创建该接口之前,必须决定如何存储数据。 您可以按自己的喜好以任何形式存储数据,然后根据需要设计读写数据的接口。
以下是 Android 中提供的一些数据存储技术:
SQLiteOpenHelper
类可帮助您创建数据库,SQLiteDatabase
类是用于访问数据库的基类。请记住,您不必使用数据库来实现存储区。提供程序在外部表现为一组表,与关系型数据库类似,但这并不是对提供程序内部实现的要求;
java.net
和 android.net
中的类。 您也可以将基于网络的数据与本地数据存储(如数据库)同步,然后以表或文件的形式提供数据。 示例同步适配器示例应用展示了这类同步。以下是一些设计提供程序数据结构的技巧:
BaseColumns._ID
是最佳选择,因为将提供程序查询的结果链接到 ListView
的条件是,检索到的其中一个列的名称必须是 _ID
;ContentResolver
文件方法来访问数据;您也可以使用 BLOB 来实现独立于架构的表。在这类表中,您需要以 BLOB 形式定义一个主键列、一个 MIME 类型列以及一个或多个通用列。 这些 BLOB 列中数据的含义通过 MIME 类型列中的值指示。 这样一来,您就可以在同一个表中存储不同类型的行。 举例来说,联系人提供程序的“数据”表 ContactsContract.Data
便是一个独立于架构的表。
内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其授权)和一个指向表或文件的名称(路径)。 可选 ID 部分指向表中的单个行。 ContentProvider
的每一个数据访问方法都将内容 URI 作为参数;您可以利用这一点确定要访问的表、行或文件。
内容提供程序基础知识主题中描述了内容 URI 的基础知识。
提供程序通常具有单一授权,该授权充当其 Android 内部名称。为避免与其他提供程序发生冲突,您应该使用互联网网域所有权(反向)作为提供程序授权的基础。 由于此建议也适用于 Android 软件包名称,因此您可以将提供程序授权定义为包含该提供程序的软件包名称的扩展名。 例如,如果您的 Android 软件包名称为 com.example.
,则应为提供程序提供 com.example.
授权。
开发者通常通过追加指向单个表的路径来根据权限创建内容 URI。 例如,如果您有两个表:table1 和 table2,则可以通过合并上一示例中的权限来生成 内容 URI com.example.
和 com.example.
。路径并不限定于单个段,也无需为每一级路径都创建一个表。
按照惯例,提供程序通过接受末尾具有行所对应 ID 值的内容 URI 来提供对表中单个行的访问。 同样按照惯例,提供程序会将该 ID 值与表的 _ID
列进行匹配,并对匹配的行执行请求的访问。
这一惯例为访问提供程序的应用的常见设计模式提供了便利。应用会对提供程序执行查询,并使用 CursorAdapter
以 ListView
显示生成的 Cursor
。 定义 CursorAdapter
的条件是, Cursor
中的其中一个列必须是 _ID
用户随后从 UI 上显示的行中选取其中一行,以查看或修改数据。 应用会从支持 ListView
的 Cursor
中获取对应行,获取该行的 _ID
值,将其追加到内容 URI,然后向提供程序发送访问请求。 然后,提供程序便可对用户选取的特定行执行查询或修改。
为帮助您选择对传入的内容 URI 执行的操作,提供程序 API 加入了实用类 UriMatcher
,它会将内容 URI“模式”映射到整型值。 您可以在一个 switch
语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 选择所需操作。
内容 URI 模式使用通配符匹配内容 URI:
*
:匹配由任意长度的任何有效字符组成的字符串#
:匹配由任意长度的数字字符组成的字符串以设计和编码内容 URI 处理为例,假设一个具有授权 com.example.app.provider
的提供程序能识别以下指向表的内容 URI:
content://com.example.app.provider/table1
:一个名为 table1
的表content://com.example.app.provider/table2/dataset1
:一个名为 dataset1
的表content://com.example.app.provider/table2/dataset2
:一个名为 dataset2
的表content://com.example.app.provider/table3
:一个名为 table3
的表提供程序也能识别追加了行 ID 的内容 URI,例如,content://com.example.app.provider/table3/1
对应由 table3
中 1
标识的行的内容 URI。
可以使用以下内容 URI 模式:
content://com.example.app.provider/*
content://com.example.app.provider/table2/*
:
dataset1
和表
dataset2
的内容 URI,但不匹配
table1
或
table3
的内容 URI。
content://com.example.app.provider/table3/#
:匹配
table3
中单个行的内容 URI,如
content://com.example.app.provider/table3/6
对应由
6
标识的行的内容 URI。
以下代码段演示了 UriMatcher
中方法的工作方式。 此代码采用不同方式处理整个表的 URI 与单个行的 URI,它为表使用的内容 URI 模式是content://
,为单个行使用的内容 URI 模式则是 content://
。
方法 addURI()
会将授权和路径映射到一个整型值。 方法 match()
会返回 URI 的整型值。switch
语句会在查询整个表与查询单个记录之间进行选择:
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn't. */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); } ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (sUriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // If the URI is not recognized, you should do some error handling here. } // call the code to actually do the query }
另一个类 ContentUris
会提供一些工具方法,用于处理内容 URI 的 id
部分。Uri
类和 Uri.Builder
类包括一些工具方法,用于解析现有 Uri
对象和构建新对象。
ContentProvider
实例通过处理来自其他应用的请求来管理对结构化数据集的访问。 所有形式的访问最终都会调用 ContentResolver
,后者接着调用ContentProvider
的具体方法来获取访问权限。
抽象类 ContentProvider
定义了六个抽象方法,您必须将这些方法作为自己具体子类的一部分加以实现。 所有这些方法(onCreate()
除外)都由一个尝试访问您的内容提供程序的客户端应用调用:
query()
Cursor
对象返回。
insert()
update()
delete()
getType()
onCreate()
ContentResolver
对象尝试访问您的提供程序时,系统才会创建它。
请注意,这些方法的签名与同名的 ContentResolver
方法相同。
您在实现这些方法时应考虑以下事项:
onCreate()
除外)都可由多个线程同时调用,因此它们必须是线程安全方法。如需了解有关多个线程的更多信息,请参阅进程和线程主题;onCreate()
中执行长时间操作。将初始化任务推迟到实际需要时进行。 实现 onCreate() 方法部分对此做了更详尽的描述;insert()
调用并返回 0。ContentProvider.query()
方法必须返回 Cursor
对象。如果失败,则会引发 Exception
。 如果您使用 SQLite 数据库作为数据存储,则只需返回由 SQLiteDatabase
类的其中一个 query()
方法返回的 Cursor
。 如果查询不匹配任何行,您应该返回一个 Cursor
实例(其 getCount()
方法返回 0)。只有当查询过程中出现内部错误时,您才应该返回 null
。
如果您不使用 SQLite 数据库作为数据存储,请使用 Cursor
的其中一个具体子类。 例如,在 MatrixCursor
类实现的游标中,每一行都是一个 Object
数组。 对于此类,请使用 addRow()
来添加新行。
请记住,Android 系统必须能够跨进程边界传播 Exception
。 Android 可以为以下异常执行此操作,这些异常可能有助于处理查询错误:
IllegalArgumentException
(您可以选择在提供程序收到无效的内容 URI 时引发此异常)NullPointerException
insert()
方法会使用 ContentValues
参数中的值向相应表中添加新行。 如果 ContentValues
参数中未包含列名称,您可能想在您的提供程序代码或数据库架构中提供其默认值。
此方法应该返回新行的内容 URI。要想构建此方法,请使用 withAppendedId()
向表的内容 URI 追加新行的 _ID
(或其他主键)值。
delete()
方法不需要从您的数据存储中实际删除行。 如果您将同步适配器与提供程序一起使用,应该考虑为已删除的行添加“删除”标志,而不是将行整个移除。 同步适配器可以检查是否存在已删除的行,并将它们从服务器中移除,然后再将它们从提供程序中删除。
update()
方法采用 insert()
所使用的相同 ContentValues
参数,以及 delete()
和 ContentProvider.query()
所使用的相同 selection
和 selectionArgs
参数。 这样一来,您就可以在这些方法之间重复使用代码。
Android 系统会在启动提供程序时调用 onCreate()
。您只应在此方法中执行运行快速的初始化任务,并将数据库创建和数据加载推迟到提供程序实际收到数据请求时进行。 如果您在 onCreate()
中执行长时间的任务,则会减慢提供程序的启动速度, 进而减慢提供程序对其他应用的响应速度。
例如,如果您使用 SQLite 数据库,可以在 ContentProvider.onCreate()
中创建一个新的 SQLiteOpenHelper
对象,然后在首次打开数据库时创建 SQL 表。 为简化这一过程,在您首次调用 getWritableDatabase()
时,它会自动调用 SQLiteOpenHelper.onCreate()
方法。
以下两个代码段展示了 ContentProvider.onCreate()
与 SQLiteOpenHelper.onCreate()
之间的交互。第一个代码段是 ContentProvider.onCreate()
的实现:
public class ExampleProvider extends ContentProvider /* * Defines a handle to the database helper object. The MainDatabaseHelper class is defined * in a following snippet. */ private MainDatabaseHelper mOpenHelper; // Defines the database name private static final String DBNAME = "mydb"; // Holds the database object private SQLiteDatabase db; public boolean onCreate() { /* * Creates a new helper object. This method always returns quickly. * Notice that the database itself isn't created or opened * until SQLiteOpenHelper.getWritableDatabase is called */ mOpenHelper = new MainDatabaseHelper( getContext(), // the application context DBNAME, // the name of the database) null, // uses the default SQLite cursor 1 // the version number ); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which table to open, handle error-checking, and so forth ... /* * Gets a writeable database. This will trigger its creation if it doesn't already exist. * */ db = mOpenHelper.getWritableDatabase(); } }
下一个代码段是 SQLiteOpenHelper.onCreate()
的实现,其中包括一个帮助程序类:
... // A string that defines the SQL statement for creating a table private static final String SQL_CREATE_MAIN = "CREATE TABLE " + "main " + // Table's name "(" + // The columns in the table " _ID INTEGER PRIMARY KEY, " + " WORD TEXT" " FREQUENCY INTEGER " + " LOCALE TEXT )"; ... /** * Helper class that actually creates and manages the provider's underlying data repository. */ protected static final class MainDatabaseHelper extends SQLiteOpenHelper { /* * Instantiates an open helper for the provider's SQLite data repository * Do not do database creation and upgrade here. */ MainDatabaseHelper(Context context) { super(context, DBNAME, null, 1); } /* * Creates the data repository. This is called when the provider attempts to open the * repository and SQLite reports that it doesn't exist. */ public void onCreate(SQLiteDatabase db) { // Creates the main table db.execSQL(SQL_CREATE_MAIN); } }
ContentProvider
类具有两个返回 MIME 类型的方法:
getType()
getStreamTypes()
getType()
方法会返回一个 MIME 格式的 String
,后者描述内容 URI 参数返回的数据类型。Uri
参数可以是模式,而不是特定 URI;在这种情况下,您应该返回与匹配该模式的内容 URI 关联的数据类型。
对于文本、HTML 或 JPEG 等常见数据类型,getType()
应该为该数据返回标准 MIME 类型。 IANA MIME Media Types 网站上提供了这些标准类型的完整列表。
对于指向一个或多个表数据行的内容 URI,getType()
应该以 Android 供应商特有 MIME 格式返回 MIME 类型:
vnd
android.cursor.item/
android.cursor.dir/
vnd.
.
您提供
和
。
值应具有全局唯一性,
值应在对应的 URI 模式中具有唯一性。 适合选择贵公司的名称或您的应用 Android 软件包名称的某个部分作为
。 适合选择 URI 关联表的标识字符串作为
。
例如,如果提供程序的授权是 com.example.app.provider
,并且它公开了一个名为 table1
的表,则 table1
中多个行的 MIME 类型是:
vnd.android.cursor.dir/vnd.com.example.provider.table1
对于 table1
的单个行,MIME 类型是:
vnd.android.cursor.item/vnd.com.example.provider.table1
如果您的提供程序提供文件,请实现 getStreamTypes()
。 该方法会为您的提供程序可以为给定内容 URI 返回的文件返回一个 MIME 类型 String
数组。 您应该通过 MIME 类型过滤器参数过滤您提供的 MIME 类型,以便只返回客户端想处理的那些 MIME 类型。
例如,假设提供程序以 .jpg
、.png
和 .gif
格式文件形式提供照片图像。 如果应用调用 ContentResolver.getStreamTypes()
时使用了过滤器字符串 image/*
(任何是“图像”的内容),则 ContentProvider.getStreamTypes()
方法应返回数组:
{ "image/jpeg", "image/png", "image/gif"}
如果应用只对 .jpg
文件感兴趣,则可以在调用 ContentResolver.getStreamTypes()
时使用过滤器字符串 *\/jpeg
,ContentProvider.getStreamTypes()
应返回:
{"image/jpeg"}
如果您的提供程序未提供过滤器字符串中请求的任何 MIME 类型,则 getStreamTypes()
应返回 null
。
协定类是一种 public final
类,其中包含对 URI、列名称、MIME 类型以及其他与提供程序有关的元数据的常量定义。 该类可确保即使 URI、列名称等数据的实际值发生变化,也可以正确访问提供程序,从而在提供程序与其他应用之间建立协定。
协定类对开发者也有帮助,因为其常量通常采用助记名称,因此可以降低开发者为列名称或 URI 使用错误值的可能性。 由于它是一种类,因此可以包含 Javadoc 文档。 集成开发环境(如 Android Studio)可以根据协定类自动完成常量名称,并为常量显示 Javadoc。
开发者无法从您的应用访问协定类的类文件,但他们可以通过您提供的 .jar
文件将其静态编译到其应用内。
举例来说,ContactsContract
类及其嵌套类便属于协定类。
安全与权限主题中详细描述了 Android 系统各个方面的权限和访问。 数据存储主题也描述了各类存储实行中的安全与权限。 其中的要点简述如下:
SQLiteDatabase
数据库是您的应用和提供程序的私有数据库;如果您想使用内容提供程序权限来控制对数据的访问,则应将数据存储在内部文件、SQLite 数据库或“云”中(例如,远程服务器上),而且您应该保持文件和数据库为您的应用所私有。
即使底层数据为私有数据,所有应用仍可从您的提供程序读取数据或向其写入数据,因为在默认情况下,您的提供程序未设置权限。 要想改变这种情况,请使用属性或
元素的子元素在您的清单文件中为您的提供程序设置权限。 您可以设置适用于整个提供程序、特定表甚至特定记录的权限,或者设置同时适用于这三者的权限。
您可以通过清单文件中的一个或多个
元素为您的提供程序定义权限。要使权限对您的提供程序具有唯一性,请为 android:name
属性使用 Java 风格作用域。 例如,将读取权限命名为 com.example.app.provider.permission.READ_PROVIDER
。
以下列表描述了提供程序权限的作用域,从适用于整个提供程序的权限开始,然后逐渐细化。 更细化的权限优先于作用域较大的权限:
元素的
android:permission
属性指定。
元素的
android:readPermission
属性和
android:writePermission
属性 指定它们。它们优先于
android:permission
所需的权限。
元素的
子元素指定您想控制的每个 URI。 对于您指定的每个内容 URI,您都可以指定读取/写入权限、读取权限或写入权限,或同时指定所有三种权限。 读取权限和写入权限优先于读取/写入权限。 此外,路径级别权限优先于提供程序级别权限。
假设您需要实现电子邮件提供程序和应用的权限,如果您想允许外部图像查看器应用显示您的提供程序中的照片附件, 为了在不请求权限的情况下为图像查看器提供必要的访问权限,可以为照片的内容 URI 设置临时权限。 对您的电子邮件应用进行相应设计,使应用能够在用户想要显示照片时向图像查看器发送一个 Intent,其中包含照片的内容 URI 以及权限标志。 图像查看器可随后查询您的电子邮件提供程序以检索照片,即使查看器不具备对您提供程序的正常读取权限,也不受影响。
要想启用临时权限,请设置
元素的 android:grantUriPermissions
属性,或者向您的
元素添加一个或多个
子元素。如果您使用了临时权限,则每当您从提供程序中移除对某个内容 URI 的支持,并且该内容 URI 关联了临时权限时,都需要调用 Context.revokeUriPermission()
。
该属性的值决定可访问的提供程序范围。 如果该属性设置为 true
,则系统会向整个提供程序授予临时权限,该权限将替代您的提供程序级别或路径级别权限所需的任何其他权限。
如果此标志设置为 false
,则您必须向
元素添加
子元素。每个子元素都指定授予的临时权限所对应的一个或多个内容 URI。
要向应用授予临时访问权限,Intent 必须包含 FLAG_GRANT_READ_URI_PERMISSION
和/或 FLAG_GRANT_WRITE_URI_PERMISSION
标志。 它们通过 setFlags()
方法进行设置。
如果 android:grantUriPermissions
属性不存在,则假设其为 false
。
与 Activity
和 Service
组件类似,必须使用
元素在清单文件中为其应用定义 ContentProvider
的子类。 Android 系统会从该元素获取以下信息:
android:authorities
)
android:name
)
ContentProvider
的类。 实现 ContentProvider 类中对此类做了更详尽的描述。
android:grantUriPermssions
:临时权限标志android:permission
:统一提供程序范围读取/写入权限android:readPermission
:提供程序范围读取权限android:writePermission
:提供程序范围写入权限实现内容提供程序权限部分对权限及其对应属性做了更详尽的描述。
android:enabled
:允许系统启动提供程序的标志。android:exported
:允许其他应用使用此提供程序的标志。android:initOrder
:此提供程序相对于同一进程中其他提供程序的启动顺序。android:multiProcess
:允许系统在与调用客户端相同的进程中启动提供程序的标志。android:process
:应在其中运行提供程序的进程的名称。android:syncable
:指示提供程序的数据将与服务器上的数据同步的标志。开发指南中针对
元素的主题提供了这些属性的完整资料。
android:icon
:包含提供程序图标的可绘制对象资源。 该图标出现在Settings > Apps > All 中应用列表内的提供程序标签旁;android:label
:描述提供程序和/或其数据的信息标签。 该标签出现在Settings > Apps > All中的应用列表内。开发指南中针对
元素的主题提供了这些属性的完整资料。
应用可以通过 Intent
间接访问内容提供程序。 应用不会调用 ContentResolver
或 ContentProvider
的任何方法,它并不会直接提供,而是会发送一个启动某个 Activity 的 Intent,该 Activity 通常是提供程序自身应用的一部分。 目标 Activity 负责检索和显示其 UI 中的数据。 视 Intent 中的操作而定,目标 Activity 可能还会提示用户对提供程序的数据进行修改。 Intent 可能还包含目标 Activity 在 UI 中显示的“extra”数据;用户随后可以选择更改此数据,然后使用它来修改提供程序中的数据。
您可能想使用 Intent 访问权限来帮助确保数据完整性。您的提供程序可能依赖于根据严格定义的业务逻辑插入、更新和删除数据。 如果是这种情况,则允许其他应用直接修改您的数据可能会导致无效的数据。 如果您想让开发者使用 Intent 访问权限,请务必为其提供详尽的参考资料。 向他们解释为什么使用自身应用 UI 的 Intent 访问比尝试通过代码修改数据更好。
处理想要修改您的提供程序数据的传入 Intent 与处理其他 Intent 没有区别。 您可以通过阅读 Intent 和 Intent 过滤器主题了解有关 Intent 用法的更多信息。
来自谷歌:https://developer.android.com/guide/topics/providers/content-providers.html