这个是Android的帮助文档中关于ContentProvider类的详细描述,非常权威。下面将中文部分翻译奉上。

 Content providers 用于存储和检索数据并可以使这些数据被所有的应用所得到。她是 Android 在应用 之间共享数据的唯一办法。她没有共同的存储空间,所有的应用都能够得到这些数据。
   Android 为常见的数据类型提供了一些 Content Providers(音频、视频、图像 、个人通讯录信息等等)。你可以在android.provider 这个包里面看到其中的一些 Content Proviers。你可以从这些 providers中查到他们所包含的数据,但是她们中有一些数据你必须得到正确的许可才能够读取。 
    如果你想让你自己应用中的数据共享,你有两种选择:你可以自建一个 Content Provider(一个Content Provider的子类)或者你可以将他们加到已有的 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 providers 来交流。
   当一个查询方法开始时,Android 系统会确认 content provider 的查询的目标以及它启动和运行了。系统会实例化所有的 ContentProvider 对象;不需要你亲自来做这件事情。实际上你从来不直接操作所有的 ContentProvider。一般每种类型的 ContentProvider 只有一个实例,但是她可以在不同的应用和进程中和多个 ContentResolver 对象交流,进程之间的交互由 ContentResolver 和 ContentProvider 两个类控制着。 
数据模型 
    Content providers 在一个数据库模型中以一个简单的表来显示数据,其中每一行是一个记录、每一列表示一个不同的数据类型和数据。例如个人通讯录信息会显示成如下的样式:

 

_ID

NUMBER

NUMBER_KEY

LABEL

NAME

TYPE

13

(425)555667

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 对象,利用 Cursor 可以来查询可读内容的每一行、每一列的字段。它有特定的方法来读取每种类型的数据,所以想要读取某一个字段,你必须知道这个字段是什么数据类型的。(我们会在下面的 query results and Cursor objects 详细讲解) 

URIs 

    每一个 content provider 提供一个公共的 URI(封装成一个 URI 对象)来独一无二的标识这个数据集。一个拥有着多个数据集(多张表)的 Content Provider 会为每张表提供一个不同的 URI。所有的provider URI 都要以”content://”这个字符串来开头。“content:”体系的结构代表一些被 content provider 所有操控的数据。

    如果你要定义一个 content provider,也可以为其 URI 定义个常量来简化客户端代码并使未来的更新更加简单。Android 为系统平台中所有的 providers 定义了 CONTENT_URI 常量,比如,用来匹配个人电话号码的表的 URI 和用来匹配个人相片集的表的 URI(这两张表都在 Contacts 这个 content provider 里面): 

                    android.provider.Contacts.Phones.CONTENT_URI 
                      android.provider.Contacts.Photos.CONTENT_URI 
    相似的有:最近的通话记录表和日历条目表的 URI: 

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

    URI 常量被用在和 content provider 的所有交互中。每一个 ContentResolver 方法使用 URI 作为第一 个参数,URI 就是用来如何确认哪一个 content provider 应该来交互和这个 content provider 中哪一个表来作为交互的目标的途径。 

查询一个 Content Provider 

    你需要三条信息用来查询一个 content provider: 
    1:标识了当前 content provider 的 URI 
    2:你想要接收的数据字段的名称 
    3:上述字段的数据类型

    如果你要查询一个特殊的字段,你会需要这个字段的 ID。

