快捷搜索:  汽车  科技

android开发简介(开发中文引导-内容提供者基础)

android开发简介(开发中文引导-内容提供者基础)表1:示例用户字典表。例如,有个安卓系统的内置提供者是用户字典,它保存了用户想要保留的非标准单词的拼写。表1展示了这个提供者的表里的数据可能看起来的样子:用来在内容提供者里插入,更新,删除数据的API。其他便于使用内容提供者的实用的API功能。将数据以一张或多张表的形式,类似于关系数据库里看到的表,呈现给外部应用的内容提供者。一行代表内容提供者所收集数据的某种类型的一个实例,它的每一列代表构成实例的一个单独的数据片段。

内容提供者管理中央数据存储库的访问。内容提供者是安卓应用的一部分,它通常提供自己的界面来处理数据。但是,内容提供者主要旨在用于其他应用,他们使用提供者的客户端对象来访问该提供者。提供者和提供者的客户端一起提供了一个既能处理进程间通信又能处理数据安全访问的一致的,标准的数据接口。

这篇文章主要讲述如下的基础知识:

  • 内容提供者如何工作。

  • 用来从内容提供者获取数据的API。

  • 用来在内容提供者里插入,更新,删除数据的API。

  • 其他便于使用内容提供者的实用的API功能。

概述

将数据以一张或多张表的形式,类似于关系数据库里看到的表,呈现给外部应用的内容提供者。一行代表内容提供者所收集数据的某种类型的一个实例,它的每一列代表构成实例的一个单独的数据片段。

例如,有个安卓系统的内置提供者是用户字典,它保存了用户想要保留的非标准单词的拼写。表1展示了这个提供者的表里的数据可能看起来的样子:

表1:示例用户字典表。

android开发简介(开发中文引导-内容提供者基础)(1)

在表1中,每行都代表了可能不会在词典里的出现的单词的一个实例。每列代表了该词的某些数据,例如第一次遇到该词的语言区域。列标题是在提供者里存储的列名。要引用(1)一行的语言区域,你可以引用它的语言区域列。_ID 列用作提供者的“主键”列,它由提供者自动维护。

注意:提供者不必拥有主键,并且如果已有一个主键,它也不必使用_ID来做为主键的列名。但如果你想要将该提供者的数据绑定到列表视图,这些列名里得有一列是_ID。在显示查询结果小结详细的解释了这个需求。

访问提供者

应用使用ContentResolver客户端对象来访问内容提供者的数据。这个对象有方法调用提供者对象,ContentProvider的具体子类的实例,的同名方法。这些ContentResolver的方法为持久存储提供了基础的"CRUI"功能(创建,获取,更新,和删除)。

客户端应用进程中的ContentResolver对象和拥有该提供者应用中的ContentProvider对象自动处理进程间通信。ContentProvider也用作在它的数据存储库和数据作为表的外部外观之间的抽象层。

注意:你的应用通常要在清单文件中请求特定的权限来访问提供者。在内容提供者权限小节中更加详细的描述了这点。

例如,你可以调用ContentResolver.query来获取一个单词列表和他们的语言区域。这个query方法调用用户字典提供者定义的ContentProvider.query方法。下面的代码行显示了ContentResolver.query调用。

表2 显示了这些query(Uri projection selection selectionArgs sortOrder)参数如何匹配SQL SELECT语句:

表 2: Query 与SQL 查询相比.

android开发简介(开发中文引导-内容提供者基础)(2)

内容URI

内容URI是标识提供者数据的URI。内容URI包括了整个提供者的符号名(它的授权机构)和指向表的名称(路径)。当你调用客户端方法访问提供者的表时,该表的内容URI是其中一个参数。

在前面的代码行中,常量CONTENT_URI包含了用户字典"words"表的内容URI。ContentResolver对象解析出了URI的授权机构,并使用它来“解析”提供者,通过比较该授权机构与已知提供者的系统表。ContentResolver那时可以将查询参数分派给正确的提供者。

ContentProvider使用内容URI的路径部分选择表来访问。提供者通常对它暴露的每张表都有一个路径。

前面的代码张中,"words"表的完整URI是:

