Android开发指南-框架主题-内容提供器

内容提供器Content Providersjava

内容提供器用来存放和获取数据并使这些数据能够被全部的应用程序访问。它们是应用程序之间共享数据的惟一方法;不存在全部Android软件包都能访问的公共储存区域。android

Android为常见数据类型(音频,视频,图像,我的联系人信息,等等)装载了不少内容提供器。你能够看到在android.provider包里列举了一些。你还能查询这些提供器包含了什么数据(尽管,对某些提供器,你必须获取合适的权限来读取数据)。数据库

若是你想公开你本身的数据,你有两个选择:你能够建立你本身的内容提供器(一个ContentProvider子类)或者你能够给已有的提供器添加数据-若是存在一个控制一样类型数据的内容提供器且你拥有写的权限。数组

这篇文档是一篇关于如何使用内容提供器的简介。先是一个简短的基础知识讨论,而后探究如何查询一个内容提供器,如何修改内容提供器控制的数据,以及如何建立你本身的内容提供器。安全

内容提供器的基础知识Content Provider Basicsapp

内容提供器究竟如何在表层下保存它的数据依赖于它的设计者。可是全部的内容提供器实现了一个公共的接口来查询这个提供器和返回结果-增长,替换,和删除数据也是同样。dom

这是一个客户端直接使用的接口,通常是经过ContentResolver对象。你能够经过getContentResolver()从一个活动或其它应用程序组件的实现里获取一个ContentResolver:ide

ContentResolver cr = getContentResolver();ui

而后你可使用这个ContentResolver的方法来和你感兴趣的任何内容提供器交互。this

当初始化一个查询时,Android系统识别查询目标的内容提供器并确保它正在运行。系统实例化全部的ContentProvider对象;你历来不须要本身作。事实上,你历来不会直接处理ContentProvider对象。一般,对于每一个类型的ContentProvider只有一个简单的实例。但它可以和不一样应用程序和进程中的多个ContentProvider对象通信。进程间的交互经过ContentResolver和ContentProvider类处理。

数据模型The data model

内容提供器以数据库模型上的一个简单表格形式暴露它们的数据,这里每个行是一个记录,每一列是特别类型和含义的数据。好比,关于我的信息以及他们的电话号码可能会如下面的方式展现:

_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字段用来惟一标识这个表格里的记录。IDs能够用来匹配相关表格中的记录-好比,用来在一张表格中查找我的电话号码并在另一张表格中查找这我的的照片。

一个查询返回一个Cursor 对象可在表格和列中移动来读取每一个字段的内容。它有特定的方法来读取每一个数据类型。因此,为了读取一个字段,你必须了解这个字段包含了什么数据类型。(后面会更多的讨论查询结果和游标Cursor对象)。

惟一资源标识符URIs

每一个内容提供器暴露一个公开的URI(以一个Uri 对象包装)来惟一的标识它的数据集。一个控制多个数据集(多个表)的内容提供器为每个数据集暴露一个单独的URI。全部提供器的URIs以字符串"content://"开始。这个content:形式代表了这个数据正被一个内容提供器控制着。

若是你正准备定义一个内容提供器,为了简化客户端代码和使未来的升级更清楚,最好也为它的URI定义一个常量。Android为这个平台全部的提供器定义了CONTENT_URI 常量。好比,匹配我的电话号码的表的URI和包含我的照片的表的URI是:(均由联系人Contacts内容提供器控制)

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

相似的,最近电话呼叫的表和日程表条目的URI以下:Similarly, the URIs for the table of recent phone calls and the table of calendar entries are:

android.provider.CallLog.Calls.CONTENT_URI
android.provider.Calendar.CONTENT_URI

这个URI常量被使用在和这个内容提供器全部的交互中。每一个ContentResolver 方法采用这个URI做为它的第一个参数。正是它标识了ContentResolver应该和哪一个内容提供器对话以及这个内容提供器的哪张表格是其目标。

查询一个内容提供器Querying a Content Provider

你须要三方面的信息来查询一个内容提供器:

·         用来标识内容提供器的URI

