一个Content Provider的管理器能够访问中央存储库的数据。一个provider是android程序的一部分,该provider通常提供它自己的UI供数据使用。然而,contentprovider最初的目的是被别的app使用client provider object访问数据使用的。Provider和客户端的provider提供一个一致的数据标准接口,该接口还处理进程间通信以及安全的数据访问。
该文档包括下面的要点:
● content provider如何工作
● 从content provider中检索数据需要的API
● 向content provider中插入,修改或删除数据时需要的API
● 别的可以便捷的使用content provider的API
Overview
Contentprovider以类似数据库中的表的形式,对外部的app表现数据。它们可以表现一张或多张表。一行表示provider中存储的一些类型的数据的一个实例,而每一列表示该实例中的单独的数据片段。
例如,在android平台中的多个内置provider中,有一个是用户的词典。该provider中存储了用户希望保存的非标准词语的拼写。下表表明在provider中保存的数据可能的样子:
Table 1
word | app id | frequency | locale | _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 |
在表一中,每一行表示一个可能不会在标准词典中找到的词的实例。每一列代表这个词的一些数据,例如是谁最初碰到这个词的。每列的第一行是保存在provider中列的名字。对该provider而言,_ID这一列在provider中自动作为关键字列作用。
Note:一个provider并不要求拥有一个“关键字”,也不要求使用_ID作为主键的列名。然而,当你希望将provider中的数据绑定到ListView时,其中一个列的名字必须为_ID。主要原因会在下面Displaying query results中解释。
Accessing a provider
一个app通过ContentResolver客户端对象可以从contentprovider中获取数据。该对象有与provider对象相同名字的方法,而provider对象是ContentProvider的子类实例。ContentResolver方法提供基础的“CRUD”(create,retrieve, update and delete)功能维持存储。
ContentResolver对象在客户端app线程中而ContentProvider对象在app中拥有的provider自动处理的进程间通信里。ContentProvider也充当一个在抽象层之间的存储库的数据和实际表现出来的数据的表格。
Note: 为了访问provider,你的app通常需要在manifest文件中请求特殊的许可。在后文中的Content Provider Permissions中会讨论到。
例如,想要从用户的Dictionary Provider中获取一列词以及他们的locales,你需要调用ContentResolver.query().该方法会调用被UserDictionary Provider定义的方法ContentProvider.query()。下面代码显示一个ContentResolver.query()的调用:
//Queries the user dictionary and returns results
mCusor= getContentResolver.query(
UserDictionary.Words.CONTENT_URI,//Thecontent URI of the words table
mProjection, //The columns to return foreach row
mSelectionClause, //Selection criteria
mSelectionArgs, //Selection criteria
mSortOrder); //The sort order for thereturned rows
下表二显示了query方法中的参数与SQL SELECT statement是如何匹配的:
query() argument | SELECT keyword/parameter | Notes |
Uri | FROM table_name | Uri映射provider中名叫table_name的表 |
projection | col,col,col,... | Projection是很多列的集合,它应该包括每一行的检索 |
selection | WHERE col = value | Selection声明从行中获取数据的条件 |
selectionArgs | (No exact equivalent. Selection arguments replace ? placeholders in the selection clause.) |
|
sortOrder | ORDER BY col,col,... | SortOrder声明列在返回cursor的时候以哪个值排序 |
Content URIs
一个Content URI是一个识别provider中数据的URI。ContentURIs包括整个provider(its authority)的符号名(symbolic name)以及指向一张表的名字(a path)。当你调用一个客户端的方法访问一个provider中的一张表,该content URI只这张表的一个参数。
之前的代码中,常量CONTENT_URI包含user dictionary的“words”表的content URI。ContentResolver对象解析出URI的authority,并使用该authority解析该provider——通过比较该authority与系统中已知provider的authority。ContentResolver可以发送查询参数给对应的provider。
ContentProvider使用content URI的path部分来连接正确的table。一个Provider中的每一个表通常都有不同的path。
例如,在前面代码中“words”表完整的URI是:
content://user_dictionary/words
当user_dictionary字符串是provider authority的时候,words字符串就是这个表的path。字符串content://…通常总是显式的,并且被识别为content URI。
许多provider通过在URI末尾添加一个ID值,允许你访问表中一个单独的行。例如,从用户的词典中检索一行_ID是4的数据,你可以如下使用content URI:
Uri singleUri =ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
当你要检索许多行数据,然后对他们进行删除或更新操作的时候,需要经常使用Id值。
Note: Uri类和Uri. Builder类包含可以通过String创建结构精简的Uri对象的便捷的方法。ContentUris包含可以将ID值添加到URI的便捷方法。前面的小片段就使用了withAppendedId()方法将一个id添加到User Dictionary content URI.
Retrieving Data from the Provider
下面的部分描述如何从provider中获取数据,还是使用User Dictionary Provider作为例子。为了达到清晰的目的,下面的代码是在“UI线程”中的ContentResolver.query()方法中运行。在实际的代码中,你应该将query方法运行在别的线程中。方法之一是使用CursorLoader类,这个类在Loader指引中会有更清晰的描述。注意,下面是代码片段,它们并不代表一个完整的应用。
为了从provider中获取数据,要遵从下面的基本步骤:
1. 获取provider的阅读权限
2. 编写可以向provider发送查询语句的代码
Requesting read access permission
为了从provider中获取数据,你的app需要provider提供的“读取许可”。你不可以在运行期间才请求获取该许可,这个许可必须定义在app的manifest文件中。当你在你的manifest文件中用<uses-permission>声明获取provide许可后,你的app就可以访问provider了。当用户安装你的app后,app会隐式的获得provider的许可。
为了获得你要使用的provider的名字从而获取它的读取许可,可以阅读provider的文档查询。在Content Provider Permissions文档中可以查阅更多详细数据。
例如,使用用户词典Provider可以在manifest文件中声明该许可:android.permission.READ_USER_DICTIONARY,即可使用该provider。
Constructing the query
从provider中检索数据的下一步是创建一个query。下面的片段声明了一些访问User Dictionary Provider的变量:
// A “Projection ” defines the columns that willbe returned for each row
String [] mProjection = {
UserDictionary.Words._ID,//Contract class constant for the _ID column name
UserDictionary.Words.WORD,//… for the word column name
UserDictionary.Words.LOCAL//… for the local column name
};
//Defines a string to contain the selectionclause
String mSelectionClause = null;
//Initializes an array to contain selectionarguments
String[] mSelectionArgs= {“”};
接下来代码片段以User Dictionary Provider作为例子展示如何使用ContentResolver.query()。一个provider客户端query与一条SQL query非常相像,并且它包含一个返回队列的集合,一个选择标准的集合以及排列次序。
为了获取返回的队列的集合,query必须调用一个projection(上面的变量mProjection)
表达式声明了需要检索的行,它被分裂成一条选择字句和选择参数。选择子句是逻辑表达式,布尔表达式,column name,和值的混合体。(参数mSelectionClause)。如果你用?代替值,则query方法会从选择参数数组中检索相应的值(参数mSelectionArgs)。
在下一个片段中,如果用户一个词也不输入,则选择子句会被设置为null,而query返回provider中所有的词汇。如果用户输入一个词,选择子句则会设置为UserDictionary.Words.Word+”= ? ”,在选择参数集合中的第一个元素则会被设置为用户键入。
// This defines a one-element String array to containthe selection argument
String [] mSelectionArgs={“”};
//Get a word from the UI
mSearchString = mSearchWord.getText().toString();
//Remember to insert code here to check forinvalid or malicious input
//If the word is the empty string ,gets everything
if (TextUtils.isEmpty(mSearchString)){
//Settingthe selection clause to null will return all words
mSelectionClause= null;
mSelectionArgs[0]=“”;
} else {
//Constructsa selection clause that matches the word that the user entered.
mSelectionClause= UserDictionary.Words.Word + “=?”;
//Movethe user’s input string to the selection arguments.
mSelectionArgs[0]=mSearchString;
}
// Does a query against the table and returns aCursor object
mCursor = getContentResolver.query(
UserDictionary.Words.CONTANT_URI,//The content URI of the word table
mProjection, //Thecolumns to return for each row
mSelectionClause, //Either null, orthe word user entered
mSelectionArgs, //Either empty, orthe string the user entered
mSortOrder); //The sortorder for the returned rows
// Some providers return null if an error occurs,other throw an exception
if (null == mCursor){
//Insert codehere to handle the error. Be sure not to use the cursor! You may //want to callandroid.util.Log.e() to log this error
//ifthe Cursor is empty, the provider found no mathes
} else if (mCursor.getCount()<1){
/*Insertcode here to notify the user that the search was unsuccessful. This isn’t
* necessarilyan error. You may want to offer the user the option to insert a
* new row, orre-type the search term.
*/
} else {
//Insertcode here to do something with the result
}
该query与SQL语法非常类似:
SELECT _ID, word , locale FROM words WHEREword=<userinput> ORDER BY word ASC;
在上面的SQL语法中,使用的是列本身的名字,而非创建一个contract类实例。
Protecting against malicious input
如果被content provider管理的数据是保存在SQL数据库中的,包括外部的不可信的数据为原始的SQL语句都可能导致SQL注入。
考虑下面的selection子句:
//Constructs a selection clause by concatenatingthe user’s input to the column name
String mSelectionClause = “var= ”+mUserInput;
如果你这样做,你就已经允许用户在你的SQL语句中注入非法输入。例如,用户可以输入“nothing;DROP TABLE *;”,这也是mUserInput的值,这样会导致在选择语句中var =nothing; DROPTABLE *; 当该查询子句执行的时候,就可能导致provider清除掉在底层SQLite数据库中的所有的表格。(除非该provider设置为可以捕获SQL注入企图)
为了避免这种情况,可以在选择子句中使用?替代输入的参数,并且将选择参数拆分为数组。如果你这样处理,用户的输入就会直接绑定到query中,而不是直接注入SQL的执行语句作为其一部分。因为query并不能作为SQL一样对待,所以用户的输入并不能向SQL恶意注入。使用下面的选择子句而不是将用户的输入连接在一起:
//Constructs a selection clause with areplaceable 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;
一个选择子句使用?作为替代参数并且使用选择参数数组是一个声明selection语句的好方法,尽管provider并不一定基于SQL数据库。
Displaying query results
ContentResolver.query()客户端方法总是返回一个Cursor,该Cursor包含被query的projection声明的行匹配query的selection选择出来的队列。一个Cursor对象会对它包含的行和列提供随机的阅读访问。使用Cursor方法,你可以迭代结果中所有的行,确定每列的数据类型,从列中取出数据,以及检查结果中的其他性质。一些Cursor实例会在provider的数据改变时自动更新,或会在Cursor或provider改变的时候触发监视对象的方法。
Note: 一个provider可能通过创建query的对象来限制对其列的访问。例如,Contacts Provider限制同步适应器(sync adapter)访问一些列,所以它不会将值返回给任何一个activity或service。
如果没有行匹配选择条件,provider会返回一个Cursor.getCount()值为0的Cursor对象。(就是一个空的Cursor)
如果发生了内部错误,query的结果则依赖于一个特殊的provider。该provider可以选择返回null,也可以抛出Exception。
一旦Cursor是许多行的“list”,通过SimpleCursorAdapter将它们作为一个ListView显示出来是一个很好的方法。
下面的代码片段包含前面展示的代码片段。下面创建了一个SimpleCursorAdapter对象包含通过query检索出来的Cursor对象,同时将该对象设置为Listview的adapter。
//Defines a list of columns to retrieve from theCursor and load into an output row
String[] mWordListColumns= {
UserDictionary.Words.WORD,
//Contractclass constant containing the word column name
UserDictionary.Words.LOCALE
//Contractclass constant containing the locale column name
};
//Defines a list of View IDs that will receivethe Cursor columns for each row
int[] mWordListItems = {R.id.dictWrod,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 inthe ListView
mCursor, //The resultfrom the query
mWordListColumns, //A String array of column names in thecursor
mWordListItem, //An integer array of view IDsin the row layout
0); //Flags(usually none are needed)
//Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
Note:返回一个带有Cursor的ListView,该cursor必须包含一个名为_ID的列。因为query会优先检索“words”表的_ID列,即使ListView并不会显示该列。这样的检索方式解释了为什么大多数provider都会在他们的表中具有_ID列。
Getting data from query results
与其简单的显示query的结果,你还可以将他们使用到别的方面。例如,你可以从用户的词典中检索拼写,然后再用它们检索别的provider。为了达到这个目的,你可以如下遍历Cursor中的每一行:
//Determine the column index of the column named “word”
int index =mCursor.getColumnIndex(UserDictionary.Words.WORD);
//Only executes if the cursor id valid. The UserDictionary Provider returns null if
//an internal error occurs. Other providers maythrow an Exception instead of //returning null;
if (mCursor !=null){
/*Movesto the next row in the cursor. Before the first movement in the cursor,
*the “rowpointer” is -1, and if you try to retrieve data at the position you will
*getan exception
*/
while(mCursor.moveToNext()){
//gets the value from the column
newWord = nCursor.getString(index);
// insert code here to process theretrieved word
…
// end of while loop
}
} else {
//insert codehere to report an error if the cursor is null or the provider threw an//exception
}
Cursor实施包含了许多“get”方法以便检索对象中的不同类型数据。例如,前面的片段使用了getString()方法。Cursor也有getType()方法返回数据列的类型。
Content Provider Permissions
一个provide的应用可以声明许可,别的app想要访问该provide获取数据时需要获得该许可。这些许可保证了用户知道哪些app试图访问provide并获取数据。基于provider的要求,别的app在访问provider时需要发送请求获得许可。用户在安装app的时候会看到要求获得许可的请求。
如果一个provide应用程序并不要求任何许可,那么别的app则不能访问该provider的数据。然而,除了特殊的权限外,在该provider应用程序中的组件(component)是有全部的读写权限的。
正如之前提到的,想要通过用户字典Provider检索其中的数据,需要提供权限 android.permission.READ_USER_DICTIONARY。该provide还有插入,修改以及删除数据的权限要求:android.permission.WRITE_USER_DICTIONARY。
为了获取读写provide的权限,app必须在<user-permission>中声明该权限。当Android Package Manager安装了该应用程序时,用户要同意所有的权限要求才可以安装,如果用户不同意该请求,Package Manager会取消该app的安装。
下面所示就是在<user-permission>中获取用户字典Provider的读取许可:
<user-permission android:name=”android.permission.READ_USER_DICTIONARY”>
关于许可的更多详细信息,参见Security and Permissions指南。
Inserting, Updating, and Deleting Data
同样的方式,你要从provider中检索数据,你也需要利用provider客户端以及provider的ContentProvider的相互作用来修改数据。你可以调用ContentResolver的方法将参数传递给与之相关的ContentProvider。Provider和provider端会自动处理安全域进程间通信。
Inserting data
为了向provider中插入数据,你必须调用ContentResolver.insert()方法。该方法向provider插入一行新数据,并返回该数据的Content URI。下面代码片段展示了如何向User Dictionary Provider插入一个新的单词:
//Defines a new Uri object that receives theresult of the insertion
Uri mNewUri;
…
//Defines an object to contain the new values toinsert
ContentValues mNewValues = new ContentValues();
//Sets the values of each column and inserts theword. 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对象中,该对象与具有单行数据的cursor相似。该对象中的列不需要拥有相同类型的数据,且如果你不想声明任何值,可以使用方法ContentValues.putNull()将列设置为null。
上面的代码片段并没有添加_ID列,这是因为该列会被自动添加上。Provider会给添加的每一行分配独一无二的_ID值。通常,provider也会使用这个值作为表格的私有key。
ContentURI会在newUri中返回一个声明了的新添加的行,格式如下:
content://user_dictionary/words/<id_value>
<id_value>就是新行的_ID内容。大部分Provide都可以通过这个格式自动删除content URI并且在特定的行执行查询操作。
从返回的Uri对象中获取_ID值,只需要调用方法ContentUris.paredId()即可。
Updating Data
为了更新行中的数据,你可以使用带有更新值的ContentValues对象,就像之前做插入操作那样更新数据,且选择条件与之前使用query一致。在客户端上,你可以使用方法ContentResolver.update()。你只需要添加ContentValues对象到你需要更新的列上即可。如果你想清楚列中的内容,将值设置为null即可。
下面的代码片段将每行的locale值从”en”设置为null。返回值则是被更新值的行的数量。
//Defines an object to contain the updated values
ContentValues mUpdateValues = newContentValues();
//Defines selection criteria for the rows youwant to update
String mSelectionClause = userDictionary.Words.LOCALE + “LIKE ?”;
String[] mSelectionArgs = [“en_%”];
//Defines a variable to contain the number ofupdated rows
int mRowsUpdated = 0;
…
//Sets the updated values and updates theselected words.
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
mRowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI, //the user dictionary content URI
mUpdateValues, //thecolumn to update
mSelectionClause, //the column toselect on
mSelectionArgs //the valueto compare to);
在调用ContentResolver.update()方法时你也要对用户输入进行审查。可以阅读文档Protecting against malicious input获取更多信息。
Deleting data
删除行与检索行数据类似:给你想删除的行声明选择条件然后客户端会返回删除的行数。下面的代码片段说明如何删除匹配”user”的行,该方法返回删除的行数。
//Defines selection criteria for the rows youwant to delete
String mSelectionClause =UserDictionary.Words.APP_ID +”LIKE ?”;
String [] mSelectionArgs = {“user”};
//Defines a variable to contain the number ofrows deleted
int mRowsDeleted = 0;
…
//Deletes the words that match the selectioncriteria
mRowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI, //the user dictionary content URI
mSelectionClause, //thecolumn to select on
mSelectionArgs //thevalue to compare to);
在方法ContentResolver.delete()中你同时也需要做用户输入审查。更多关于输入审查的信息可以参考文档Protecting against malicious input.
Provider Data Types
ContentProvider可以提供许多不同类型的数据。UserDictionary Provider只能提供text,但是provider可以提供下面这些格式的数据:
● 整数(integer)
● 长整数(long integer)
● 浮点数 (floating point)
● Double类型 (long floating point)
另外一个provider常用的类型是Binary Large Object(BLOB),通常以64KB字节的数据实现。你可以通过查看Cursor的get方法就知道里面有多少可用类型数据。
Provider中每列殊绝的类型通常会列在provider的文件中。例如User Dictionary Provider的数据类型就在与之相关的文件,该文件的contract class就是UserDictionary.Words。(contract class在文档Contract Class中会有详细介绍)你可以用方法Cursor.getType()决定数据类型。
Provider同时也包含MIME数据类型。你可以使用MIME数据类型,只要你的app可以控制provider提供的MIME数据类型,或者根据MIME的数据类型选择你的设备上的一个app来控制MIME类型数据。当你使用的provider中包含复杂的数据结构或文件的时候,你是需要使用MIME类型的。例如,在ContactsProvider中ContactsContract.Data表格使用MIME类型标记储存在每一行的联系人数据。要获取每一个content URI中的MIME相关数据,可以调用方法ContentResolver.getType()。
章节MIME Type Reference介绍MIME类型的语法和结构。
Alternative Forms of Provider Access
有一下可选择的三个形式在app中访问provider:
● Batch access(批量访问): 你可以通过方法ContentProviderOperation类创建一批访问方法,并用ContentResolver.applyBatch()实施。
● 异步query: 你应该在别的线程中执行query。其中一个方法是使用CursorLoader对象。例子可以参照Loaders文档。
● Data access via intents(通过intent传递数据): 尽管你不能直接通过intent向provider传递数据,你可以发送一个含有数据的intent给provider的应用,该应用是修改provider中数据的最佳工具。
Batch access
如果你需要大量插入行,或者使用同一个方法将行插入多个不同的表中,或普通情况下的跨线程执行一个事务,使用batch access都是最佳选择。
为了在“batch”模式下进入provider,你应该创建ContentProviderOperation对象数组,然后用方法ContentResolver.applyBatch()将它分派到content provider中。你将content provider的权限传递到该方法中而不是将特殊的content URI传递过去。这允许在数据中的每一个ContentProviderOperation对象对不同的表起作用。调用方法ContentResolver.applyBatch()返回一个array结果。
Contract类ContactsContract.RowContacts的描述包括一段能够显示batch插入的代码。在ContactAdder.java源文件中会有Contact管理者程序添加batch访问许可的例子。
Data access via intents
Intent可以提供间接访问content provider的通道。即使是你的app没有获得访问许可,用户也可以访问provider的数据,。或者是通过一个有访问许可的app返回的结果intent,或者是激活一个具有访问权限的app并允许用户通过它来操作。
Displayingdata using a helper app
如果你的app没有访问provider的权限,你可能需要通过一个intent在别的app中展示数据。例如,日历app接收一个ACTION_VIEW的intent,该intent显示特别的日期或事件。这就允许你在不创建自己UI的情况下显示日历信息。可以查看文档Calendar Provider获取更多信息。
你发送intent给的app并不一定需要与相关的provider有关系。例如,你可以从Content Provider中检索contact,然后发送包含了content URI的ACTION_VIEW的intent。
Getting access with temporary permission
如果你的app没有获取访问相应provider的正确权限,你可以通过发送intent到具有相应权限的app并从该app中返回的intent获取包含“URI”的权限。这些为了一个特殊contentURI的权限持续到activity接收后就会终止。App通过在返回的结果intent中设定如下Flag就有永久权限允许这些临时的权限:
Readpermission: FLAG_GRANT_READ_URI_PERMISSION
Writepermission: FLAG_GRANT_WRITE_UTI_PERMISSION
Note: 这些flag并不给予普通的访问provider的读或写的权限(该权限在content URI中)。该access仅仅授权给URI对象。
Provider在其manifest文件中的<provider>元素里使android:grantUriPermission标签就可以为content URI声明URI许可,在<provider>的子元素<grant-uri-permission>中也可以同样声明。URI的许可机制更多详情请见指引Security and Permissions中的“URI Permission”章节。
例如,即使你没有READ_CONTACT许可,你也可以在Contacts Provider中检索一个contact的数据。例如你想要在该app中向你的联系人的生日当天发送简讯。与其要求获得可以获得所有联系信息的许可READ_CONTATCS,你可以让用户选择你的app将使用哪一个联系人的信息。为了达成这个目的,你可以遵循下面的步骤:
1. 你的app使用方法startActivityForResult()发送action为ACTION_PICK以及MIME类型“contacts”为CONTENT_ITEM_TYPE的intent。
2. 因为该intent匹配People app的“selection”activity中的intent过滤器,该activity会出现在前端
3. 在SelectionActivity中,用户选择一个联系人。当这个事件发生后,selection activity会调用setResult(resultcode, intent)方法创建一个intent并返回给你的app。该intent包含用户选择的联系人的content URI,以及“extras”flagFLAG_GRANT_READ_URI_PERMISSION。这些flag授权URI许可给你的app,你的app可以阅读由该content URI指定的联系人数据。Selection activity接下来会调用finish()方法将控制权交回你的app
4. 你的activity返回到前台,然后系统会调用你的activity中的方法onActivityResult()。这个方法会获取到从People app的selection activity创建的resultintent。
5. 通过从结果intent中的content URI,你可以阅读Contacts Provider中的联系人数据,即使你没有在你的manifest文件中要求获取阅读权限。你可以从数据中拿到联系人的生日数据以及她/他的邮件地址,手机号码然后给她/他发送简讯
Using another application
一个简单的方法允许用户修改并没有获得权限的数据的方法是,激活一个有修改数据权限的app并让用户通过有权限的app对数据进行操作。
例如,日历app接收一个ACTION_INSERT的intent,这就意味着它允许你激活并使用它的插入UI。你可以在intent中添加“extra”数据,该数据会被app提前使用到UI上。因为复发性事件会有复杂的语法,所以向Calendar Provider插入数据的最好做法就是使用带有ACTION_INSERT的intent激活Calendar app并允许用户插入数据
Contract Classes
一个contract类声明了常量,该常量可以帮助app作用于contentURI,列名,intent action以及其他content provider的特征。Contract类并不自动包含provider,provider的开法者不得不声明provider并使他们对于别的developer是有效的。在android平台上的许多provider都在包android.privider中并与contract类有关。
例如,用户词典Provider有一个contract类UserDictionary包含contentURI以及联系人列表名。“Word”表的ContentURI被声明在在联系人的UserDictionary.Words.CONTENT_URI。UserDictionary.Words类同样也包含了联系人列名,例如一个query projection可以如下定义:
String[] mProjection={
UserDictionary.Words._ID,
UserDictionary.Words.WORD,
UserDictionary.Words.LOCALE
};
另一个Contacts Provider的contract类是ContactsContract。这个类的其中一个子类,ContactsContract.Intents.Insert,是一个包含了联系人intent和intent数据的contract类。
MIME Type Reference
ContentProvider可以返回标准的MIME媒体类型,或者是自定义的MIME类型字符串,或者两者都有。
MIME类型有如下格式
type/subtype(类型/子类型)
例如,一个已知类型为text/html的MIME类型具有text类型和html子类型。如果provider返回URI类型,这就意味着query使用URI将返回包含HTML标签的text。
普通MIME类型的string,也称为“vendor-specific”MIME类型,有更复杂的类型和子类型。类型值总是:vnd.android.dir;多行数据或单行数据,可能是vnd.android.cursor.item。
子类型是provider特有的。Android内置的provider通常有一个简单的子类型。例如,当Contacts app创造了一行电话号码后,它会如下设置该行的MIME类型:
vnd.android.cursor.item/phone_v2
注意子类型是简单的phone_v2。
别的provider开发者可能基于provider的权限以及表的名字来创建他们自己的子类型。例如,考虑创建一个包含列车时刻表的provider。该provider具有权限com.example.trains,同时它包含了表Line1,Line2和Line3。
其Line1 的content URI是:content://com.example.trains/Line1
provider返回的MIME类型是:vnd.android.cursor.dir/vnd.example.line1
在表Line2中的第五行的content URI是:content://com.example.trains/Line2/5
而provider返回的MIME类型是:vnd.android.item/vnd.example.line2
大部分content provider会为他们要使用的MIME类型声明contract类常量。Contacts Provider的contract类是ContactsContract.RawContacts。例如,为联系人的单独一行原始数据声明的MIME常量为CONTENT_ITEM_TYPE。
ContentURI的单独行声明则在Content URIs章节中有描述。