您的位置:首页 > 移动开发 > Android开发

Android 复习 Content Provider

2011-06-16 12:15 831 查看
Content ProvidersContent providers存储与获取数据,并且使所有的应用都可以访问他。这是唯一的跨进程共享数据的方法,这里没有所有Android包可以访问的通用数据区。Android为一些常见的数据类型(音频,视频,图像,个人联系信息等等)提供了一组contentprovider 。你可以在android.provider包下看到列出的他们中的一些。你可以查询他们拥有的数据(然而,有些provider,你必须要拥有一定权限才可以)。如果你想让自己的数据公有化,你有两个选择:你可以创建你自己的contentprovider ( 一个contentprovider 的子类)或者你可将你自己的数据添加到一个已存在的contentprovider中去(如果有一个contentprovider 控制与你的相同的数据类型且你对其有写权限)。

ContentProvider基础(Content Provider Basics)

一个contentprovider如何存储他的数据完全依赖于他的设计。但是所有的contentprovider都实现一个通用的接口用以查询数据和返回结果,同时还有添加,修改,删除。他是一个客户端可以间接使用的接口,最为常见的是通过ContentResolver对象。你可以在一个Activity的实现中或者别的组件中通过调用getContentResolver()来获取一个ContentResolver。
ContentResolver cr = getContentResolver();
你可以使用ConentResolver的方法来与你感兴趣的任何ContentProvider来交互。当一个查询初始化了,Android系统识别该查询的目标contentprovider,并确保他已经运行起了。系统初始化所有的ContentProvider对象,你不用自己去管这个。实际上,你永远不会直接处理ContentProvider。典型的,每个ContentProvider只有一个实例存在系统中。但是他可以和不同的进程与应用的ContentResolver通信。这种跨进程的交互在ContentResolver与ContentProvider类中处理。

数据模型(Thedata model)

ContentProvider根据数据库模型以一个简单的表的形式暴露他的数据,每一行代表一条记录,每一列是一个特殊类型的数据有他特别的意思。例如,人和他们的电话号话的信息可以像以下表来展露:
_IDNUMBERNUMBER_KEYLABELNAMETYPE
13(425) 555 6677425 555 6677Kirkland officeBully PulpitTYPE_WORK
44(212) 555-1234212 555 1234NY apartmentAlan VainTYPE_HOME
45(212) 555-6657212 555 6657Downtown officeAlan VainTYPE_MOBILE
53201.555.4433201 555 4433Love NestRex CarsTYPE_HOME
每一行记录包括一个数字类型的_ID用来唯一识别表中的一条记录。ID可以用来在相关的表中配置记录。例如,在一个表中查找一个人的电话号码,在另一个表中查到他的图片。一个查询返回一个Cursor对象,他可以从一条记录移动到另一条记录,也可以从一列移动到另一列来读取每一个域中的数据。他对每一种数据类型的读取都有其相应的方法。所以,读一个域的数据,你必须知道他包含的数据的类型。

URIs

每一个ContentProvider暴露一个公共的URI(一个Uri的对象),他用以唯一识别他的数据集。一个contentprovider控制多个数据集(多个表),每个数据集各有一个URI。所有provider的URI都以”content://”开始。content:标识这个数据被contentprovider控制的。如果你定义一个ContentProvider,为URI定义一个常量是一个好主意,他可以使客户端代码简单化也可以使将来维护清晰。来自平台的的ContentProvider的URI都定义常量为CONTENT_URI。例如:一个匹配电话到人和一个图片到人的表的URI如下:
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
[/code]
这些URI在所有的与ContentProvider交互的地方使用。所有的ContentResolver方法都将URI作为他的第一个参数。他用以唯一识别ContentResolver应该与哪个ContentProvider交流,以及这个Provider的哪一个表才是他的目标。

查询一个ContentProvider(Querying a Content Provider)

查询一个contentprovider你需要三块信息:URI用以识别哪个Content Provider.你想获取的数据域的名称。这些域的数据类型如果你要查询某一特定记录,你还需要那条记录的ID。

查询(Makingthe query)