使用查询

    要查询一个 content provider 你既可以使用 ContentReslover.query()方法还可以使用Activity.managedQuery()方法。这两个方法都带有相同的数据参数并返回一个 Cursor 对象。但是,managedQuery()会让 activity 来操纵这个 Cursor 的生命周期。一个被绑定的 Cursor 会处理所有的细节,比如它会在 activity 被 pause 的时候卸掉自己的数据,在 activity restart 的时候重新装载数据。你可以调用Activity.startManagingCursor()来使一个 Activity 绑定一个没有被绑定的 Cursor。

    在 query( )或者 managedQuery()中的第一个参数都是这个 provider 的 URI——这个CONTENT_URI 常量必须确认一个特定的 ContentProvider 和数据集。(见 URIS) 

    如果要限制查询的数据仅为一条数据,你可以在这个 URI 后面跟上这条记录的__ID 值——就是说,用一个字符串来匹配这个 URI 路径中的最后一部分的 ID。比如,如何 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;

    使用 ContentUris 里的方法来生成一个基础的 URI:通讯录表中 ID 是 23 的记录 
    // Use the ContentUris method to produce the base URI for the contact with _ID == 23.
    Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23); 

    或者,使用 Uri 中的方法来生成这条基础的 URI 
    // Alternatively, use the Uri method to produce the base URI. 
   它需要一个 String 类型的而不是一个 Interger 类型的 
    // 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()中其他参数限定了本次查询的详细条件。她们是: 
    1.将被返回的数据列的名称,如果是 null 将会返回所有的列,否则只有被指明了名称的列才会被返回。所有系统平台自带的 content providers 为他们的列定义了常量。比如 android.provider.Contacts.Phones 类在其中的电话记录表中定义了前面描述的所有列的名称常量——__ID,NUMBER,NUMBER_KEY,NAMED 等等。 
  2.一个详细描述了那些行会被返回的过滤器。格式就像一条SQL语句中的WHERE字句(包括WHERE本身)。如果是null值则返回所有的行(除非URI限制了本次查询是一条单个记录)
 3.Selection参数
 4.返回的行的一种排序方式,就像一条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");


   这个查询方法检索了Contacts content provider中People表中的数据,它得到了每条记录中的姓名、私人电话和独一无二的ID,它还返回了每条记录中__COUNT(记录的总数)字段的数据。
  
  所有的列名的常量都被定义在各种接口里面——__ID和__COUNT是在BaseColumns,NAME在PeopleColumns,还有NUMBER是在PhoneColumns里面.Contacts.People类实现了上面的每一个接口,这就是为什么上面的例子的代码可以直接用类名来引用他们的原因。
   
query返回什么?

  一个query方法会返回一个包含0个或多个数据库记录的数据集。列名、默认排序以及他们的数据类型都是在每个content provider中特定的,但是每个content provider的都有一个__ID列用来为每一条记录标识一个独一无二的数字ID。每个content 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可以通过结果集来向前或向后迭代。你只能使用这个对象来读取数据,要想添加、修改或者删除数据,你必须使用一个ContentResolver对象。
  
读取检索的对象

   通过查询返回的Cursor对象提供了访问结果的记录集。如果你已经使用了_ID查询了一个特定的记录值,这个集将会仅包含一条记录,不然的话它会包含多条记录值。(如果没有特定的值与_ID相匹配,它可能是一个空集)。你可以通过一些特定的字段来读取数据,但是你必须知道这些字段的数据类型,因为Cursor对象对读取不同的数据类型拥有不同的方法——比如getString( ),getInt( )和getFloat( )。(然而,对于大多数的数据类型,如果你使用的是读取String的方法,它会返回这个数据的String形式)。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来获取那些数据。通常来说,较小的数据量(比方说,20到50K的或者更小的)一半都是被直接导入到表中的,并且能够使用Cursor.getBlob()方法来读取,他会返回一个字节类数组。
   如果这个表里的某项是一个content:类型的URI,你千万不能直接打开和读取这个文件(一方面来讲,权限问题可能导致操作失败)。取而代之的是,你可以调用ContentResolver.openInputStream( )方法来得到一个InputStream对象,进而可以用它来读取里面的数据。
修改数据
   一个content provider所拥有的数据可以被以下方式修改:
  1:添加新的记录
  2:对已有的记录添加新的数据值
  3:批量更新已有的记录
  4:删除记录
   所有的数据修改都会使用ContentReslover里面的方法来完成。一些content provider需要一个比读取数据更严格的权限来写入数据。如果你没有相关的写入content provider的权限,那么使用ContentReslover里的方法将会失败。

 

添加数据

要向一个content provider里添加一条新的记录,第一步在ContentValues对象里建立一个键值对型的map,在这之中,每一个key对应content provider中每一列的名称、每一个value就是在这一列中要添加的新记录的值,然后调用ContentResolver.insert()方法并且传送对应的content provider的URI和ContentValues对象,这个方法返回的是这条关于新记录的完整的URI——就是说,关于这条新记录的带有ID的provider的URI。你可以使用这个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);


 

添加新的值

  只要那条记录存在,你就可以向里面添加新的信息或者修改已经存在的信息。举个例子,在上面那个例子的下一步将是在新的字段里添加通讯信息——比如一条phone number、一条IM或者一条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()中需要一个字节数组参数类型的方法将小量的二进制数据添加到表中,比如它可以用于添加一个图标似的图像或者一个简短的音频剪辑,可是如果你有一大量的二进制数据要加进去,像一张照片或者一首完整的歌,你可以将一个content://型的URI放到表中去当作这个字段的数据,然后利用这个文件的URI调用ContentResolver.openOutputSream()这个方法来获取。(那将导致content provider要把这个数据存储在文件里并在这条记录的隐藏字段里记录这个文件的路径)。

  就这一点而言,MediaStore content provider,主要用于分管image、audio以及video数据的provider,它采用了一个特殊的协定:在query()或者manageQuery()中使用相同的URI来得到这二进制数据的元信息(比如,照片的标题和拍摄的日期),这些二进制数据是用openInputStream()方法得到的其数据本身。相似的,在insert()中也使用相同的URI来将元信息放到一个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里保存了这个图像的名称和描述 
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.//添加一条新的记录,添加的不是这个bitmap而是刚刚设置的values
// insert() returns the URI of the new record.//insert()返回了这条新记录的URI
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.这里的sourceBitmap是一个代表了数据库保存的文件的Bitmap对象。
try {
     OutputStream outStream =getContentResolver().openOutputStream(uri);//可能有很多人对于这个sourceBitmap的由来不是很懂,我认为这个//sourceBitmap应该是这样构造的。
 
File file=new File(String Path);
InPutStream in=null;
Try{
in=new BufferedInputStream(new FileInputStream(file));
BitMap sourceBitmap=BitMapFactory. decodeStream(in);
 }//上面是我的推断,大家应该能看懂
     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(),但是加入的是要删除的记录的类型。(比如,android.provider.Contacts.People..CONTENT_URI)以及一条SQL WHERE字句定义了哪些行要被删除。(提醒:如果你要删除一个普通的类型,请确定SQL包含一个正确的WHERE字句,否则你将要有风险的删除了过多的你不想要删除的记录)

 

创建一个自己的Content Provider

  要创建一个content provider,你必须有以下几个步骤:

  1.建立一个系统,用于存储数据。大多数的content providers使用Android自带的存储方法或者SQLite数据库来存储其数据,但是你可以以任何方式存储你的数据。Android提供了SQLiteOpenHelper类来帮助你创建一个数据库并且可以用SQLiteDatabase来管理它。

  2.继承ContentProvider这个类来提供操控数据的入口。

  3.在你的应用程序里的manifest里声明这个content provider(AndroidManifest.xml)

  下面的章节将会说明上面的最后两项。

 

继承ContentProvider类

  你定义一个ContentProvider的子类来向其他的使用ContentResolver和Cursor对象协定的客户端公开你的数据。首要的你要实现六个在ContentProvider类中声明的抽象方法。

query()

insert()

update()

delete()

getType()

onCreate()

query()方法必须返回一个可以迭代这些需要的数据的Cursor对象。Cursor本身也是一个接口,但是Android提供了一些现成的你可以使用的Cursor对象。比如,SQLiteCursor可以迭代存储在SQLite数据库里的数据。你可以通过调用任何SQLiteDatabase类的query()方法来得到Cursor对象。还有一些其他的Cursor的实现诸如MartrixCursor,可以迭代一些没有存储在数据库中的数据。

  由于这些ContentProvider的方法可以被多个ContentResolver的对象在不同的进程和线程中调用,他们必须以一个threadsafe方式实现。

  作为一种习惯,当有数据被修改时,你应该要调用ContentResolver.notifyChange()来通知监听者。

  在定义完子类后,你需要一些其他的步骤来简化处理客户端的工作和使这个类更加的可理解。

  ●定义一个public static final Uri命名为CONTENT_URI。这是一个代表了你的content provider所操控的全称 content:URI类型的字符串。你必须定义一个独一无二的值的字符串。最好的方法是使用当前的content provider类的全描述名称(使用小写)。所以举个例子,一个TransportationProvider类的URI可以被定义成以下形式:


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


  如果这个provider有子表,也需要为每个子表定义CONTENT_URI常量。这些URI应该都包含相同的源(就像定义content provider的那个),并且仅能够用他们的路径来区别。比如:

  content://com.example.codelab.transportationprovider/train

content://com.example.codelab.transportationprovider/air/domestic

content://com.example.codelab.transportationprovider/air/international

  对于一个content:URI的概述,请看在这篇文章的末尾的Content URI Summary

 ●定义content provider将要返回给客户端的列的名称。如果你正在使用一个底层数据库,那么这些列名应该和SQL数据库所提供的列名明显相同的。这些也要定义成pulic static String常量,那样用户可以用来在查询中指定这些列以及用在一些其他的说明里。

  确认你有一个名为”_id”的integer型的列(使用_ID常量),这个作为记录的ID。你必须要有这个字段不管你是否拥有其他的字段(比如一个URI),这个也是在所有记录中独一无二的。如果你要使用SQLite数据库,_ID字段应该是下面的类型:

  INTEGER PRIMARY KEY AUTOINCREMENT

  AUTOINCREMENT描述是可选择的。但是如果没有它,SQLite会增加一个ID counter字段用于记录下一个数字,这个数字是大于此列中现有的最大的数字的数字。如果你删除了最后一行,那么下一个被加进去的行将要用一个和被删除的行相同的ID。AUTOINCREMENT避免了此事,它不管有没有列被删除了,只是使这个SQLite自增到下一个更大的数字。

●仔细的记录每一列的数据类型。客户端需要用这些信息来读取数据。
  ●如果你要处理一个新的处理类型,你必须定义一个新的MIME类型来作为你实现ContentProvider.getType()方法的返回值。这个类型在某种程度上取决于被提交到getType()方法的content:URI是否对特定的记录限制了要求。一种形式的MIME类型是一个单个记录,还有一种是多条记录。使用Uri里面的方法可以帮助我们来决定那种类型被请求了。下面给出了每种类型的一般形式:
  1.一条记录:vnd.android.cursor.item/vnd.yourcompanyname.contenttype
  比如一个对train表中ID为122的请求,就像下面的URI
                    content://com.example.transportationprovider/trains/122   应该传回下面的MIMIE type:
                    vnd.android.cursor.item/vnd.example.rail  2.多条记录:vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
   比如一个对train表中所有的记录的请求,就像下面的URI
                      content://com.example.transportationprovider/trains    应该传回下面的MIME type:
                      vnd.android.cursor.dir/vnd.example.rail  ●如果你想展示你自己放进表中的很大的字节类数据——比如一个大的位图文件——这个字段向客户端展现数据时应该包含一个content:URI的字符串。利用这个字符串可以使客户得到这个数据文件。这条记录应该还有其他的字段,列名为“_data”的呈列了关于这个文件在系统里的精确路径。这个字段不会被客户端所想要读取,但是ContentReslover需要。客户会在面向用户的字段里调用ContentResolver.openInputStream()来操作这个文件的URI,然后ContentResolver会向那条记录的”_data”字段索要数据,因为这一步需要有比用户有更高的权限,这能够直接进入那个文件并返回一个可读的封装给用户。
  要想看到一个个人实现content provider的例子,你可以去看放在SDK中的一个Notepad的样例程序其中的NodePadProvider类。
 声明这个content provider
  要想让Android系统知道你设计的content provider,你要在应用中的AndroidManifest.xml中声明一个<provider>的标签。如果不声明的话,系统是不会见到的。
  标签里的name属性应该是你content provider子类的全称。authorities属性就是你为这个provider定义的content:URI里面的authority部分。例如:如果一个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的哪个URI,不包括路径;你的provider可以以任何方式来诠释你的URI的path部分。
  其他的<provider>的属性是可以设置读写数据的权限、对用户提供一个图标和一段文字,生效或者不生效等等。如果数据不需要在多个运行版本的content provider之间同步,设置multiprocess属性为true的话。这将会允许为每一个用户产生一个provider的实例对象,无须执行IPC。
  
  URI概述
  下面是一个关于URI的重要部分的概括:

                        
  A.标准的前缀表明了数据是被哪个content provider所拥有的。这是永远不会更改的
  B.URI中的authority部分:它定义了content provider。作为一个第三方的应用,这部分应该是其类名的全称(转换成小写)来保证唯一性。authority应该被声明在<provider>元素中的authorities属性里面:
  <provider name=".TransportationProvider"
         authorities="com.example.transportationprovider"
          . . . >
  C.关于content provider用来决定哪种数据将被需求的那部分。这部分可以没有或者有多部分的。如果provider展现的只有一种类型的数据(比如,只有火车),那么这部分可以没有。但是如果provider提供了多种类型的数据,还包含子类型,那么这部分应该是多部分长的——比如,“land/bus”,“land/train”,“sea/ship”以及“sea/submarine”,就会有4种可能性。
  D.还有的话就是被请求的指定记录的ID,它就是被请求记录里的_ID值。如果此次请求没有限定为一条单独的记录,那么这个部分和结尾的斜杠可以省略:
                       content://com.example.transportationprovider/trains