·         你想获取的数据字段的名字

·         这些字段的数据类型

若是你想查询某一条记录,你一样须要那条记录的ID。

生成查询Making the query

你可使用ContentResolver.query()方法或者Activity.managedQuery()方法来查询一个内容提供器。两种方法使用相同的参数序列,并且都返回一个Cursor对象。不过,managedQuery()使得活动须要管理这个游标的生命周期。一个被管理的游标处理全部的细节,好比当活动暂停时卸载自身,而活动从新启动时从新查询它本身。你可让一个活动开始管理一个还没有被管理的游标对象,经过以下调用: Activity.startManagingCursor()。

不管query()仍是managedQuery(),它们的第一个参数都是内容提供器的URI-CONTENT_URI常量用来标识某个特定的ContentProvider和数据集(参见前面的URIs)。

为了限制只对一个记录进行查询,你能够在URI后面扩展这个记录的_ID值-也就是,在URI路径部分的最后加上匹配这个ID的字符串。好比,若是ID是23,那么URI会是:

content://. . . ./23

有一些辅助方法,特别是ContentUris.withAppendedId() 和Uri.withAppendedPath(),使得为URI扩展一个ID变得简单。因此,好比,若是你想在联系人数据库中查找记录23,你可能须要构造以下的查询语句:

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() 和managedQuery()方法的其它参数限定了更多的查询细节。以下:

·         应该返回的数据列的名字。null值返回全部列。不然只有列出名字的列被返回。全部这个平台的内容提供器为它们的列定义了常量。好比,android.provider.Contacts.Phones类对前面说明过的通信录中各个列的名字定义了常量ID, NUMBER, NUMBER_KEY, NAME, 等等。

·         指明返回行的过滤器,以一个SQL WHERE语句格式化。 null值返回全部行。(除非这个URI限定只查询一个单独的记录)。

·         选择参数

·         返回行的排列顺序,以一个SQL ORDER BY语句格式化(不包含ORDER BY自己)。null值表示以该表格的默认顺序返回,有多是无序的。

让咱们看一个查询的例子吧,这个查询获取一个联系人名字和首选电话号码列表:

import android.provider.Contacts.People;

import android.database.Cursor;

 

// Form an array specifying which columns to return.

String[] projection = new String[] {

                             People._ID,

                             People._COUNT,

                             People.NAME,

                             People.NUMBER

                          };

 

// Get the base URI for the People table in the Contacts content provider.

Uri contacts =  People.CONTENT_URI;

 

// Make the query.

Cursor managedCursor = managedQuery(contacts,

                         projection, // Which columns to return

                         null,       // Which rows to return (all rows)

                         null,       // Selection arguments (none)

                         // Put the results in ascending order by name

                         People.NAME + " ASC");

这个查询从联系人内容提供器中获取了数据。它获得名字,首选电话号码,以及每一个联系人的惟一记录ID。同时它在每一个记录的_COUNT字段告知返回的记录数目。

列名的常量被定义在不一样的接口中-_ID和_COUNT 定义在BaseColumns里, NAME在PeopleColumns里,NUMBER在PhoneColumns里。Contacts.People类已经实现了这些接口,这就是为何上面的代码实例只须要使用类名就能够引用它们的缘由。

查询的返回结果What a query returns

一个查询返回零个或更多数据库记录的集合。列名,默认顺序,以及它们的数据类型是特定于每一个内容提供器的。但全部提供器都有一个_ID列,包含了每一个记录的惟一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对象暴露出来,经过游标你能够在结果集中先后浏览。你只能用这个对象来读取数据。若是想增长,修改和删除数据,你必须使用一个ContentResolver对象。

读取查询所获数据Reading retrieved data

查询返回的游标对象能够用来访问结果记录集。若是你经过指定的一个ID来查询,这个集合将只有一个值。不然,它能够包含多个数值。(若是没有匹配结果,那还多是空的。)你能够从表格中的特定字段读取数据,但你必须知道这个字段的数据类型,由于这个游标对象对于每种数据类型都有一个单独的读取方法-好比getString(), getInt(), 和getFloat()。(不过,对于大多数类型,若是你调用读取字符串的方法,游标对象将返回给你这个数据的字符串表示。)游标可让你按列索引请求列名,或者按列名请求列索引。

