Content Providers

Content provider存储和检索数据并使得所有应用程序可以轻松的使用数据。这是应用程序之间分享数据的唯一途径;没有普通的存储区使得所有Android包可以通过。

Android使用很多content provider来运送普通数据类型(audio, video, images,个人通讯信息等)。你可从android.provider包中找到它们。你可以通过这些provider来查询它们包含的数据(但是,必须获得读取这些数据的合适的权限)。

如果你想公开自己的数据,有两种选择:你可以创建自己的content provider(一个ContentProvider的子类)或着你可以将自己的数据添加到一个存在的provider---provider控制相同类型的数据并且你有权限来使用。

该文档介绍如何使用content provider,在简要的讨论后将探究如何查询content provider,如何修改由provider控制的数据以及如何创建一个自己的content provider

Content Provider基本要素

一个content provider底层如何存储数据取决于它的设计。但是所有content provider实现了一套公共的接口来查询和返回结果---添加,修改和删除数据也是如此。

Content provider是一个用户间接使用的接口,大多数通过ContentResolver使用。你可以调用Activity或其他组件中实现的getContentResolver()方法来获得一个ContentResolver

ContentResolver cr = getContentResolver();

然后你可以使用ContentResolver的方法来与你感兴趣的content provider交互。

当一个查询初始化时,Android系统识别查询的目标content provider并确保它被启动并运行。系统会实例化所拥有ContentProvider对象;你不需要自己做这些。事实上你永远不会直接处理ContentProvider对象。通常,每种数据类型的ContentProvider对象只有一个实例。但是可以与不同程序或进程中的多个ContentProvider交互。进程之间的相互作用是通过ContentProviderContentResolver类处理。

数据模型:

Content provider以数据库模式的简单的表格形式公布它的数据,表格的每一行是一条记录,每一列代表一种类型或意义的数据。比如说,一个人的信息以及它的电话号码可以如下表示:

_ID

NUMBER

NUMBER_KEY

LABEL

NAME

TYPE

13

(425) 555 6677

425 555 6677

Kirkland office

Bully Pulpit

TYPE_WORK

44

(212) 555-1234

212 555 1234

NY apartment

Alan Vain

TYPE_HOME

45

(212) 555-6657

212 555 6657

Downtown office

Alan Vain

TYPE_MOBILE

53

201.555.4433

201 555 4433

Love Nest

Rex Cars

TYPE_HOME

每条记录包含一个数字_ID字段在这个表中标识这条记录。ID可以用来在相关的表中匹配记录----比如说,为了在一个表中找到一个人的号码并在另一个表中找到他的图像。

查询返回一个Cursor对象,它可以从一条记录移动到另一条记录以及从一列移动到另一列来读取每个字段的内容。它有专门的方法来读取不同类型的数据。所以在读一个字段时,你必须知道该字段包含的数据的类型。

URI

每个content provider公开一个公共的URI(由Uri对象封装),该URI可以唯一的标识它的数据集合。一个控制多个数据集的content provider为每一个数据集合公开一个URI。所有providerURI都以"content://"字符串开头。这个content: scheme识别被content provider控制的数据。

如果你定义一个content provider,也最好为它的URI定义一个常量来简化客户端代码。Android为所有来自平台的provider声明了CONTENT_URI常量。比如说,匹配电话号码和用户的表格的URI以及存储用户图片的URI(都是由Contacts content provider控制)如下:

android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI

URI常量用于所有与content provider的交互。每个ContentResolver的方法将URI作为第一个参数。它是标识哪个provider可以对话以及provider的哪个表格是目标。

查询 Content Provider

查询一个content provider你需要3个信息:

  • 标识providerURI
  • 要检索的数据字段的名称
  • 这些字段的数据类型

如果你要查询一个指定的记录,你需要这个记录的ID

执行查询:

你可以使用ContentResolver.query()Activity.mangedQuery()方法来查询一个content provider。两个方法都有相同的形参且都返回一个Cursor对象。然而mangedQuery()方法会导致activity控制Cursor的生命周期。一个被控制的Cursor处理所有细节,如当activity pause时卸载自己,当activity restart时重新查询。你可以调用Activity.startManagingCursor()activity来控制一个未被控制的Cursor

query()mangedQuery()的第一个参数是providerURI----标识特定ContentProvider和数据集合的CONTENT_URI常量。

为了将查询限制在一条记录,你可以将哪个记录的_ID值添加到URI,比如,如果ID23,那么URI应该是:

content://. . . ./23

有一些辅助方法,特别是ContentUris.withAppendedId()Uri.withAppendedPath()可以轻松将ID添加到URI。这些方法都是静态的,返回添加了IDUri对象。所以,如果你想在用户联系数据库查询一个ID23的记录,你可以建立一个如下的查询:

import android.provider.Contacts.People; import android.content.ContentUris; import android.net.Uri; import android.database.Cursor; // Use the ContentUris method to produce the base URI for the contact with _ID == 23. Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23); // Alternatively, use the Uri method to produce the base URI. // It takes a string rather than an integer. Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23"); // Then query for this specific record: Cursor cur = managedQuery(myPerson, null, null, null, null);

query()mangedQuery()的其它参数来限定查询的细节,它们是:

  • 应该被return的数据列的名称。null值将返回所有列。否则,只有被列举了名称的列才被返回。所有来自于平台的content provider都为其列声明了一个常量。比如:android.provider.Contacts.Phones 类为电话表的类的名称声明的常量有:_ID,NUMBER,NUMBER_KEY,NAME, 等。
  • 一个决定哪行被返回的flter。它是用SQLWHERE从句的格式,null值将返回所有的行(除非URI限制只查询一条记录)。
  • 可供选择的形参
  • 返回的行的排列顺序,它是用SQLORDER BY从句的格式(不包含ORDER BY)。Null将按照其在表中的默认顺序返回。

下面是一个查询联系名列表和主要电话号码的例子:

import android.provider.Contacts.People; import android.database.Cursor; // 在一个数组中指定那一列要返回 String[] projection = new String[] { People._ID, People._COUNT, People.NAME, People.NUMBER }; // 获得Contacts content provider中的People表格的base URI. Uri contacts = People.CONTENT_URI; // 执行查询. Cursor managedCursor = managedQuery(contacts, projection, // 返回哪一列 null, // 返回哪一行 (null:所有行) null, // 选择参数 (none) // 以name递增的顺序对返回的结果集排序 People.NAME + " ASC");

这个查询从Contacts content provider People表返回数据。它获得联系人的name, 主要 phone number 唯一的record ID_COUNT

在不同的接口声明了列名的常量—在BaseColumns中的_ID_COUNT,PeopleColumns中的NAME和在PhoneColumns中的NUMBERContacts.People类实现了上面的每个接口,所以上面代码中可以使用所有这些名字。

查询返回什么:

查询返回另个或多个数据库字段的集合。每个provider都有_ID以及_COUNT(记录的数量)

上面一个例子的返回结果如下:

_ID

_COUNT

NAME

NUMBER

44

3

Alan Vain

212 555 1234

13

3

Bully Pulpit

425 555 6677

53

3

Rex Cars

201 555 4433

返回数据由Cursor对象公开。它可以在结果集上向前或向后重复。你只能用这个对象来读取数据。

读取检索数据:

查询返回的Cursor对象提供了访问返回的数据集的方法。如果你有一个查询来获得一个由ID指定的记录,返回的集合将只包含一个值。其他情况下将包含多个值。(如果没有匹配,它也可以是空的。)你可以从记录中的特定的字段读取数据,但是你必须知道则这个字段的数据类型,因为Cursor对象用不同的方法来读取不同数据类型---比如getString(),getInt()getFloat()。(然而,对大多数类型,如果你调用读取string的方法,Cursor对象会为你返回这个数据的字符串表示。)Cursor允许你通过列的索引获得列名或者通过列名获得索引。

下面是上例中从查询结果读取namephone number的例子:

import android.provider.Contacts.People; private void getColumnData(Cursor cur){ if (cur.moveToFirst()) { String name; String phoneNumber; int nameColumn = cur.getColumnIndex(People.NAME); int phoneColumn = cur.getColumnIndex(People.NUMBER); String imagePath; do { // Get the field values name = cur.getString(nameColumn); phoneNumber = cur.getString(phoneColumn); // Do something with the values. ... } while (cur.moveToNext()); } }

如果一个查询可以返回二进制数据,比如一幅图像或声音,数据可直接写入表或表的数据可能是一个字符串,该字符串指定的内容是可以获得数据的content:URI。一般而言,小的数据(2050K)常常直接写入表,并可以使用Cursor.getBlob()读取数据。

如果一个表的条目是content:URI,你应该永远不要试图直接打开和读取文件(首先,权限问题可能使其失败),相反的,你应该调用ContentResolver.openInputStream()来获得一个你可以用来读取数据的InputStream对象。

修改数据

Content provider的数据可以被修改:

  • 添加一个新的记录
  • 给存在的记录添加一个新的值
  • 批量修改存在的记录
  • 删除记录

所有的修改操作都是通过ContentResolver的方法完成。比起读数据一些content provider需要更为严格的权限来写数据。如果你没有权限来写入content providerContentResolver的方法将失败。

添加记录:

为了给一个content provider添加一个新的记录,首先用一个ContentValues对象建立一个含有键值对的map,它的每个key匹配在content provider中的一个列的名字,每个value是新的记录在该列的值。然后调用ContentResolver.insert()并将providerURI和这个ContentValues map传递给它。这个方法返回新的记录的全部URI----传递的URI加上新纪录的ID。你可以用这个URI查询并获得一个指向新纪录的Cursor。下面是一个例子:

import android.provider.Contacts.People; import android.content.ContentResolver; import android.content.ContentValues; ContentValues values = new ContentValues(); // Add Abraham Lincoln to contacts and make him a favorite. values.put(People.NAME, "Abraham Lincoln"); // 1 = the new contact is added to favorites // 0 = the new contact is not added to favorites values.put(People.STARRED, 1); Uri uri = getContentResolver().insert(People.CONTENT_URI, values);

添加新的值:

一旦一个记录存在,你就可以向其添加新的信息或者修改存在的信息。比如说,上例的下一步将是向新的条目添加联系信息---如一个电话号码或者一个IM或者是e-mail地址。The best way to add to a record in the Contacts database is to append the name of the table where the new data goes to the URI for the record, then use the amended URI to add the new data values.Contact数据库里添加记录的最好的方法,就是将要添加新数据的表的名字附加在URI上,然后用这个修正的URI来增加新的数据值 每一个联系人表为了这个目的将给出一个作为CONTENT_DIRECTORY常量的名字,继续上面的例子,为新创建的记录添加一个电话号码和e-mail地址。

Uri phoneUri = null; Uri emailUri = null; // 为Abraham Lincoln添加一个电话号码. 先从由insert()返回的新纪录的URI开始,它以新纪录的_ID结尾 //所以我们不需要自己添加ID。 // Then append the designation for the phone table to this URI, // 并用返回的 URI 来插入一个电话号码 phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY); values.clear(); values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE); values.put(People.Phones.NUMBER, "1233214567"); getContentResolver().insert(phoneUri, values); // 用相同的方法添加 email 地址. emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY); values.clear(); // ContactMethods.KIND is used to distinguish different kinds of // contact methods, such as email, IM, etc. values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL); values.put(People.ContactMethods.DATA, "test@example.com"); values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME); getContentResolver().insert(emailUri, values);

你可以调用ContentValues.put()(形参有一个byte数组)将少量的小的二进制数据放入表中。如一个图标式的图像或一小段音频可以用此方式。然而如果你有大量的二进制数据要添加,比如一幅照片或者一首完整的歌,应在表格中放置一个该数据的contentURI并调用ContentResolver.openOutputStream()(形参为文件的URI)。(这将使content provider将数据存入文件并在记录的一个隐藏字段记录文件的路径)。

在这一点,MediaStore content provider(分配image, audio video 数据的主要provider)利用了一个特殊的协议:query()managequery()一起使用来获得二进制数据的元信息(比如,照片的标题或者拍摄的日期等等)的URI,与openInputStream()一起使用就可以来获得它自身的数据。同样的,与insert()一起使用来将元信息存入MediaStore记录的URI,也被用来与openOutputStream()一起来放置那里的二进制数据。下面的程序说明了这个过程:

import android.provider.MediaStore.Images.Media; import android.content.ContentValues; import java.io.OutputStream; // 在 ContentValues map中保存图像的名称和描述 ContentValues values = new ContentValues(3); values.put(Media.DISPLAY_NAME, "road_trip_1"); values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles"); values.put(Media.MIME_TYPE, "image/jpeg"); //添加一个新的记录不是用bitmap,而是用刚才设置的值. // insert() 返回新纪录的 URI. Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values); // 现在获得一个该记录的文件的句柄,将数据存入. // 这里, sourceBitmap 是一个 Bitmap 对象,它代表了要存如数据库的文件. try { OutputStream outStream = getContentResolver().openOutputStream(uri); sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); outStream.close(); } catch (Exception e) { Log.e(TAG, "exception while writing image", e); }

批量修改记录:

要批量修改一组记录的数据(如将所有字段中的”NY ”修改为”New York”),调用函数ContentResolver.update(),传入列以及要修改的值。

删除记录:

要删除一条记录,调用ContentResolver.delete(),并传入指定行的Uri

要删除多行,调用ContentResolver.delete()并传入要删除的这一类记录的Uri(比如android.provider.Contacts.People.CONTENT_URI)和一个SQL WHERE形式的语句来指明哪行要删除。

更多相关文章

  1. Android(安卓)okHttp 实战(三):okHttp网络请求之Json解析
  2. Android应用程序的数据存放目录解说
  3. Android(安卓)retrofit2+OkHttp3的初尝试
  4. Android(安卓)VideoView 循环播放视频
  5. Android(安卓)学习之--HttpClient详细使用
  6. Android(安卓)的用户层 uevent处理机制
  7. Android源代码分析(二) MediaScanner源码分析(上)
  8. Content Prodvider 类----实例:获取通讯录信息
  9. Android知识体系总结之Android部分之Intent篇

随机推荐

  1. Xml SelectNodes 与 XPath
  2. XML 树结构
  3. XML指南——XML CDATA
  4. FusionCharts 3D双柱状图
  5. 四种XML解析方式详解
  6. FusionCharts 2D柱状图和折线图的组合图
  7. XML指南——XML 语法
  8. PHP扩展之XML操作(五)——XMLWriter
  9. FusionCharts 2D柱状图和折线图的组合图
  10. XML指南——XML元素