Android学习笔记37:使用Content Providers方式共享数据

  在Android中一共提供了5种数据存储方式,分别为:

  (1)Files:通过FileInputStream和FileOutputStream对文件进行操作。具体使用方法可以参阅博文《Android学习笔记34:使用文件存储数据》。

  (2)Shared Preferences:常用来存储键值对形式的数据,对系统配置信息进行保存。具体使用方法可以参阅博文《Android学习笔记35:使用Shared Preferences方式存储数据》。

  (3)Content Providers:数据共享,用于应用程序之间数据的访问。

  (4)SQLite:Android自带的轻量级关系型数据库,支持SQL语言,用来存储大量的数据,并且能够对数据进行使用、更新、维护等操作。具体使用方法可以参阅博文《Android学习笔记36:使用SQLite方式存储数据》。

  (5)Network:通过网络来存储和获取数据。

  本篇博文介绍第三种方式,通过Content Providers实现应用程序之间的数据共享。

 

1.Content Providers简介

  在Android系统中,不存在一个公共的数据存储区供所有的应用程序访问,也就是说数据在各个应用程序中是私有的。那么,如何在一个应用程序中访问另一个应用程序中的数据,实现应用程序之间的数据共享呢?

  当然,你可以通过《Android学习笔记34:使用文件存储数据》一文中讲到的设置openFileOutput()方法中的第二个参数mode为Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE,让别的应用程序可以读写该应用程序中的文件。但是,使用这种方式的弊端也是显而易见的,不仅需要知道该文件的存储路径,而且会将该文件内容完全的暴露出去,对于内容提供者和内容访问者来说都是不方便和不安全的。

  为此,Android系统提供了Content Providers,用以方便安全的实现应用程序间的数据共享。

1.1ContentResolver

  所有的Content Providers都会实现一些共同的接口,包括数据的查询、添加、更改和删除。在应用程序中,我们可以通过使用getContentResolver()方法来取得一个ContentResolver对象,然后就可以通过这个ContentResolver对象来操作你需要的Content Provider了。ContentResolver类提供的用来操作Content Provider的方法主要有insert()、delete()、update()和query()。

  通常,对于开发者而言,并不需要同Content Provider对象直接打交道。系统运行时,会将所有的ContentProvider对象实例化,对于每一种类型的ContentProvider只有一个实例。这个实例可以与在不同的程序或进程中的多个ContentResolver对象进行通信。而这些进程间的交互则是由ContentResolver和ContentProvider类进行处理的。

  对于Content Providers而言,最重要的就是数据存储结构和URI。

1.2数据存储结构

  Content Providers将其存储的数据以数据表的形式提供给访问者,在数据表中,每一行为一条记录,每一列为具有特定类型和意义的数据。比如,联系人的Content Provider数据存储结构如图1所示。

图1 Content Provider数据存储结构示例

  可以看出,每条记录都有一个_ID字段用来唯一的标识该记录,类似于数据库中的主键。

1.3URI

  每一个Content Provider都对外提供一个能够唯一标识自己数据集的公开URI,如果一个Content Provider管理多个数据集,则需要为每一个数据集都分配一个独立的URI。

  Android规定,所有Content Provider的URI都必须以“content://”开头。通常,URI由3部分组成:“content://”、数据的路径、标识ID(可选)。

  比如,以下是系统提供的一些URI:

  (1)content://media/internal/images

  (2)content://contacts/people/2

  (3)content://contacts/people

  其中,(1)将返回设备上存储的所有图片;(2)将返回联系人信息中ID为5的联系人记录;(3)将返回设备上所有的联系人信息。

  每个ContentResolver对象都将URI作为其第一个参数,URI决定了ContentResolver将与哪一个Content Provider对话。

 

2.获取Content Provider内容

  Android系统为一些常见的数据类型(如音频、视频、图像、通讯录联系人等)内置了一系列的Content Provider。以下就以通讯录联系人为例,讲讲如何获取Content Provider内容。

  首先,我们需要在模拟器中运行“联系人”应用程序程序,并在其中添加联系人。如图2所示。

图2 添加联系人

  如图2所示,我在“联系人”应用程序程序中添加了两个联系人:李明和王磊。

  然后,我们需要创建一个自己的工程,该工程的主要功能就是得到持有联系人信息的Content Provider中的数据。这里,我在布局文件中定义了一个TextView控件,用来将获得的联系人数据显示出来,运行后的效果如图3所示。

图3 获取Content Provider内容

  由图3可以看出,我们自己创建的应用程序确实从“联系人”应用程序程序中获得了数据(ID和Name字段),当然,如果你需要,你可以获取图1中的更多的字段信息。

  下面的代码给出了实现这一功能的一种方案。

 1     /*
 2      * Function  :    获取联系人列表信息
 3      * Author    :    博客园-依旧淡然
 4      */
 5     public String getResult() {
 6         
 7         String result = "";
 8         Uri uri = Uri.parse("content://contacts/people");        //联系人Content Provider的URI
 9         String[] columns = {People._ID, People.NAME};            //联系人的ID和Name
10         
11         ContentResolver contentResolver = getContentResolver();     //获取ContentResolver对象
12         Cursor cursor = contentResolver.query(uri, columns, null, null, null);    //查询Content Provider
13             int peopleId = cursor.getColumnIndex(People._ID);       //获得ID字段的列索引
14             int peopleName = cursor.getColumnIndex(People.NAME);    //获得Name字段的列索引
15             
16         //遍历Cursor对象,提取数据
17         for(cursor.moveToFirst(); (!cursor.isAfterLast()); cursor.moveToNext()) {
18             result = result + cursor.getString(peopleId) + "\t\t";
19             result = result + cursor.getString(peopleName) + "\t\n";
20         }
21         cursor.close();
22         return result;
23     }

  通过以上的代码可以看出,要获取Content Provider内容,我们需要知道Content Provider的URI以及Content Provider的数据存储形式(字段名称和字段类型)。然后,我们便可以通过使用ContentResolver对象的query()方法对Content Provider进行查询了,查询的结果需要使用Cursor对象存储(有关Cursor的介绍可以参阅《Android学习笔记36:使用SQLite方式存储数据》一文)。最后遍历Cursor对象,取出各个字段的信息即可。

  因为该应用程序需要访问联系人信息,所以还需要在AndroidManifest.xml文件中加入相应的权限许可,具体如下:

  <uses-permission android:name="android.permission.READ_CONTACTS"/>

  至此,我们便完成了获取联系人Content Provider内容的功能。

 

3.提供Content Provider内容

  上面介绍了如何从别的应用程序中获取Content Provider内容,那么如何在自己的应用程序中提供Content Provider内容供别的应用程序访问呢?

  一般来说,让自己的数据被别的应用程序访问有两种方式:创建自己的Content Provider(即继承自Content Provider的子类),或者是将自己的数据加入到已有的Content Provider中去。将自己的数据加入到已有的Content Provider中去有一定的局限性,因为要保证自己的数据和现有的Content Provider数据类型相同,并且具有该Content Provider的写入权限。

  下面将说说如何创建一个自己的Content Provider,大致可以分为3个步骤。

3.1建立数据的存储系统

  很显然,要将自己应用程序中的数据共享给他人,肯定需要建立自己的数据存储系统。当然了,选择什么样的存储系统(文件存储系统、SQLite数据库等)完全由开发者决定。

  在《Android学习笔记36:使用SQLite方式存储数据》一文中,我们搭建了一个简单的SQLite数据库系统。在该数据库中,我们新建了一张具有3个字段(studentId、studentName、studentAge)的表,用来存储学生信息。

  这里,我们就以该工程为例,讲讲如何将该工程中的学生信息通过Content Provider方式共享出去。

3.2扩展Content Provider类

  在该工程中,我们需要新建了一个继承自ContentProvider的类。用来将要共享的数据进行包装并以ContentResolver对象和Cursor对象能够访问的形式对外展示。这里,我将这个类命名为了“StudentContentProvider”。

  在ContentProvider类中提供了6个抽象方法,分别为:

  (1)public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);

  (2)public abstract Uri insert (Uri uri, ContentValues values);

  (3)public abstract int update (Uri uri, ContentValues values, String selection, String[] selectionArgs);

  (4)public abstract int delete (Uri uri, String selection, String[] selectionArgs);

  (5)public abstract String getType (Uri uri);

  (6)public abstract boolean onCreate ();

  其中,query()方法用于将查询到的数据以Cursor对象的形式返回;insert()方法用于向Content Provider中插入新数据记录,该方法中的第二个参数ContentValues对象表示数据记录的列名和列值的映射;update()方法用于更新Content Provider中的已存在的数据记录;delete()方法用于从Content Provider中删除数据记录;getType()方法用于返回Content Provider中数据的(MIME)类型;onCreate()方法当Content Provider启动时被调用。

  以上的6个方法将会在ContentResolver对象中被调用,所以很好的实现这些抽象方法就会为ContentResolver提供一个完善的外部接口。

  当然了,你可以根据自己应用程序的需要,有选择的实现上述6个方法,比如,你可以只实现query()方法,这样别的应用程序就自能对你提供的Content Provider进行查询操作,而无法对你提供的Content Provider进行添加、删除等操作,从而保证了数据的安全性。

  如下的代码实现了query()方法。

1     /*
2      * Function  :    查询方法
3      * Author    :    博客园-依旧淡然
4      */
5     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
6         db = mySQLiteOpenHelper.getWritableDatabase();
7         Cursor cursor = db.query("tab_student", projection, selection, selectionArgs, null, null, sortOrder);
8         return cursor;
9     }

  可以看出,查询的核心其实还是调用SQLiteDatabase类提供的query()方法,将查询到的结果存储在Cursor对象中,最后直接将Cursor对象返回即可。

  此外,在继承自ContentProvider类的“StudentContentProvider”中,我们还需要做一件很重要的事,那就是指定Content Provider的URI。该URI必须是唯一的,不能和系统的URI相同,更不能与其他应用程序提供的Content Provider的URI相同。

  这里我定义了Content Provider的URI为“content://com.example.sqlite.studentProvider/student”。

3.3声明Content Provider的权限

  创建好的Content Provider必须在应用程序的AndroidManifest.xml文件中进行声明,否则,该Content Provider对于Android系统是不可见的。

  具体的声明方式如下:

1     <!-- 声明内容提供者 -->
2     <provider                    
      android:name="com.example.android_datastorage_sqlite.provider.StudentContentProvider" 3 android:authorities="com.example.sqlite.studentProvider" > 4 </provider>

  其中,<provider></provider>标签位于<application></application>标签下。android:name属性用于指明StudentContentProvider的全称类名,android:authorities属性唯一的标识了一个Content Provider。

  至此,我们便在该工程中创建了自己的Content Provider,并提供了query()方法供别的应用程序查询该工程中的SQLite数据表。

3.4验证

  在应用程序Android_DataStorage_SQLite的SQLite数据表中,我们添加了3条记录,如图4所示。


图4 SQLite数据表信息

  新建一个应用程序Android_DataStorage_ContentProviders,用来获取自定义的Content Provider内容。获取自定义的Content Provider内容的方法,和前面讲的获取联系人应用程序的Content Provider内容的方法类似。使用自定义的Content Provider的URI,并查询数据表中相应的字段即可。可以看到查询到的信息如图5所示。

图5 查询自定义的Content Provider内容

  可以看出,在应用程序Android_DataStorage_ContentProviders中确实访问到了应用程序Android_DataStorage_SQLite中的数据表信息,通过Content Provider方式实现了数据在应用程序之间的共享。

posted @ 2013-04-17 23:34  依旧淡然  阅读(2907)  评论(1编辑  收藏  举报