正文  数据库 > Content Provider >

解读Content Provider之一

综述 Content providers是一个Android应用程序的主要部分,主要是为应用程序提供内容。它对数据进行封装然后通过单一的ContentResolver接口提供给应用程序...

综述
    Content providers是一个Android应用程序的主要部分,主要是为应用程序提供内容。它对数据进行封装然后通过单一的ContentResolver接口提供给应用程序。只有需要在多个应用程序间共享数据时,content provider才是必须的。例如:有多个应用程序(例如:打电话程序和发短信程序等等)都需要使用联系人的数据,因此我们必须将这些数据存放在content provider。如果不需要在多个应用程序中共享数据,那么可以直接在应用程序中使用SQLiteDatabase。
    当程序通过一个ContentResolver接口请求Content provider时,系统就会检查传递进来的URI的权限,并且将请求传递给拥有这些权限的content provider。content provider可以以任意的方式解析URI。UriMatcher类对于解析URIs是非常有帮助的。
    对于content provider主要要实现的方法有如下几个:
    onCreate():主要是初始化provider;
    query(Uri, String[], String, String[], String):讲数据返回给它的调用者
    insert(Uri, COntentValues):插入新的数据到content provider
    update(Uri, COntentValues, String, String[]):更新content provider中的数据
    delete(Uri, String, String[])删除content provider中的数据
    getType(Uri):返回content provider中数据的MIME类型
数据存取方法(例如:insert(Uri, ContentValues)和update(Uri, ContentValues, String, String[]))可能会在同一时间被多个线程调用,并且是线程安全的。其他的方法(例如:onCreate())仅仅被程序的主线程调用,而且必须要避免在其中进行耗时的操作。具体的请看方法的描述。
    对于ContentResolver将会自动的传给合适的ContentProvider实例,因此子类不需要担心夸进程调用的细节。
开发者指南
    对于如何使用content provider的细节,请阅读Content Providers开发者指南。
下面是Content Providers开发者指南:
    Content Providers存取数据,并且使这些数据对于所有的程序都是可用的。Android系统中并没有对任何程序都可使用的共同存储区域,因此,这是在多个应用程序间共享数据的唯一方法。
    Android系统包含许多的对常见类型数据(音频、视频、图片以及个人联系信息等等)的content providers。你可以在android.provider包中看到一部分的content provider。你可以查询这个content provider中包含的数据(尽管对于其中的一部分数据,你必须要拥有合适的权限)。
    如果你想让自己的数据对外部公开,你有两个选择:
    1.你可以创建你自己的content provider(一个Content Provider子类)
    2.你可以将自己的数据添加到已经存在的content provide,如果存在包含着和你想公开数据的相同类型数据的content provider,并且你拥有权限,你就可以将这些数据写入到这个content provider中。
    这篇文档是介绍如何使用content provider。在简洁的讨论了一下基本后,这些讨论覆盖了如何查询一个content provider中的内容,如何修改一个content provider中包含的数据,如何为自己创建一个content provider。
   
Content Provider 基础知识
     content provider如何存储数据是依赖于它的设计的。但是所有的content provider都实现了一个共同的接口用于查询provider并且返回结果--以及:添加,修改和删除数据等。
     客户端不会直接使用这个接口,几乎都是通过ContentResolver对象。你可以通过实现在Activity中或其它应用程序组件中的getContentResolver()方法来获得ContentResolver.
     ContentResolver cr = getContentResolver();
     你可以通过ContentResolver的方法和感兴趣的content providers进行交互。
     当一个查询操作初始化后,Android系统识别查询要进行的操作对象,并且确保它处于运行状态。系统实例化了所有的Contentprovider对象:你根本不需要进行这样的操作。实际上,你从来就不会直接和ContentProvider打交道。实际上,对于每一种ContentProvider,系统仅仅有一个实例。但是这一个ContentProvider实例却可以和处于多个程序、多个进程中的ContentResolver对象进行通信。多个进程间的交互是通过ContentResolver类和ContentProvider类进行的。
数据模型
     Content providers是像数据库中的表的模型一样暴露自己所包含的数据,每一行都是一条记录,每一列都是一个特定的类型并且有特定的意义。例如:联系人的信息以及他们的电话号码就可能像如下一样存储:

\

在表中的每一条记录都含有是个数值型的_ID域,每一条记录的该域都是唯一的,用以标示这条记录。IDs可以用来匹配关联表中的记录,例如:在一个表中找到一个联系人的电话号码,在另一个表中找到该联系人的图片。
    查询操作可以返回一个Cursor对象,通过这个Cursor对象你可以从一条记录移动到另一条记录,从一个列移动到另一个列来读取每个列值。
    对于每一种类型的数据,他都有特定的方法读取。因此,要读取一个域的值,你必须要知道这个域的值的数据类型(关于这方面的详细内容会在查询操作以及Cursor对象中介绍)。
URIs
    每一个content provider都暴露了一个公共的URI(包装成一个Uri对象)用来唯一标示它包含的数据集。对于那些包含多数据集(多个表)的content provider它会为每一个数据集提供一个URI。所有由provider暴露的URIs都是以字符串"content://"开头的。"content:"表明这个数据集是由一个content provider提供的。
    如果你定义了一个content provider,那么为它的URI定义一个常量是一个比较好的做法,这样就可以简化客户端的编程并且使得将来的更新更加的彻底。Android随着平台一起为所有的providers定义了CONTENT_URI这个常量,例如:对于联系人电话号码的表的URI和存储联系人图片的表的URI(两者都是被Contacts content provider控制)分别如下:
    android:provider.Contacts.Phone.CONTENT_URI
    android:provider.Contacts.Photos.CONTENT_URI
    URI常量在所有与content provider的交互操作中都会被使用到。每一个ContentResolver的方法的第一个参数都是URI。它代表着ContentResolver将会与哪一个provider通信,竟会操作provider中的哪一个表。
查询一个Content Provider
    你需要三方面的信息来查询一个xontent provider:
    1.标示provider的URI;
    2.你希望接收的数据域的名称;
    3.你希望操作的数据域的数据类型。
    如果你是查询特定的记录,那么你还需要唯一标示这条记录的ID。
 执行查询操作
    要查询一个content provider,你将会使用到ContentResolver.query(0方法或者是Activity.managedQuery()方法。这两个方法使用同样的参数,都返回一个Cursor对象。然而,managedQuery()方法会使得由activity来管理返回的Cursor对象的生命周期。一个被管理的Cursor对象必须处理所有的细节,例如:在activity处于暂停状态时就要卸载自己,当activity处于重启时,又要开始执行查询操作。 你可以调用Activity.startManagingCursor()方法来管理一个还未被管理的Cursor对象。
    query()和managedQuery()方法的第一参数都是provider的URI——CONTENT_URI常量标示了一个特定的ContentProvider以及其数据集。
    要严格的查询到一条记录,你必须追加记录的_ID到URI上——也就是,将代表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); 
   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()方法的其他参数的详细介绍如下:
    1.要返回的列的名称。如果指定为null,那么就会返回所有的列。否则,只有那些别列出的名字的列才会被返回。所有的content provider都有平台定义的列常量。例如:android.provider.Contacts.Phones类定义了phone表中的列的名字,就像之前介绍的_ID,NUMBER,NUMBER_KEY,NAME等等。
    2.对于要返回的行的过滤器,如同SQL语句中的WHERE语句(除掉WHERE自身)。若指定为N,则返回所有的行(除非指定了只查询特定的单个记录)。
    3.进行选择的参数
    4.一个代表返回的所有的行排序的参数,如同SQL语句中的QRDER 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"); 
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类实现了所有的这些接口,这也就是为什么上面的示例中我们可以仅仅通过类名就引用这些方法。
  一个查询操作的返回值
    一个查询操作会返回0条或者是更多的数据库记录。列的名称,它们默认的顺序,以及他们对于每个contect provider的数据类型。每一个provider都有一个数值型的_ID列,也就是每条记录的唯一标示ID。每一个provider也可以通过_COUNT列来返回这次查询操作总共返回了多少条记录,对于返回的每条记录,_COUNT域的值都是一样的。
    下面是通过查询操作返回的上面例子的结果的示例:\

 

获取的数据是通过一个Cursor对象来呈现的,通过这个Cursor对象,我们可以通过这个结果集来重复的向前向后操作。你仅仅可以通过这个Cursor对象来读取数据。要想添加、修改或者是删除数据,你必须使用ContentReslover对象。
读取检索出来的数据
    我们可以通过查询providers返回的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()); 
 
        } 

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指定表的入口来允许我们对这类型的数据进行存取。一般地,少量的数据(从20K到50K或者更少的)几乎都是直接记录在表中的并且可以通过Cursor.getBlob()方法读取,他返回一个byte类型的数组。
   如果表的入口是一个content:URI,你不应该直接打开或者读取(因为一件事,权限问题可能导致这个操作失败)。此时,你应该使用ContentRecesolver.openInputStream()方法获得一个InputStream对象,再通过这个InputStream对象来读去数据。

摘自 chenlong12580的专栏