下面的代码片段演示了如何从前述查询结果中读取名字和电话号码:

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的字符串可用来获取这个数据,通常而言,较小的数据(例如,20到50K或更小)最可能被直接存放到表格中,能够经过调用Cursor.getBlob()来获取。它返回一个字节数组。

若是这个表格条目是一个content: URI,你不应试图直接打开和读取该文件(会由于权限问题而失败)。相反,你应该调用ContentResolver.openInputStream()来获得一个InputStream对象,你可使用它来读取数据。

修改数据Modifying Data

保存在内容提供器中的数据能够经过下面的方法修改:

·         增长新的记录

·         为已有的记录添加新的数据

·         批量更新已有记录

·         删除记录

全部的数据修改操做都经过使用ContentResolver方法来完成。一些内容提供器对写数据须要一个比读数据更强的权限约束。若是你没有一个内容提供器的写权限,这个ContentResolver方法会失败。

增长记录Adding records

想要给一个内容提供器增长一个新的记录,第一步是在ContentValues对象里构建一个键-值对映射,这里每一个键和内容提供器的一个列名匹配而值是新记录中那个列指望的值。而后调用ContentResolver.insert()并传递给它提供器的URI和这个ContentValues映射图。这个方法返回新记录的URI全名-也就是,内容提供器的URI加上该新记录的扩展ID。你可使用这个URI来查询并获得这个新记录上的一个游标,而后进一步修改这个记录。下面是一个例子:

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);

增长新值Adding new values

一旦记录已经存在,你就能够添加新的信息或修改已有信息。好比,上例中的下一步就是添加联系人信息-如一个电话号码或一个即时通信IM或电子邮箱地址-到新的条目中。

在联系人数据库中增长一条记录的最佳途径是在该记录URI后扩展表名,而后使用这个修正的URI来添加新的数据值。为此,每一个联系人表暴露一个CONTENT_DIRECTORY常量的表名。下面的代码继续以前的例子,为上面刚刚建立的记录添加一个电话号码和电子邮件地址:

Uri phoneUri = null;

Uri emailUri = null;

 

// Add a phone number for Abraham Lincoln.  Begin with the URI for

// the new record just returned by insert(); it ends with the _ID

// of the new record, so we don't have to add the ID ourselves.

// Then append the designation for the phone table to this URI,

// and use the resulting URI to insert the phone number.

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);

 

// Now add an email address in the same way.

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()版原本把少许的二进制数据放到一张表格里去。这对于像小图标或短小的音频片段这样的数据是可行的。可是,若是你有大量二进制数据须要添加,好比一张相片或一首完整的歌曲,则须要把该数据的content: URI放到表里而后以该文件的URI调用ContentResolver.openOutputStream() 方法。(这致使内容提供器把数据保存在一个文件里而且记录文件路径在这个记录的一个隐藏字段中。)

考虑到这一点,MediaStore 内容提供器,这个用来分发图像,音频和视频数据的主内容提供器,利用了一个特殊的约定:用来获取关于这个二进制数据的元信息的query()或managedQuery()方法使用的URI,一样能够被openInputStream()方法用来数据自己。相似的,用来把元信息放进一个MediaStore记录里的insert()方法使用的URI,一样能够被openOutputStream()方法用来在那里存放二进制数据。下面的代码片段说明了这个约定:

import android.provider.MediaStore.Images.Media;

import android.content.ContentValues;

import java.io.OutputStream;

 

// Save the name and description of an image in a 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");

 

// Add a new record without the bitmap, but with the values just set.

// insert() returns the URI of the new record.

Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);

 

// Now get a handle to the file for that record, and save the data into it.

// Here, sourceBitmap is a Bitmap object representing the file to save to the database.

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);

}

批量更新记录Batch updating records

要批量更新一组记录(例如,把全部字段中的"NY"改成"New York"),能够传以须要改变的列和值参数来调用ContentResolver.update()方法。

