数据存储与ContentProvider

数据存储

典型的桌面系统会提供通用文件系统,任何应用程序都可以通过文件系统存储文件,其它应用程序可以来读取这些文件(可能需要某些访问权限设置)。Android使用不同的系统:在Android上,应用程序的所有数据都是其私有的。但是,Android仍然提供了一套标准方法——通过Content Provider,使一个应用程序可以把它的私有数据开发给其它应用程序访问。Content Provider对应用程序来说是一个可选的组建组件,它暴露了应用程序数据的读/写访问,可以对其设置任何限制。Content Provider提供了请求和修改数据的标准语法和读取返回数据的标准机制。Android为标准的数据类型提供了一些Content Provider,如图像,视频和音频文件,以及个人通讯录信息。更多关于Content Provider使用的信息,请参考Content Provider。

无论是否需要把应用程序的数据输出给其他应用程序,我们都需要一种方法来保存这些数据。Android提供了四种机制来存储和获取数据:Preferences, Files, Databases, 和 Network。

Preferences

Preferences是存取原始数据类型键-值对的一种轻量级机制。Preferences的经典应用于存储应用程序的参数选项,如应用程序启动时候的缺省问候语和加载的文本字体。通过调用Context.getSharedPreferences()方法来读/写值。为Preferences集合赋予一个名称就可以与同一个应用程序中其它的组件共享这些Preferences,或者使用不带名字的Activity.getPreferences()方法保存这些Preferences对调用它们的Activity的私密性。除非通过content provider,Preferences不能跨应用程序共享。

下面的例子是计算器设置按键静音模式的用户参数选项:

import android.app.Activity;
import android.content.SharedPreferences;
  
public class Calc extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
 . . . 
  
 @Override
 protected void onCreate(Bundle state){ 
super.onCreate(state);
 
. . .
 
// Restore preferences
 SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
 boolean silent = settings.getBoolean("silentMode", false);
 setSilent(silent);
 }
 
@Override
 protected void onStop(){
 super.onStop();
 
// Save user preferences. We need an Editor object to
 // make changes. All objects are from android.context.Context
 SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
 SharedPreferences.Editor editor = settings.edit();
 editor.putBoolean("silentMode", mSilentMode);
  
 // Don't forget to commit your edits!!!
 editor.commit();
 }
}

Files

应用程序可以直接在移动设备或可移动存储介质上保存文件,缺省情况下,其它应用程序是不能访问这些文件的。

通过调用Context.openFileInput()函数,传递文件的本地名称和路径,就可以读取其中的数据。这个函数返回一个标准java FileInputStream的对象。调用Context.openFileOutput()函数,传递文件的名称和路径参数可以写一个文件,这个函数返回FileOutputStream对象。通过传递文件名称和路径调用上述的方法对其它应用程序是无效的,只能访问本身的文件。

如果在编译时有一个静态文件一同与应用程序打包在一块儿,就可以把这个文件保存在中的res/raw/myDataFile,然后通过Resources.openRawResource (R.raw.myDataFile)函数打开它,返回的是用于读取文件的InputStream对象。

Databases

Android API支持创建和使用SQLite数据库。数据库对于创建它的应用程序来说是私有的。

SQLiteDatabase对象代表了一个数据库以及与数据库交互的方法——访问和管理数据。

调用SQLiteDatabase.create()函数和SQLiteOpenHelper的子类,创建数据库。

作为支持SQLite数据库系统的一部分,Android提供了数据库管理功能,通过这些功能我们可以保存包装在有用对象中的复杂数据集。例如,Android为通讯录信息定义了一种数据类型,它包含了很多域,包括姓和名(字符串),地址和电话号码(字符串),照片(bitmap图片),以及其它更多的描述一个人的信息。

sqlite3工具同Android是一同发布的,通过这个工具我们可以浏览数据库内容、运行SQL命令、执行其它SQLite数据库上的有用的功能。

SQLite及其它所有的数据库都存储在设备上的/data/data/package_name/databases 中。

讨论创建多少个数据表、表包含什么字段,表之间如何关联,这些问题超出了本文的范围。但是,Android在标准SQLite概念之外没有强加任何限制。我们强烈建议包含一个能够作为唯一ID,并快速定一条位记录的自增值字段。关于这方面更多内容参考Content Provider文档。NotePad例子代码中的NotePadProvider类提供了怎样创建和使用新数据库的示例。创建的任何数据库都可以通过名称被应用程序中的其它类访问,但是对外部应用程序来说是不可访问的。

Network

如果网络可用,我们也可以通过网络保存和获取数据。网络存取要用到下面的包中的类:

· java.net.*

· android.net.*

Content Providers

Content providers可以存取数据,并且可以使所有应用程序访问这些数据。Content providers是应用程序之间共享数据的唯一方式;不存在所有Android包都能够访问的共用存储区。

Android包含了一些常用数据类型的Content providers(音频、视频、图像、个人通讯录等),在android.provider包中可以找到它们。可以查询这些Provider包含的数据(对某些数据来说,我们必须获得必要的授权)。

本文介绍怎样使用Content providers。基本概念的简要讨论之后,我们将探讨怎样查询一个Content Provider,怎样修改一个Provider控制的数据,以及怎样创建自己的Content Provider。

Content Provider基础知识

content provider实际上是怎样把数据存储在表面之下,这取决于它的设计。但是,所有的content providers都会实现一个通用接口来查询provider并返回结果,除此之外,还要添加、改变和删除数据。

大多数情况下,客户端可以通过ContentResolver对象,直接使用接口。在Activity或其它应用程序组件的实现代码中,通过调用getContentResolver()函数,就可以得到ContentResolver。

ContentResolver cr = getContentResolver();

然后,通过ContentResolver提供的方法,跟任何感兴趣的content provider进行互动。

查询初始化时,Android系统会确定作为查询目标的那个content provider,并且会确保它启动运行。系统会把所有ContentProvider对象实例化,用户不需要自己做这些。实际上,用户从来根本不需要同ContentProvider对象直接打交道。通常,对于每一种类型的ContentProvider,只有一个实例,但是这个实例可以与在不同的程序或进程中的多个ContentResolver对象进行通讯。进程之间的互动是由ContentResolver 和 ContentProvider类处理的。

数据模型

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

每条记录包含了“numeric _ID”域,它是在表中可唯一标识该条记录的。ID也可以用于在相关表中匹配激励——例如,在一个表中查找一个人的电话号码,在另一个表中获取其照片。

查询会返回一个Cursor 对象,Cursor 可以从一条记录移到另一条记录,一列一列地读取每个字段的数据。读取每种类型的数据都有特定的方法,所有,如果要读取一个字段,必须知道该字段所包含的数据的类型。

URIs

每个content provider 都会对外提供一个公共的URI(包装成Uri对象),URI唯一确定 这个content provider的数据集。控制多个数据集(多个表)的content provider会为每一个数据集提供独立的URI。Provider的URI都是以字符串“content://”开头的。content:表示这些数据是被content provider控制的。

如果我们自己定义一个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和日程表的URI分别是:

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

所有与其content provider互动的过程中都可以使用这个URI 常量。每个ContentResolver 都把URI作为其第一个参数,它决定了ContentResolver 将与哪一个provider对话,这个provider瞄准的是哪一个数据表。

构造查询

查询Content Provider需要三块儿信息:

· 标识provider的URI

· 想要获取的数据字段的名称

· 这些字段的数据类型

如果访问一条特殊记录,也是需要它的ID的。

通过调用 ContentResolver.query()函数 或者调用Activity.managedQuery()函数来查询一个Content Provider。 这两个函数携带形同的参数,并且均返回一个Cursor对象。但是, managedQuery()函数使activity管理Cursor的生命周期。被管理的Cursor负责处理所有具体事务,例如当activity暂停的时候,Cursor负责把自己卸载下去,当activity重新启动的时候,重新查询。通过调用 Activity.startManagingCursor()函数,我们可以让activity管理一个没有被管理的Cursor对象。

传给query() 或managedQuery()的第一个参数是这个Provider的URI——CONTENT_URI常量标识了一个特定的ContentProvider及其数据集。

在URI后面附上记录的_ID值,就可以把查询限制到一条记录上——即,把匹配ID值的一个字符串作为URI路径部分的最后一段,例如,如果ID是23,那么URI的形式就是:

content://. . . ./23

通过ContentUris.withAppendedId()Uri.withAppendedPath()这样的辅助方法,很容易把一个ID附加到URI上。这两个函数都是静态方法,返回一个附加了ID的Uri对象。例如,如果想在联系人数据库中查找第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平台提供的content providers都为它们自己的列定义了常量。例如, android.provider.Contacts.Phones类 为phone数据表中的列名定义了常量_ID, NUMBER, NUMBER_KEY, NAME等。

· 过滤器的格式如同SQL的Where语句,它定义了返回那些行。 如果URI没有显示产寻某一单一记录,null值表示返回查到的所有行。

· 选择参数。

· 返回行的排序,格式定义如同SQL 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");

这个查询从通讯录Content Provider的人员表中获取数据,它得到每个联系人的姓名、电话和唯一的记录ID,它也报告了记录的数量作为每条记录的_COUNT字段返回。

列名的常量在不同的接口中定义——在BaseColumns中定义了_ID 和_COUNT,在PeopleColumns中定义了NAME,在PhoneColumns中定义了NUMBER。Contacts.People类实现了所有这些接口,这就是为什么上面的例子代码能够通过类名引用它们。

查询返回什么

一条查询返回的是一组零或数据库记录的集合。列名、缺省排序、数据类型对每个Content Provider是特定的,但是每个Provider都有一个_ID列,它为每条记录保存一个数字ID值。每个Provider都可以把返回的记录数量作为_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对象用于在结果集中前向或后向列举记录,Cursor对象只能用来读数据。增加、修改和删除数据必须使用ContentResolver对象。

读取取回的数据

查询返回的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 values
 name = cur.getString(nameColumn);
 phoneNumber = cur.getString(phoneColumn);
 
 // Do something with the values. 
... 
} while (cur.moveToNext());
 }
}

如果一个查询返回诸如图像和声音的二进制数据,那么在数据表中可以被直接存储或者数据的表入口是指定一个Content的字符串:URI,通过这个URI可以得到数据。一般说来,小数量的数据(20K到50K或者更少)常常在表中直接存储,通过Cursor.getBlob()函数读取,这个函数返回的是一个二进制数组。

如果数据表入口是一个Content:URI,就不要直接打开和读取文件(一个原因是权限问题),而是通过调用ContentResolver.openInputStream()函数来得到一个可以用来读数据的InputStream对象。

修改数据

可以通过下面的方法修改content provider控制的数据:

· 增加新记录

· 对存在的记录添加新值

· 批量更新存在的记录

· 删除记录

调用 ContentResolver 的方法可以完成数据的修改。 某些content providers写数据需要比读数据更严格的权限,如果没有写Content Provider的权限, ContentResolver的方法将失效。

添加数据

要想向Content Provider添加一条新纪录,首先要在ContentValues对象中创建一个键-值对的映射,在这个映射中,每个值对应content provider中列的名字,相应的值就是新纪录在对应列中的值;然后,调用以Provider的URI和ContentValues映射作为参数ContentResolver.insert()函数。这个函数返回值是新记录的完整URI——即Provider的RUI加上新纪录的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或电子邮件地址。

为联系人数据库中增加一条新纪录最佳方法是把新数据所去的表明添加到该记录的URI后面,然后使用修正后的RUI来添加新数据值。每个联系人表都开放了一个名字作为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()函数可以把一小段二进制数据放到数据表中,ContentValues.put()函数携带一个二进制数组。这种方法适合于小的图标类图片或者一小段音频剪辑。如果要添加大量的二进制数据。例如照片,一整首歌曲,在数据表中保存该数据的content: URI,然后调用文件URI的ContentResolver.openOutputStream()函数(这会使ContentProvider把数据保存在一个文件里,,把这个文件的路径保存在记录的一个隐含字段中)。

就这方面来说,MediaStore Content Provider是分配图像、视频和音频数据的主要的Provider。MediaStore Content Provider采用特殊的规则:用于query() 或 managedQuery()来获取二进制数据的元信息(如照片的标题或者摄制日期)的URI,同样用于openInputStream()来获取二进制数据本身。类似的,用于insert()函数来把元信息放入MediaStore记录的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);
}

批量更新数据

想要批量更新一组记录,例如把所有字段的"NY"改成 "New York",就要调用ContentResolver.update()函数,用想要改变的列和值作为参数。

删除记录

调用ContentResolver.delete()函数,以特定行的Uri为参数,可以删除一条记录。

调用ContentResolver.delete()函数,以需要删除的记录类型的Uri(如android.provider.Contacts.People.CONTENT_URI)为参数,可以删除多行数据,SQL的WHERE语句可以指定删除哪儿些行。

注意:如果要删除一个通用类型,一定要确定WHERE语句是有效的,否则将会面临删掉比预计更多的记录的风险。

创建Content Provider

要创建一个Content Provider,必须:

· 建立保存数据的系统。大多数content providers使用Android的文件或SQLite数据库来存储数据,但是我们可以以我们想要的任何方式存储数据。 Android提供了 SQLiteOpenHelper来帮助我们创建数据库,提供了 SQLiteDatabase类来管理数据库。

· 扩展ContentProvider类来提供访问数据的方法。.

· 在应用程序的AndroidManifest.xml文件中声明这个Content Provider。

下面各节将讨论上面任务的后两项

定义ContentProvider类的子类把数据开放给使用ContentResolver和Cursor期望规则的其它应用程序,这主要是要实现在ContentProvider类中定义的六个虚函数:

query()

insert()

update()

delete()

getType()

onCreate()

query()方法必须返回一个能够遍历所求数据的Cursor对象。Cursor本身也是接口,但是Android一些可用的Cursor对象,例如,SQLiteCursor可以遍历保存在SQLite数据库中的数据。通过调用任何SQLiteDatabase类的query()方法就可以得到Cursor对象,还有其它为不是保存在数据库中的数据而设的Cursor实现,诸如MatrixCursor

由于在不同的进程和线程中的ContentResolver 对象都可以调用ContentProvider的方法,必须在线程安全模式下使用。

当对数据修改时,处于善意,会调用call ContentResolver.notifyChange()函数来通知监听者。

除了定义子类,还要采取其他步骤来简化客户端的工作,使这个类更容易使用:

· 定义 public static final Uri命名为 CONTENT_URI,它是一个字符串,代表了content provider能够处理的全部内容。 必须为这个值定义唯一的字符串。最佳的方法是使用content provider的完全类名 (小写)。例如, TransportationProvider类的定义如下的URI

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

如果Provider有子表,为每一个子表都要定义CONTENT_URI常量。这些URIs 应该具备相同的权限,只能通过它们的路径来区分。例如:

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

· 定义Content Provider返回给客户端的列名。 如果使用数据库,这些列名通常与SQL数据库类名同名。还要定义public static字符串常量,客户端可以在查询或其它指令中指定列

要确保为记录的ID提供一个命名为"_id" (常量 _ID)的整数列。无论是否还有其它字段(如URI)能够唯一标识记录,都应该定义这个ID字段。如果使用SQLite数据库,_ID应为下面的类型:

INTEGER PRIMARY KEY AUTOINCREMENT

AUTOINCREMENT描述符是可选的。 如果没有它,SQLite载ID字段添加比现存ID列中最大的数大一的数。如果删除了最后一行,那么增加的下一行将使用与被删除行相同的ID。AUTOINCREMENT使SQLite增加到目前最大值的下一个值,而无论这个最大值是否被删除。

· 仔细考证每个列的数据类型,客户端将使用这个信息来读数据。

· 如果要处理新的数据类型,必须在ContentProvider.getType()函数的实现中定义一种新的MIME类型返回。这种数据类型部分依赖于提交给getType()函数的content: URI 是否局限于请求某一特定记录。针对单一记录有一种MIME类型的格式,针对多记录则是另外一种格式。利用 Uri 方法有助于确定请求的是什么。下面是每种类型的通用格式:

o 对单一记录:

vnd.android.cursor.item/vnd.yourcompanyname.contenttype

例如,请求train记录122的URI格式如下:

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

返回的MIME类型:

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

o 对多记录:vnd.android.cursor.dir/vnd.yourcompanyname.contenttype

例如,请求所有train记录的URI格式如下

content://com.example.transportationprovider/trains

返回的MIME类型:

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

· 如果输出的字节数据太大以至于不能放在数据表中,如大的bitmap文件,那么向客户端输出的字段实际上应该包含一个content: URI字符串。这个字段提供给客户端访问数据文件的方式。记录中也要包含名为“_data”的字段。 “_data”列出了该文件在设备上的确切的文件路径,它不是提供给客户端访问的,而是由ContentResolver读取。客户端可以在面向用户字段上调用 ContentResolver.openInputStream() ,这个字段保存条目的URI。 ContentResolver会请求记录的"_data"字段。由于ContentResolver比起客户端来说有更高的权限,所以ContentResolver 可以直接访问文件,向客户端返回一个read wrapper。

content provider实现的例子,参考随同SDK一同发布的Notepad 示例应用程序中NodePadProvider 类。

声明content provider

为了让Android系统知道我们开发的content provider,需要在应用程序的AndroidManifest.xml文件中声明一个<provider>元素。没有在mainfest中声明的Content providers对Android系统来说是不可见的。

Name属性是能够完全确定ContentProvider子类的属性;authorities属性是标识provider的content: URI的权限部分。例如,ContentProvider的子类AutoInfoProvider的<provider>元素定义如下:

<provider name="com.example.autos.AutoInfoProvider"
 authorities="com.example.autos.autoinfoprovider" 
. . . />
</provider>

注意:authorities属性忽略了content: URI的路径部分。例如,如果AutoInfoProvider为不同类型的汽车或制造商管理不同的子表

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

这些路径不要在manifest中声明,authority是标识provider的,provider能够解释所选择的URI的路径部分。

其它<provider>属性可以设置读写数据的权限,提供可以显示给用户的图标或文本,使provider有效或无效等等。如果在content provider多个运行版本之间不需要数据的同步,把multiprocess属性设置为“true”,这允许在每个客户进程中创建provider实例,消除执行IPC的需求。

Content URI 总结

下面重温一下content URI的重要部分:

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

v/:* {behavior:url(#default#VML);} o/:* {behavior:url(#default#VML);} w/:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;}

A: 标准前缀,指明数据是由一个content provider控制的,永远不要修改这个前缀。

B: URI的权柄部分,它指明了content provider。对第三方应用程序来说,这应当是一个完全限定类名(小写),以确保其唯一性。在<provider>元素的authorities属性中声明这个权柄。

<provider name=".TransportationProvider"
 authorities="com.example.transportationprovider"
 . . . >

C: content provider用来确定请求哪种类型数据的路径。这部分可能有零个或多个段长。如果content provider只输出一种类型的数据(例如,只有trains),这部分是可以不要的;如果provider输出几种类型的数据,包括子类型,那么这部分可能是几个段长——例如,"land/bus", "land/train", "sea/ship",和 "sea/submarine"提供了四种可能性。

D: 如果有的话,这部分是被请求的具体记录的ID,它是被请求记录的_ID值。如果不是请求单一的记录,这部分以及斜杠线都可以省略,如:

content://com.example.transportationprovider/trains

http://www.52rd.com/Blog/Detail_RD.Blog_coolsand_20357.html

更多相关文章

  1. Andriod数据推送方案
  2. Android FileProvider应用之间共享数据
  3. 2011年linux数据库的android在线分享
  4. Android 应用程序退出后不在运行列表中显示的方法
  5. 如何使用Jdbc和Servlet操作Mysql数据库,编写Android登录注册服务
  6. Android与服务器端数据交互(http协议整合struts2+android)
  7. 2020年20种最佳Android应用程序模板

随机推荐

  1. android的线性布局
  2. android googlemap权限问题
  3. android双击事件
  4. 三款Android游戏源码
  5. Android shape 参数
  6. Android定义宽高比控件
  7. Android NDK Tools 下载链接大全
  8. android常用权限
  9. Eclipse adt Android Test Project
  10. Android Wear - App Structure for Andro