前言
本篇博客讲讲ContentProvider,内容提供者。前面已经讲过了数据持久化,但是除了共享内存(SDCard)的数据外,其他包括SQLite、SharedPreferences都是仅限于被当前所创建的应用访问,而无法使它们的数据在应用程序之间交换数据,所以Android提供了ContentProvider,ContentProvider是不同应用程序之间进行数据交换的标准API。虽然Android附带了需要有用的内容提供者,但是本片博客不涉及这方面的内容,而是专注讲解如何创建自己的ContentProvider,并在其他应用中如何调用。
概述
ContentProvider可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。其他的Android应用可以使用ContentResolver对象通过与ContentProvider同名的方法请求执行,被执行的就是ContentProvider中的同名方法。所以ContentProvider很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的,如图:
Uri
在Android中,Uri是一种比较常见的资源访问方式。而对于ContentProvider而言,Uri也是有固定格式的:
<srandard_prefix>://<authority>/<data_path>/<id>
- <srandard_prefix>:ContentProvider的srandard_prefix始终是content://。
- <authority>:ContentProvider的名称。
- <data_path>:请求的数据类型。
- <id>:指定请求的特定数据。
ContentProvider
ContentProvider也是Android应用的四大组件之一,所以也需要在AndroidManifest.xml文件中进行配置。而且某个应用程序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可以通过这个接口来操作它的内部数据。
Android附带了许多有用的ContentProvider,但是本篇博客不会涉及到这些内容的,以后有时间会再讲解。Android附带的ContentProvider包括:
- Browser:存储如浏览器的信息。
- CallLog:存储通话记录等信息。
- Contacts:存储联系人等信息。
- MediaStore:存储媒体文件的信息。
- Settings:存储设备的设置和首选项信息。
在Android中,如果要创建自己的内容提供者的时候,需要扩展抽象类ContentProvider,并重写其中定义的各种方法。然后在AndroidManifest.xml文件中注册该ContentProvider即可。
ContentProvider是内容提供者,实现Android应用之间的数据交互,对于数据操作,无非也就是CRUD而已。下面是ContentProvider必须要实现的几个方法:
- onCreate():初始化提供者。
- query(Uri, String[], String, String[], String):查询数据,返回一个数据Cursor对象。
- insert(Uri, ContentValues):插入一条数据。
- update(Uri, ContentValues, String, String[]):根据条件更新数据。
- delete(Uri, String, String[]):根据条件删除数据。
- getType(Uri) 返回MIME类型对应内容的URI。
除了onCreate()和getType()方法外,其他的均为CRUD操作,这些方法中,Uri参数为与ContentProvider匹配的请求Uri,剩下的参数可以参见SQLite的CRUD操作,基本一致,SQLite的内容在另外一篇博客中有讲解:Android--数据持久化之SQLite。、
Tips:还有两个非常有意思的方法,必须要提一下,call()和bulkInsert()方法,使用call,理论上可以在ContentResolver中执行ContentProvider暴露出来的任何方法,而bulkInsert()方法用于插入多条数据。
在ContentProvider的CRUD操作,均会传递一个Uri对象,通过这个对象来匹配对应的请求。那么如何确定一个Uri执行哪项操作呢?需要用到一个UriMatcher对象,这个对象用来帮助内容提供者匹配Uri。它所提供的方法非常简单,仅有两个:
- void addURI(String authority,String path,int code):添加一个Uri匹配项,authority为AndroidManifest.xml中注册的ContentProvider中的authority属性;path为一个路径,可以设置通配符,#表示任意数字,*表示任意字符;code为自定义的一个Uri代码。
- int match(Uri uri):匹配传递的Uri,返回addURI()传递的code参数。
在创建好一个ContentProvider之后,还需要在AndroidManifest.xml文件中对ContentProvider进行配置,使用一个<provider.../>节点,一般只需要设置两个属性即可访问,一些额外的属性就是为了设置访问权限而存在的,后面会详细讲解:
- android:name:provider的响应类。
- android:authorities:Provider的唯一标识,用于Uri匹配,一般为ContentProvider类的全名。
下面通过一个示例来讲解一下ContentProvider,在这个例子中,需要用到SQLite数据库来存储数据,定义了一个StudentDAO类,用于进行对SQLite的CRUD操作,这里就不提供数据访问的源码了,有兴趣的朋友可以在下载源码查看:
ContentProvider实现:
1 package com.example.contentproviderdemo; 2 3 import com.example.dao.StudentDAO; 4 import android.content.ContentProvider; 5 import android.content.ContentUris; 6 import android.content.ContentValues; 7 import android.content.UriMatcher; 8 import android.database.Cursor; 9 import android.net.Uri; 10 import android.os.Bundle; 11 import android.util.Log; 12 13 public class StudentProvider extends ContentProvider { 14 15 private final String TAG = "main"; 16 private StudentDAO studentDao = null; 17 private static final UriMatcher URI_MATCHER = new UriMatcher( 18 UriMatcher.NO_MATCH); 19 private static final int STUDENT = 1; 20 private static final int STUDENTS = 2; 21 static { 22 //添加两个URI筛选 23 URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider", 24 "student", STUDENTS); 25 //使用通配符#,匹配任意数字 26 URI_MATCHER.addURI("com.example.contentproviderdemo.StudentProvider", 27 "student/#", STUDENT); 28 } 29 30 public StudentProvider() { 31 32 } 33 34 @Override 35 public boolean onCreate() { 36 // 初始化一个数据持久层 37 studentDao = new StudentDAO(getContext()); 38 Log.i(TAG, "---->>onCreate()被调用"); 39 return true; 40 } 41 42 @Override 43 public Uri insert(Uri uri, ContentValues values) { 44 Uri resultUri = null; 45 //解析Uri,返回Code 46 int flag = URI_MATCHER.match(uri); 47 if (flag == STUDENTS) { 48 long id = studentDao.insertStudent(values); 49 Log.i(TAG, "---->>插入成功, id="+id); 50 resultUri = ContentUris.withAppendedId(uri, id); 51 } 52 return resultUri; 53 } 54 55 @Override 56 public int delete(Uri uri, String selection, String[] selectionArgs) { 57 int count = -1; 58 try { 59 int flag = URI_MATCHER.match(uri); 60 switch (flag) { 61 case STUDENT: 62 // delete from student where id=? 63 //单条数据,使用ContentUris工具类解析出结尾的Id 64 long id = ContentUris.parseId(uri); 65 String where_value = "id = ?"; 66 String[] args = { String.valueOf(id) }; 67 count = studentDao.deleteStudent(where_value, args); 68 break; 69 case STUDENTS: 70 count = studentDao.deleteStudent(selection, selectionArgs); 71 break; 72 } 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 Log.i(TAG, "---->>删除成功,count="+count); 77 return count; 78 } 79 80 @Override 81 public int update(Uri uri, ContentValues values, String selection, 82 String[] selectionArgs) { 83 int count = -1; 84 try { 85 int flag = URI_MATCHER.match(uri); 86 switch (flag) { 87 case STUDENT: 88 long id = ContentUris.parseId(uri); 89 String where_value = " id = ?"; 90 String[] args = { String.valueOf(id) }; 91 count = studentDao.updateStudent(values, where_value, args); 92 break; 93 case STUDENTS: 94 count = studentDao.updateStudent(values, selection, 95 selectionArgs); 96 break; 97 } 98 } catch (Exception e) { 99 e.printStackTrace(); 100 } 101 Log.i(TAG, "---->>更新成功,count="+count); 102 return count; 103 } 104 105 @Override 106 public Cursor query(Uri uri, String[] projection, String selection, 107 String[] selectionArgs, String sortOrder) { 108 Cursor cursor = null; 109 try { 110 int flag = URI_MATCHER.match(uri); 111 switch (flag) { 112 case STUDENT: 113 long id = ContentUris.parseId(uri); 114 String where_value = " id = ?"; 115 String[] args = { String.valueOf(id) }; 116 cursor = studentDao.queryStudents(where_value, args); 117 break; 118 case STUDENTS: 119 cursor = studentDao.queryStudents(selection, selectionArgs); 120 break; 121 } 122 } catch (Exception e) { 123 e.printStackTrace(); 124 } 125 Log.i(TAG, "---->>查询成功,Count="+cursor.getCount()); 126 return cursor; 127 } 128 129 130 @Override 131 public String getType(Uri uri) { 132 int flag = URI_MATCHER.match(uri); 133 String type = null; 134 switch (flag) { 135 case STUDENT: 136 type = "vnd.android.cursor.item/student"; 137 Log.i(TAG, "----->>getType return item"); 138 break; 139 case STUDENTS: 140 type = "vnd.android.cursor.dir/students"; 141 Log.i(TAG, "----->>getType return dir"); 142 break; 143 } 144 return type; 145 } 146 @Override 147 public Bundle call(String method, String arg, Bundle extras) { 148 Log.i(TAG, "------>>"+method); 149 Bundle bundle=new Bundle(); 150 bundle.putString("returnCall", "call被执行了"); 151 return bundle; 152 } 153 }
在AndroidManifest.xml中<application>节点中增加:
1 <provider 2 android:name=".StudentProvider" 3 android:authorities="com.example.contentproviderdemo.StudentProvider" > 4 </provider>
ContentResolver
ContentResolver,内容访问者。可以通过ContentResolver来操作ContentProvider所暴露处理的接口。一般使用Content.getContentResolver()方法获取ContentResolver对象。上面已经提到ContentResolver的很多方法与ContentProvider一一对应,所以它也存在insert、query、update、delete等方法。
下面就通过一个简单的Demo来演示ContentResolver的使用,这里就不另外新建一个项目了,在原有项目上使用Android JUnit新建一个测试类,用于测试。在另外一个项目中的操作也是一样的。关于JUnit,不了解的朋友可以参见另外一篇博客:JUnit单元测试。
JUnit测试类:
1 package com.example.contentproviderdemo; 2 3 import android.content.ContentResolver; 4 import android.content.ContentValues; 5 import android.database.Cursor; 6 import android.net.Uri; 7 import android.os.Bundle; 8 import android.test.AndroidTestCase; 9 import android.util.Log; 10 11 public class MyTest extends AndroidTestCase { 12 13 public MyTest() { 14 // TODO Auto-generated constructor stub 15 16 } 17 18 public void insert() { 19 ContentResolver contentResolver = getContext().getContentResolver(); 20 Uri uri = Uri 21 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 22 ContentValues values = new ContentValues(); 23 values.put("name", "Demo"); 24 values.put("address", "HK"); 25 Uri returnuir = contentResolver.insert(uri, values); 26 Log.i("main", "-------------->" + returnuir.getPath()); 27 } 28 29 public void delete() { 30 ContentResolver contentResolver = getContext().getContentResolver(); 31 // 删除多行:content://com.example.contentproviderdemo.StudentProvider/student 32 Uri uri = Uri 33 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 34 contentResolver.delete(uri, null, null); 35 } 36 37 public void deletes() { 38 ContentResolver contentResolver = getContext().getContentResolver(); 39 Uri uri = Uri 40 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 41 String where = "address=?"; 42 String[] where_args = { "HK" }; 43 contentResolver.delete(uri, where, where_args); 44 } 45 46 public void update() { 47 ContentResolver contentResolver = getContext().getContentResolver(); 48 Uri uri = Uri 49 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 50 ContentValues values = new ContentValues(); 51 values.put("name", "李四"); 52 values.put("address", "上海"); 53 contentResolver.update(uri, values, null, null); 54 } 55 56 public void updates() { 57 ContentResolver contentResolver = getContext().getContentResolver(); 58 Uri uri = Uri 59 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 60 ContentValues values = new ContentValues(); 61 values.put("name", "王五"); 62 values.put("address", "深圳"); 63 String where = "address=?"; 64 String[] where_args = { "beijing" }; 65 contentResolver.update(uri, values, where, where_args); 66 } 67 68 public void query() { 69 ContentResolver contentResolver = getContext().getContentResolver(); 70 Uri uri = Uri 71 .parse("content://com.example.contentproviderdemo.StudentProvider/student/2"); 72 Cursor cursor = contentResolver.query(uri, null, null, null, null); 73 while (cursor.moveToNext()) { 74 Log.i("main", 75 "-------------->" 76 + cursor.getString(cursor.getColumnIndex("name"))); 77 } 78 } 79 80 public void querys() { 81 ContentResolver contentResolver = getContext().getContentResolver(); 82 Uri uri = Uri 83 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 84 String where = "address=?"; 85 String[] where_args = { "深圳" }; 86 Cursor cursor = contentResolver.query(uri, null, where, where_args, 87 null); 88 while (cursor.moveToNext()) { 89 Log.i("main", 90 "-------------->" 91 + cursor.getString(cursor.getColumnIndex("name"))); 92 } 93 } 94 95 public void calltest() { 96 ContentResolver contentResolver = getContext().getContentResolver(); 97 Uri uri = Uri 98 .parse("content://com.example.contentproviderdemo.StudentProvider/student"); 99 Bundle bundle = contentResolver.call(uri, "method", null, null); 100 String returnCall = bundle.getString("returnCall"); 101 Log.i("main", "-------------->" + returnCall); 102 } 103 104 }
效果演示:
执行insert(),数据库在创建的时候存在初始数据:
执行update():
执行updates():
执行query()
执行delete()
getType()中的MIME
MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型。在ContentProvider中的getType方法,返回的就是一个MIME类型的字符串。如果支持需要使用ContentProvider来访问数据,就上面这个Demo,getType()完全可以只返回一个Null,并不影响效果,但是覆盖ContentProvider的getType方法对于用new Intent(String action, Uri uri)方法启动activity是很重要的,如果它返回的MIME type和activity在<intent filter>中定义的data的MIME type不一致,将造成activity无法启动。这就涉及到Intent和Intent-filter的内容了,以后有机会再说,这里不再详解。
从官方文档了解到,getType返回的字符串,如果URI针对的是单条数据,则返回的字符串以vnd.android.cursor.item/开头;如果是多条数据,则以vnd.adroid.cursor.dir/开头。
访问权限
对于ContentProvider暴露出来的数据,应该是存储在自己应用内存中的数据,对于一些存储在外部存储器上的数据,并不能限制访问权限,使用ContentProvider就没有意义了。对于ContentProvider而言,有很多权限控制,可以在AndroidManifest.xml文件中对<provider>节点的属性进行配置,一般使用如下一些属性设置:
- android:grantUriPermssions:临时许可标志。
- android:permission:Provider读写权限。
- android:readPermission:Provider的读权限。
- android:writePermission:Provider的写权限。
- android:enabled:标记允许系统启动Provider。
- android:exported:标记允许其他应用程序使用这个Provider。
- android:multiProcess:标记允许系统启动Provider相同的进程中调用客户端。
总结
以上就讲解了内容提供者的简单使用,关于内容提供者的内容涉及面很多,一篇博客无法一一说明,这里只是简单的定义一个内容提供者,并且说明如何操作它。这个博客的示例源码中,声明的内容提供者和内容访问者均在一个项目中,可能不太利于大家理解,但是在另外一个项目中的操作,与JUnit类中的操作一致,所以这里就没有另外创建一个项目去说明它,不过源码中有另外一个小项目,只是实现了Insert的操作,有兴趣的朋友对照看一下就会发现其实是一样的。