删除一个记录Deleting a record

要删除单个记录,能够传以一个特定行的URI参数来调用ContentResolver.delete()方法。

要删除多行记录,能够传以须要被删除的记录类型的URI参数来调用ContentResolver.delete()方法(例如,android.provider.Contacts.People.CONTENT_URI)以及一个SQL WHERE 语句来定义哪些行要被删除。(当心:若是你想删除一个通用类型,你得确保包含一个合法的WHERE语句,不然你可能删除比设想的多得多的记录!)

建立一个内容提供器Creating a Content Provider

要建立一个内容提供器,你必须:

·         创建一个保存数据的系统。大多数内容提供器使用Android的文件储存方法或SQLite数据库来存放它们的数据,可是你能够用任何你想要的方式来存放数据。Android提供SQLiteOpenHelper类来帮助你建立一个数据库以及SQLiteDatabase类来管理它。

·         扩展ContentProvider类来提供数据访问接口。

·         在清单manifest文件中为你的应用程序声明这个内容提供器(AndroidManifest.xml)。

下面的章节对后来两项任务有一些标注。

扩展ContentProvider类Extending the ContentProvider class

你能够定义一个ContentProvider子类来暴露你的数据给其它使用符合ContentResolver和游标Cursor对象约定的应用程序。理论上,这意味须要实现6个ContentProvider类的抽象方法:

query()
insert()
update()
delete()
getType()
onCreate()

query()方法必须返回一个游标Cursor对象能够用来遍历请求数据,游标自己是一个接口,但Android提供了一些现成的Cursor对象给你使用。例如,SQLiteCursor能够用来遍历SQLite数据库。你能够经过调用任意的SQLiteDatabase类的query()方法获得它。还有一些其它的游标实现-好比MatrixCursor-用来访问没有存放在数据库中的数据。

由于这些内容提供器的方法能够从不一样的进程和线程的各个ContentResolver对象中调用,因此它们必须以线程安全的方式来实现。

周到起见,当数据被修改时,你可能还须要调用ContentResolver.notifyChange()方法来通知侦听者。

除了定义子类之外,你应该还须要采起其它一些步骤来简化客户端的工做和让这个类更容易被访问:

·         定义一个public static final Uri 命名为CONTENT_URI。这是你的内容提供器处理的整个content: URI的字符串。你必须为它定义一个惟一的字符串。最佳方案是使用这个内容提供器的全称(fully qualified)类名(小写)。所以,例如,一个TransportationProvider类能够定义以下:

public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transporationprovider");

若是这个内容提供器有子表,那么为每一个子表也都定义CONTENT_URI常量。这些URIs应该所有拥有相同的权限(既然这用来识别内容提供器),只能经过它们的路径加以区分。例如:

content://com.example.codelab.transporationprovider/train
content://com.example.codelab.transporationprovider/air/domestic
content://com.example.codelab.transporationprovider/air/international

请查阅本文最后部分的Content URI Summary以对content: URIs有一个整体的了解。

·         定义内容提供器返回给客户端的列名。若是你正在使用一个底层数据库,这些列名一般和SQL数据库列名一致。一样还须要定义公共的静态字符串常量用来指定查询语句以及其它指令中的列。

确保包含一个名为"_id"(常量_ID)的整数列来返回记录的IDs。你应该有这个字段而无论有没有其它字段(好比URL),这个字段在全部的记录中是惟一的。若是你在使用SQLite数据库,这个_ID 字段应该是下面的类型:

INTEGER PRIMARY KEY AUTOINCREMENT

其中AUTOINCREMENT描述符是可选的。可是没有它,SQLite的ID数值字段会在列中已存在的最大数值的基础上增长到下一个数字。若是你删除了最后的行,那么下一个新加的行会和这个删除的行有相同的ID。AUTOINCREMENT能够避免这种状况,它让SQLite老是增长到下一个最大的值而无论有没有删除。

·         在文档中谨慎的描述每一个列的数据类型。客户端须要这些信息来读取数据。

·         若是你正在处理一个新的数据类型,你必须定义一个新的MIME类型在你的ContentProvider.getType()实现里返回。这个类型部分依赖于提交给getType()的content: URI参数是否对这个请求限制了特定的记录。有一个MIME类型是给单个记录用的,另一个给多记录用。 使用Uri 方法来帮助判断哪一个是正在被请求的。下面是每一个类型的通常格式:

²  对于单个记录:    vnd.android.cursor.item/vnd.yourcompanyname.contenttype

好比,一个火车记录122的请求,URI以下

content://com.example.transportationprovider/trains/122

可能会返回这个MIME类型:

vnd.android.cursor.item/vnd.example.rail

²  对于多个记录:    vnd.android.cursor.dir/vnd.yourcompanyname.contenttype

好比, 一个全部火车记录的请求,URI以下

content://com.example.transportationprovider/trains

可能会返回这个MIME类型:

vnd.android.cursor.dir/vnd.example.rail

·         若是你想暴露过于庞大而没法放在表格里的字节数据-好比一个大的位图文件-这个给客户端暴露数据的字段事实上应该包含一个content: URI字符串。这个字段给了客户端数据访问接口。这个记录应该有另外的一个字段,名为"_data",列出了这个文件在设备上的准确路径。这个字段不能被客户端读取,而要经过ContentResolver。客户端将在这个包含URI的用户侧字段上调用ContentResolver.openInputStream() 方法。ContentResolver会请求那个记录的"_data"字段,并且由于它有比客户端更高的许可权,它应该可以直接访问那个文件并返回给客户端一个包装的文件读取接口。

自定义内容提供器的实现的一个例子,参见SDK附带的Notepad例程中的NodePadProvider 类。

声明内容提供器Declaring the content provider

为了让Android系统知道你开发的内容提供器,能够用在应用程序的AndroidManifest.xml文件中以 元素声明它。未经声明的内容提供器对Android系统不可见。

名字属性是ContentProvider子类的全称名(fully qualified name)。权限属性是标识提供器的content: URI的权限认证部分。例如若是ContentProvider子类是AutoInfoProvider,那么 元素可能以下:

< p>

          authorities="com.example.autos.autoinfoprovider"

          . . . />

请注意到这个权限属性忽略了content: URI的路径部分。例如,若是AutoInfoProvider为各类不一样的汽车或制造商控制着各个子表,Note that the authorities attribute omits the path part of a content: URI. For example, if AutoInfoProvider controlled subtables for different types of autos or different manufacturers,

content://com.example.autos.autoinfoprovider/honda
content://com.example.autos.autoinfoprovider/gm/compact
content://com.example.autos.autoinfoprovider/gm/suv

这些路径将不会在manifest里声明。权限是用来识别提供器的,而不是路径;你的提供器能以任何你选择的方式来解释URI中的路径部分。

其它 属性能够设置数据读写许可,提供能够显示给用户的图标和文本,启用或禁用这个提供器,等等。若是数据不须要在多个内容提供器的运行版本中同步则能够把multiprocess属性设置成"true"。这使得在每一个客户进程中都有一个提供器实例被建立,而无需执行IPC调用。

Content URI 总结

这里回顾一下content URI的重要内容:

 


A.      标准前缀代表这个数据被一个内容提供器所控制。它不会被修改。

B.      URI的权限部分;它标识这个内容提供器。对于第三方应用程序,这应该是一个全称类名(小写)以确保惟一性。权限在 元素的权限属性中进行声明:

< p>

          authorities="com.example.transportationprovider"

          . . .  >

C.      用来判断请求数据类型的路径。这能够是0或多个段长。若是内容提供器只暴露了一种数据类型(好比,只有火车),这个分段能够没有。若是提供器暴露若干类型,包括子类型,那它能够是多个分段长-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"这4个可能的值。

D.      被请求的特定记录的ID,若是有的话。这是被请求记录的_ID数值。若是这个请求不局限于单个记录, 这个分段和尾部的斜线会被忽略:

content://com.example.transportationprovider/trains

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/iefreer/archive/2009/09/07/4528800.aspx

相关文章
相关标签/搜索