那里user_dictionary字符串是提供者的授权机构,words字符串是表的路径。字符串 content://(协议)总会出现,并且标识出这是个内容URI。

许多提供者通过在URI结尾附加一个ID值来让你可以访问表中单独的行。例如,要从字典中获得ID为4的行,你可以使用这个内容URI:

当已经获得一组行然后要更新或删除其中之一的时候,你通常要使用ID值。

注意:Uri和Uri.Builder类包含了用于从字符串构建格式良好的Uri对象的便利方法。ContentUris包含了用于附加id值到URI的便利方法。前面的代码片段使用了withAppendedId来将id附加到了UserDictionary内容URI上。

从提供者获取数据

本节使用用户字典提供者作为示例,来介绍了如何从提供者获取数据。

为了清晰起见,本节的代码片段在“UI线程”中调用了ContentResolver.query。但是,在实际的代码中你应该在独立的线程异步地执行查询。有一种方法是使用CursorLoader类,在加载器引导中会详细的描述它。代码行也只是片段;他们没有展示完整的应用。

要从提供者获取数据,遵循这几个基本的步骤:

请求读取访问权限

要从提供者获取数据,你的应用需要对提供者的“读取访问权限”。你不能在运行时请求这个权限;你换而得在清单中指明说你需要这个权限,通过使用<uses-permission>元素和由提供者明确定义的权限名称。当你在清单中指定这个元素的时侯,你实际上是在为你的应用“请求”这个权限。当用户安装你的应用时,他们暗含地授权了这个请求。

要找到你要为提供者使用的读取访问权限的准确名称,和其他提供者使用的访问权限名称,请参考提供者的文档。

权限在访问提供者中的作用,在内容提供者权限小节中有更加详细的描述。

用户字典在清单文件中定义了permission android.permission.READ_USER_DICTIONARY,因此想要从提供者中读取数据的应用必须请求这个权限。

构建查询

获取数据的下一步要构建一个查询。第一段代码定义了用于访问用户字典提供者的一些变量:

下段代码展示了如何使用ContentResolver.query,以用户字典提供者为例。提供者客户端的查询类似于SQL的查询,并且它包括了一组列,一组选择条件和一种排列顺序。

查询应该返回的这组列叫做映射(mProjection变量)。

详细描述所要获取的行的表达式被分成选择从句和选择参数。选择从句是逻辑和布尔表达式,列名和值(mSelectionClause变量)的组合。如果指定了可替换参数'?'代替值(译者注:这里是指将替换符放到'='后,替换具体列,后面会有详述。),查询方法将从选择参数数组获取值(mSelectionArgs变量)。

在下段代码中,如果用户没有输入一个单词,选择从句被设置为空,那么该查询会返回提供者的所有单词。如果用户输入了一个单词,那么选择从句设置为UserDictionary.Words.WORD " = ?",然后选择参数数组的第一个元素设置为用户输入的单词。

该查询类似于SQL语句:

在这个SQL语句中,使用了实际的列名来替换了契约类常量。

防止恶意输入

如果由内容提供者管理的数据是在SQL数据库中,将外部不可信任数据包含进原始的SQL语句可能会导致SQL注入。

思考一下这个选择从句:

如果你这么做,那么你正允许用户将恶意SQL连接到你的SQL语句。例如,用户可以为mUserInput输入"nothing; DROP TABLE *;"。它会导致选择从句 var = nothing; DROP TABLE *;。由于选择从句被认为是SQL语句,这会导致提供者删除所有基础SQLite数据库中的表(除非提供者设置为捕获SQL注入尝试)。

为了避免这个问题,要使用一个将?作为可替换参数和一组独立的选择参数数组的选择从句。当你这么做的时候,用户的输入被直接结合到了查询中而不是解释为SQL语句。因为它不被视为SQL语句,用户输入不能注入恶意SQL语句。没有使用连接到语句来包含用户输入,而是使用这个选择从句:

像这样建立一个选择参数数组:

像这样为选择参数数组放入一个值:

使用?作为可替换参数和选择参数数组的选择从句是指定一个选择的优选方式,即便提供者不是基于SQL数据库。

显示查询结果