查询一个ContentProvider,你可以使用ContentResolver.query()方法,或者使用Activity.managedQuery()方法。两个方法都采用同一组参数,都返回一个Cursor对象。然而,managedQuery()使Activity管理Cursor的生命周期。一个托管的Cursor处理他所有的变化,例如,当activity暂停时上传他自己,当activity重新开始时重新查询他自己。你可以通过Activity.startManagedCursor()要求Activity去管理一个没有被管理的对象。两个方法query()与managedQuery()的第一个参数都是Provider的URI。CONTENT_URI常量识别一个特殊的ContentProvider和数据集。约束只对一条记录的查询,你可以为那条记录的URI添加上他的_ID值,即是,放一个匹配的字符ID作为URI的最后一个部分。例如,如果ID是23,他的URI应该是:
content://. .. ./23
这里有一些比较有用的方法,尤其是
ContentUris.withAppendedId()
Uri.withAppendedPath(),
他们使添加一个
ID
URI
简单化。两个静态方法都返回一个添加了
ID
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);
[/code]query()与managedQuery()的其它参数从更多细节方面限制一条查询。它们是:应该返回的数据列的名称。Null值将返回所有列。否则,只有给出的列的值才会返回。所有的平台列名都是以常量的形式这义他们的列名。例如,android.provider.Contacts.Phone类定义了phone表中的所有的列名为常量,如前图表所示。_ID,NUMBER,NUMBER_KEY,NAME等等。一个过滤器定义哪些行应该返回。格式化为一个SQL的where语句(where本身除外)。Null值返回所有行(除非URI限制只返回一条记录)。选择参数。一个为查询的结果排序的排序器,格式化为SQL中的ORDERBY语句(ORDERBY除外)。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 returnnull,       // Which rows to return (all rows)null,       // Selection arguments (none)// Put the results in ascending order by namePeople.NAME + " ASC");
[/code]这个查询从Contacts的contentprovider中获取People表中的数据。他获取每一个联系人的姓名,主要电话号码,唯一的记录ID。他同时以每条记录的_Count域返回记录的条数。这些列名被定义到各种接口中。_ID和_COUNT在BaseColumns中,NAME在PeopleColumns中,NUMBER在PhoneColumns中。Contacts.People类实现了这些每一个接口,所以我们可以在以上的代码中仅使用类名即可以引用他。

查询的返回(Whata query returns)

一个查询返回0条或者多条数据记录。列名,默认的排序,以及他们的数据类型被每一个ContentProvider所指定。但是每个Provider都有一个_ID列,他为每一条记录持有一个唯一的数字的ID。每一个provider可以通过_COUNT列报告记录的条数,对于所有的行这个数据是一样的。下面是前面查询的结果的一个例子:
_ID_COUNTNAMENUMBER
443Alan Vain212 555 1234
133Bully Pulpit425 555 6677
533Rex Cars201 555 4433
返回的数据是一个Cursor对象,他可以通过后退或者前进以枚举遍历其结果集。这个对象你只能用来读数据。添加,修改或者删除数据,你必须使用ContentResolver的对象。

读取数据(Readingretrieved data)

Cursor对象是通过查询返回,提供了一个访问结果的记录集的途径。如果你是查询一个特殊ID的记录,那个记录集只包含一个值。否则他可能包含多个值。(如果没有匹配的,他也可以是空的。)你可以读取记录中的特定字段的值,但是你必须知道这个字段的值类型,因为Cursor对象针对每一种类型有一个特别的方法,比如:getString(),getInt(),getFloat()( 然而,对于绝大多数类型,你可以使用读取字符串的方式来读取,Cursor对象会将该数据以字符串的形式返回给你)。Cursor要求你请求索引列的列名或者索引列名的索引列号。下列片断演示了从之前的查询中读取名字与电话号码:
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 valuesname = cur.getString(nameColumn);phoneNumber = cur.getString(phoneColumn);// Do something with the values....} while (cur.moveToNext());}}
[/code]如果一个查询会返回二进制数据,比如图像或者声音,数据可能会直接进入一个表,或者进入一个字符串指定content:的URI你可以用以取得数据。通常,小量的数据(20~50K或者更小)直接进入数据表,可以通过Cursor.getBlob()来读取。他返回一个byte的数组。如果表输入的是一个content:的URI, 你不应该试图直接打开或者读取这个文件(比如:权限问题有可能使其失败)。你应该调用ContentResolover.openInputStream()来获取一个InputStream对象,你可以用以读取数据。

修改记录(ModifyingData)

Content proivder持有的数据可以通过以下方式被修改:添加新记录添加新值到已存在的记录中去批量更新已存在的记录删除记录所有的数据的修改都通过ContentResolver的方法来完成。一些contentprovider需要比读取更多一些的权限来写数据。如果你没有权限对一个contentprovider写数据,那么ContentResolver的方法会失败。

添加记录(Addingrecords)

添加一条新记录到一个contentprovider,每一件事情是用ContentValues对象创建一组键值对的映射,每个键匹配一个contentprovider的列名,值是为新记录的那一列准备的值。传递provider的URI和ContentValues的映射给ContentResolver.insert()方法。这个方法返回新记录的全URI,即,这个provider的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 favoritesvalues.put(People.STARRED, 1);Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
[/code]

添加新值(Addingnew values)

一旦一个记录存在了,你可以为其添加新的信息,也可以修改其已存在的信息。例如,上例的下一步,你可以添加联系信息,像电话号码,或者IM,或者email地址,到这个新的条目。在Contacts数据库中添加一条记录的最好的方法是添加一个新数据进行的表名到这个记录的URI,然后使用这个修正后的URI添加新的数据值。为了这个目的每一个Contacts表以CONTENT_DIRECTORY常量暴露一个名字。以下代码继续之前的例子为刚刚创建的记录添加一个电话号码和一个e-mail地址。
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()使用一组byte数组将少量的二进制数据放入一个表中。比如,他可以是一个小的图片,或者一个小的音频片段。然而,如果你有一个大量的二进制数据需要添加,比如一张照片,或者一首完整的歌,放一个content:的URI到表中,并且和文件的URI一起调用ContentResolver.openOutputStream()。(他促使contentprovider在一个字段中存储数据并在记录的一个隐含字段中记录文件的路径。)在这点上,MediaStorecontent provider,主要分配图像,音频,视频的provider,遵守一个特定的公约:相同的URI用于query()和managedQuery()去获取二进制数据的元数据(例如照片的标题和拍照的时间),用openInputStream()来获取数据本身。类似的,相同的URI用以insert()将元信息存入MediaStore记录,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);}
[/code]

批量更新记录(Batchupdating records)

批量更新一组记录,调用ContentResolver.update()方法来改变。

删除一条记录(Deletinga record)

如果删除单独一条记录,调用ContentResolver.delete()和特定行的URI。删除多条记录,调用ContentResolver.delete(),提供一个要删除数据类型的URI和一个SQL的where 语句来定义哪些行应该被删。

创建一个ConentProvider(Creating a Content Provider)

要创建一个ContentProvider,你必须:设置一个存储数据的系统。大多数contentprovider使用Android的存储方式或者SQLite的数据库来存储他们的数据,但是你可以以你想要的任何方式来存储数据。Android提供SQLiteOpenHelper类来帮助你创建一个数据库以及SQLiteDatabase来管理他。扩展ContentProvider类以提供对数据的访问。在你的应用的manifest文件中声明一个ContentProvider。

扩展ContentProvider类(Extendingthe ContentProvider class)

你定义一个ContentProvider的子类使用ContentResolver期望的公约和Cursor对象来将你的数据暴露给其它组件或者应用。原则上,这意味着要实现ContentProvider定义的六个抽象方法:
query()
insert()
update()
delete()
getType()
onCreate()
query()方法必须返回一个Cursor对象,可以枚举遍历请求的数据。Cursor他本身是一个接口,但是Android提供了一些准备好了的Cursor对象供你使用。例如:SQLiteCursor可以枚举存储在SQLite数据库中的数据。你调用任何一个SQLiteDatabase类的query()方法可以获得一个Cursor对象。这里还有一些别的Cursor的实现,比如MatrixCursor,数据没有存在一个数据库中。由于这些ContentProvider方法的调用可能来自己不同进程的不同线程的ContentResolvoer对象,它们必须设计为线程安全的。当有数据被修改时你可能会调用ContentResolver.notifyChange()来通知你的监听者。除了定义子类本身,下面还有其它步骤可以简化客户端的工作以及增该类的可访问性:·定义一个public static final 的URI ,命名为CONTENT_URI。这是一个字符串,他表示为你的contentprovider处理的完整的content:的URI。你必须为此定义一个唯一的字符串。最好的解决办法是使用contentprovider(由小写)的全限定类名。例如,一个TransportationProvider类可以如下定义:
public static final Uri CONTENT_URI =Uri.parse("content://com.example.codelab.transportationprovider");
[/code]
如果provider还有子表,也应该为每一个子表各定义一个CONTENT_URI。这些URI都应该有相同的权限。只通过他们的路径进行区别。例如:
content://com.example.codelab.transportationprovider/traincontent://com.example.codelab.transportationprovider/air/domesticcontent://com.example.codelab.transportationprovider/air/international
[/code]
·定义content provider将要返回给客户端的列的列名。如果你使用一个隐含的数据库,这些列名通常与数据库中的列表相同。也定义为publicstatic的字符串常量,客户端可以用以指定在查询或者别的指令中使用的列。
确保包含一个记录ID的列名为_id的列。(其静态常量为_ID),不管你有没有其它字段,你应该有这个字段,且所有记录间都是唯一的。如果你使用SQLite数据库,_ID字段应该是如下类型:
INTEGER PRIMARY KEY AUTOINCREMENT
[/code]
描述符AUTOINCREMENT是可选的。但是,如果没有它,一个ID的SQLite递增计数器字段上方的最大的下一个数列中的现有人数。但是如果你删除掉最后一行,下一个添加行的ID会与删除掉的行的ID值一样。[code]AUTOINCREMENT
避免了通过
SQLite
增加到下一个最大值,不论是删除与否。
[/code]·仔细记录每个列的数据的类型。客户需要这些信息来读取数据。·如果处理一个新的数据类型,你必须定义一个新的MIME类型,在你的ContentProvider.getType()中返回。这个类型部分依赖于content:的URI是否提交到getType()限制到一个特殊记录的请求。这里有一个针对单一记录的MIME类型的格式,另一个是针对多条记录的。使用Uri方法有助于判断请求的是什么。下面是每个类型的普遍的格式:
对一条单独的记录:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype
例如一个对train的122的记录的请求,像这样的URI:
content://com.example.transportationprovider/trains/122
[/code]
可能返回这样的MIME类型:
vnd.android.cursor.item/vnd.example.rail
[/code]
对于多条记录:
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
假如,一个对所有train记录的请求,像如下的URI:
content://com.example.transportationprovider/trains
[/code]
可能返回如下MIME类型:
vnd.android.cursor.dir/vnd.example.rail
·如果暴露的二进制数据太大而不能放在这个表中,比如一个很大的位图文件,那个字段暴露给客户端的应该是一个content:的URI的字符串。这个字段让用户可以访问数据文件。该记录应该还有另一个字段_Data,列出扩展文件在设备中的路径。这个字段不许客户端读取,但是可以被ContentResolver访问。客户端将针对用户面对的字段从那项中提出的URI调用ContentResolver.openInputStream()。ContentResolver将要为这条记录请求_data字段,因为他拥有比客户端更高的权限,他应该可以直接访问文件并且返回一个文件的可读的wrapper给客户端。

声明contentprovider(Declaring the content provider)

让Android系统知道你开发的contentprovider, 在应用的AndroidManifest.xml文件中使用一个<provider>元素来声明他。ContentProvider如果没有在manifest中声明,那么他对系统不可见。Name属性是ContentProvider子类的完全保留名称。
Authorities
属性识别这个
Proivder
Uri
的权限部分。例如,一个
ContentProvider
的子类是
AutoInfoProvider, <provider>
的属性可能像这样:
<provider android:name="com.example.autos.AutoInfoProvider"android:authorities="com.example.autos.autoinfoprovider". . . /></provider>
[/code]注意authorities属性省略掉了路径中的content:部分。例如,如果AutoInfoProvider为不同类型的auto和不同类型的生产商控制着一些子表,
content://com.example.autos.autoinfoprovider/hondacontent://com.example.autos.autoinfoprovider/gm/compactcontent://com.example.autos.autoinfoprovider/gm/suv
[/code]
这些路径不需要在manifest中声明。Authority是一个Provider的一个标识,而不是一个路径,你的Provider你可以选择以任何方式解释URI的路径部分。别的<provider>属性可以设置对数据的读写权限,提供一个图标和一个文字给用户显示,让provider可用或者不可用,等等。如果在多个contentprovider版本之间运行不需要同步,那么可以设置multiprocess为true。这允许这个provider可以在每个客户进程中创建,消除了需要执行IPC。

ContentURI总结(ContentURI Summary)

这里有一个对ContentURI的重要部分的组成的概述。A.A标准的前缀,指定数据是由一个content provider控制。他永远不会改变。B. BURI的权限部分,他识别这个content provider。对于一个三方的应用,他应该是一个完整的限定了的类名(都转为小写)去确保其唯一。权限在<provider>中以authorities属性来声明:
<provider android:name=".TransportationProvider"android:authorities="com.example.transportationprovider". . .  >
[/code]C.C路径:Content provider 用以决定哪种类型的数据被使用。它可以是0个或者多个段。如果contentprovider只暴露了一种数据类型(例如,只有trains),那么他可以缺失。如果contentprovider暴露了多个类型,包括子类型,他可以有多个段,假如:”land/bus”,”land/train”,”sea/ship”,”sea/submarine”。D.D如果ID给定,则请求的是指定的记录。这是请求的记录的_ID值。如果请求没有限制到一个单独的记录,这个段与斜杠应该省去。
content://com.example.transportationprovider/trains
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: