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方式实现了数据在应用程序之间的共享。