ContentResolver.query客户端方法总会返回一个游标,它包含了符合查询条件的行中由查询映射指定的列。游标对象提供了对它所包含数据的随机读取访问功能。你可以使用游标的方法遍历结果中的所有行,决定每列的数据类型,获取一列的数据,和结果的其他属性。有些游标实现了当提供者数据改变时自动更新该对象,或当游标改变时触发一个观察者对象的方法,或兼而有之。

注意:提供者可能会根据执行该查询的对象种类限制对列的访问。例如,联系人提供者限制一些异步适配器对某些列的访问,因此,它不会将他们返回给活动或服务。

如果没有行符合选择条件,提供者返回一个Cursor.getCount为0的游标对象(空游标)。

如果有内部错误发生,查询结果取决于特定的提供者。它可能会返回空,或抛出一个异常。

由于游标是行的“列表”,显示游标内容的一种好的方式是通过SimpleCursorAdapter来将它连接到ListView。

下面的代码片段继续之前片段的代码。它创建了一个包含查询获取的游标的SimpleCursorAdapter对象,将它设置为ListView的适配器:

注意:要用游标支持ListView,该游标必须包含叫_ID的列。因为这点,之前显示的查询要获取"words"表中的_ID列,即便ListView不显示它。这个限制也解释了为什么大多提供者要为他们的每张表拥有一个_ID列。

从查询结果获取数据

你可以将它们用于其他的任务,而不是简单的显示查询结果。例如,你可以从用户字典获得拼写然后在其他的提供者中查阅。为了做到这点,你要遍历游标中的所有行:

游标的实现中包含了几个从该对象获取不同类型数据的“get”方法。例如,之前的代码片段中使用了getString。他们也有返回指明该列数据类型值的getType方法。

内容提供者权限

提供者的应用可以指定应用必须拥有用以访问提供者数据的权限。这些权限确保用户会了解应用将视图访问什么数据。其他应用根据提供者的需求来请求他们要访问该提供者所需的权限。最终用户会在他们安装应用时看到请求的权限。

如果提供者的应用不指定任何权限,那么其他应用无法访问该提供者数据。但是,该提供者应用中的组件总会有完全的读写访问权,无论指定了什么权限。

正如之前指出的,用户字典提供者需要android.permission.READ_USER_DICTIONARY权限来从中获取数据。提供者拥有独立的权限来插入,更新,删除数据。

为了获得访问提供者的权限,应用使用清单文件中的<uses-permission>元素来请求他们。安卓包管理器安装应用时,用户必须同意所有应用请求的权限。如果用户同意全部请求,包管理器继续安装;如果用户没有同意他们,包管理器会终止安装。

下面的<uses-permission>元素请求用户字典提供者的读取访问权:

权限对提供者访问的影响在安全和权限向导中有更详细的解释。

插入,更新,删除数据

与从提供者获取数据方式相同,你也可以使用提供者客户端和提供者ContentProvider的这种交互方式来修改数据。你要调用ContentResolver的一种方法,带有会传给ContentProvider相应方法的参数。提供者和提供者客户端会自动处理安全问题和进程间通信。

插入数据

你要调用ContentResolver.insert方法来向提供者插入数据。这个方法向提供者插入一个新行然后那行数据的内容URI。这段代码展示了如何向用户字典提供者插入一个新的单词:

这行新数据放入一个单独的ContentValues对象,这类似于单行游标的形式。该对象的列不需要有与原数据有相同的数据类型,你可以使用ContentValues.putNull将某列设为空。

这段代码么有添加_ID列,因为这列是自动维护的。提供者为新加入的每行分配一个唯一的值。提供者通常使用这个值作为表的主键。

newUri变量返回的内容URI以如下格式标识新加入的行:

<id_value>是新行里_ID的内容。大多提供者可以自动检测这种形式的内容URI,然后对那特定的行执行请求的操作。

调用ContentUris.parseId来从返回的Uri获得_ID的值。

更新数据

要更新一行数据,你可以使用带有已更新值的ContentValues对象就像执行插入时做的那样,而选择条件就如执行查询时做的那样。你要使用的客户端方法则是ContentResolver.update。你唯一需要做的就是将要更新的列值加入到ContentValues对象中。如果你想清空一列的值,可以将值设为空。

下段代码将所有语言区域为"en"的行改为语言区域为空的行。返回值是已更新行的数量:

当你调用ContentResolver.update的时候还应该对用户输入进行安全处理。要了解更多信息,请阅读防止恶意输入小节。

删除数据

删除行类似于获取行数据:你要为想要删除的行指定选择条件和返回已删除行数的客户端方法。下面的代码片段删除了应用ID为“user”的行。这个方法返回了已删除的行数。

当你调用ContentResolver.delete的时候还应该对用户输入进行安全处理。了解更多信息,请阅读防止恶意输入小节。

提供者数据类型

内容提供者可以提供许多不同的数据类型。用户字典提供者仅提供文本,但是提供者还可以提供如下格式:

  • integer(整数)

  • long integer (long)(长整形:这里的括号是两种叫法的意思)

  • floating point(浮点数)

  • long floating point (double)(长浮点数或双精度数)

另一个提供者经常使用的数据类型是二进制大对象(BLOB),实现为64kb字节的数组。你可以通过查看游标类的"get"方法来看到有效的数据类型。

提供者每列的数据类型通常会在他们的文档列出。用户字典提供者的数据类型在它的契约类Dictionary.Words的参考文档中列出(契约类在契约类小结描述)。 你也可以通过调用Cursor.getType确定数据类型。

提供者也为他们定义的每个内容URI维护MIME数据类型。你可以使用MIME数据类型来确定你的应用是否可以处理提供者提供的数据,或选择基于该MIME类型的一类处理。当正使用包含复杂数据或文件的时候,你通常需要MIME类型。例如,联系人提供者的ContactsContract.Data表使用MIME来标出存储在每行的联系人数据的类型。调用ContentResolver.getType来获得内容URI相应的MIME类型。

MIME类型参考小节描述了标准和自定义MIME类型的语法。

访问提供者的可选形式

应用开发中有3种可以选择的重要形式:

批量访问和通过意图修改在下面的小节中描述。

批量访问

对提供者的批量访问有助于插入大量的行,或在同一个方法中在多张表插入行,或者一般情况下作为事物(一个原子操作)跨进程执行一组操作。

要以“批量模式”访问提供者时,你要创建一个ContentProviderOperation对象的数组,然后用ContentResolver.applyBatch将他们发送给内容提供者。你要将内容提供者的授权机构传给这个方法,而不是特定的内容URI。这允许数组中的每个ContentProviderOperation对象来对不同的表执行操作。ContentResolver.applyBatch的调用会返回一组结果。

ContactsContract.RawContacts契约类的描述包含了展示批量插入的代码片段。联系人管理器示例应用在它的ContactAdder.java文件中包含了批量访问的例子。

通过意图访问数据

意图可以提供对内容提供者的直接访问。你可以让用户可以访问提供者的数据即便你的应用没有访问权限,既可以通过从拥有权限的应用取回一个结果意图,也可以通过激活一个拥有权限的应用然后让用户在里面操作。

获取访问的临时权限

即便你没有适当的访问权限也可以访问内容提供者的数据,通过发送一个意图给拥有权限的应用然后收回一个带有"URI"权限的结果意图。这些是特定内容URI的权限,他们会持续到接收他们的活动完成为止。 拥有持久权限的应用通过在结果意图中设置标记来授予临时权限:

注意:这些标记不会给予对该内容URI所包含授权机构的提供者的通用读写权。这个访问权仅是对这个URI本身有效。

提供者使用<provider>元素的android:grantUriPermission属性和<provider>元素的<grant-uri-permission>子元素来在清单中为该内容URI定义URI权限。这种URI权限机制会在安全和权限引导,“URI权限”中更加详细地解释。

例如,即便你没有READ_CONTACTS权限,你也可以在联系人提供者中获取联系人数据。你可能会想在,一个在他的或她的生日发送电子邮件祝贺的应用中,这么做。不去使用READ_CONTACTS权限来访问所有用户的联系人和他们的信息,你会更愿意让用户来控制你的应用使用那些联系人。你要使用如下过程,来做到这点:

  1. 你的应用要使用startActivityForResult方法来发送包含ACTION_PICK操作和“联系人”MIME类型CONTENT_ITEM_TYPE的意图。

  2. 因为这个意图匹配了联系人应用的“选择”活动的过滤器,这个活动会来到前台。

  3. 用户在选择活动中选择一个联系人更新。当这种情况发生时,选择活动调用setResult(resultcode intent)来创建一个意图还给你的应用。这个意图包括了用户选择联系人的内容URI和“额外的”FLAG_GRANT_READ_URI_PERMISSION标记。这些标记通过内容URI来授予你的应用读取指定联系人的数据的权限。那时选择活动调用finish返回到你的应用来控制。

  4. 你的活动回到前台,然后系统调用活动的onActivityResult方法。这个方法接收由联系人应用选择活动创建的结果意图。

  5. 你可以用结果意图中的内容URI来从联系人提供者中读取联系人数据,即便你没有在清单中声明请求该提供者的持久读取访问权限。那时你可以获得联系人的生日信息或他或她的电子邮件地址,并在那时候发送祝贺电子邮件。

使用其他应用

一个让用户可以修改你没有权限访问的数据的方法是激活一个拥有权限的应用并让用户来在此做这件事。

例如,日历应用接受ACTION_INSERT意图,它让你可以激活该应用的插入界面。你可以将“extras”数据传给这个意图,该应用用它来提前填充界面。因为循环事件有复杂的语法,向日历提供者插入事件更好的方式是激活带有ACTION_INSERT操作的应用,然后让用户在这里插入事件。

使用一个帮助应用显示数据

如果你的应用确实有访问权限,你仍可能想要使用意图在其他的应用中显示数据。例如,日历应用接受ACTION_VIEW意图,它显示一个特定的日期或事件。这让你可以显示日历信息而不必创建自己的界面。要了解这个功能的更多信息,参阅日历提供者向导。

发送意图的应用不必成为与提供者关联的应用。例如,你可以从联系人提供者获取一个联系人,然后发送一个包含联系人图像的内容URI的ACTION_VIEW意图给图像浏览器。

契约类

契约类定义了帮助应用使用内容URI,列名,意图操作,和其他内容提供者功能的常量。契约类不会自动与提供者一起被包含进来;提供者的开发者需要自己定义它们,然后提供给其他开发者使用。安卓平台包含的许多提供者在android.provider包中都有相应的契约类。

例如,用户字典提供者有一个包含内容URI和列名常量的契约类UserDictionary。"words"表的内容URI定义在UserDictionary.Words.CONTENT_URI常量中。UserDictionary.Words也包含了列名常量,它们被用在本引导的代码片段中。例如,查询映射可以定义为:

另一个契约类是联系人提供者的ContactsContract。这个类的参考文档中包含了示例代码片段。它的子类之一,ContactsContract.Intents.Insert,是一个包含意图和意图数据常量的契约类。

MIME类型引用

内容提供者可以返回标准MIME媒体类型,或自定义MIME类型字符串,或兼而有之。

MIME类型有这样的格式

例如,熟知的MIME类型text/html有文本类型和html子类型。如果提供者返回这个类型的URI,意味着使用那个URI的查询会返回包含HTML标签的文本。

自定义MIME类型字符串,也叫“厂商特定”MIME类型,拥有更复杂的类型和子类型值。类型值总是

用于多行,或者

用于单行。

子类型是提供者特定的。安卓系统内建的提供者通常拥有一个简单的子类型。例如,当联系人应用给一个电话号码创建一行时,它会在这行设置如下MIME类型:

注意子类型值是简单的phone_v2。

其他的提供者开发者可以根据提供者的授权机构和表明来创建自己的子类型模式。例如,设想一下包含火车时刻表的提供者。该提供者的授权机构是com.example.trans,它包含了表Line1,Line2,Line3。针对这种情况的内容URI

提供者为表Line1返回这个MIME类型

相应的内容URI

用于表Line2第5行,提供者返回MIME类型

大多内容提供者会为他们使用的MIME类型定义契约类。例如,联系人提供者的契约类ContactsContract.RawContacts为原始的联系人的行数据的MIME类型定义了CONTENT_ITEM_TYPE类型。

单行数据的内容URI在内容URI小节中描述。

译者注:这里不支持代码,所以没有显示出来。

百度首发地址:《android中文开发向导》

猜您喜欢: