内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是 Android 实现跨程序共享数据的标准方式。
虽然文件和 SharedPreferences 存储中提供了 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 这两种操作模式,用于供给其他的应用程序访问当前应用的数据,但这两种模式在 Android 4.2 版本中都已被废弃了。为什么呢?因为 Android 官方已经不再推荐使用这种方式来实现跨程序数据共享的功能,而是应该使用更加安全可靠的内容提供器技术。
可能你会有些疑惑,为什么要将我们程序中的数据共享给其他程序呢?当然,这个要视情况而定的,比如说账号和密码这样的隐私数据显然是不能共享给其他程序的,不过一些可以让其他程序进行二次开发的基础性数据,我们还是可以选择将其共享的。例如系统的电话簿程序,它的数据库中保存了很多的联系人信息,如果这些数据都不允许第三方的程序进行访问的话,恐怕很多应用的功能都要大打折扣了。除了电话簿之外,还有短信、媒体库等程序都实现了跨程序数据共享的功能
如果想要访问内容提供器中共享的数据,就一定要借助 ContentResolve类,可以通过 Context 中的 getContentResolver()方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行 CRUD操作,其中 insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。有没有似曾相识的感觉?没错,SQLiteDatabase中也是使用的这几个方法来进行 CRUD 操作的,只不过它们在方法参数上稍微有一些区别
ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个 Uri参数代替,
内容 URI 最标准的格式写法如下:
content://com.example.app.provider/table1content://com.example.app.provider/table2 头部加上协议声明:content://+权限(authority):com.example.app.provider+路径(path):/table1。权限一般是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。路径则是用于对同一应用程序中不同的表做区分的。
在得到了内容 URI 字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:
Uri uri =Uri.parse("content://com.example.app.provider/table1")
以使用这个 Uri 对象来查询 table1 表中的数据了,代码如下所示:
Cursor cursor =getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
下表对使用到的这部分参数进行了详细的解释。
query()方法参数 | 对应 SQL 部分 | 描述 |
uri | from table_name | 指定查询某个应用程序下的某一张表 |
projection | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定 where 的约束条件 |
selectionArgs | - | 为 where 中的占位符提供具体的值 |
orderBy | order by column1, column2 | 指定查询结果的排序方式 |
查询完成后返回的仍然是一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置来遍历Cursor 的所有行,然后再取出每一行中相应列的数据,代码如下所示:
if (cursor != null) {
while(cursor.moveToNext()) {
String column1 =cursor.getString(cursor.getColumnIndex("column1")); int column2 =cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
示例:查询系统联系人
private void readContacts() {
Cursorcursor = null;
try {
// 查询联系人数据
cursor= getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null, null, null); while (cursor.moveToNext()) {
// 获取联系人姓名
StringdisplayName = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
Stringnumber = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER)); contactsList.add(displayName+ "\n" + number);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
if (cursor != null) {
cursor.close();
}
}
}
声明权限: <uses-permissionandroid:name="android.permission.READ_CONTACTS" />创建内容提供器的步骤 :
public classMyProvider extends ContentProvider {
@Override
publicboolean onCreate() { return false;
}
@Override
publicCursor query(Uri uri, String[] projection, String selection, String[]selectionArgs, String sortOrder) { return null;
}
@Override
public Uri insert(Uriuri, ContentValues values) { return null;
}
@Override
publicint update(Uri uri, ContentValues values, String selection, String[]selectionArgs) {
return0;
}
@Override
publicint delete(Uri uri, String selection, String[] selectionArgs) {
return0;
}
@Override
publicString getType(Uri uri) { return null;
}
}
1. onCreate()
初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,
返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在 ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。
2. query()
从内容提供器中查询数据。使用 uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。
3. insert()
向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据
保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
4. update()
更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
5. delete()
从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
6. getType()
根据传入的内容 URI 来返回相应的 MIME 类型。可以看到,几乎每一个方法都会带有 Uri 这个参数,这个参数也正是调用 ContentResolver 的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。
content://com.example.app.provider/table1这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。除此之外,我们还可以在这个内容 URI 的后面加上一个 id,如下所示:
content://com.example.app.provider/table1/1
这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据。
内容 URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容 URI,规则如下。
1. *:表示匹配任意长度的任意字符
2. #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#
接着,我们再借助 UriMatcher 这个类就可以轻松地实现匹配内容 URI 的功能。 UriMatcher 中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改 MyProvider 中的代码,如下所示:
public class MyProvider extends ContentProvider {
public static finalint TABLE1_DIR = 0;
public static finalint TABLE1_ITEM = 1;
public static finalint TABLE2_DIR = 2;
publicstatic final int TABLE2_ITEM = 3;
privatestatic UriMatcher uriMatcher;
static{
uriMatcher= new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider","table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
}
⋯⋯
@Override
publicCursor query(Uri uri, String[] projection, String selection, String[]selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)) { caseTABLE1_DIR:
// 查询table1表中的所有数据 break; case TABLE1_ITEM:
// 查询table1表中的单条数据 break; case TABLE2_DIR:
// 查询table2表中的所有数据 break; case TABLE2_ITEM:
// 查询table2表中的单条数据 break; default:
break;}
⋯⋯
}
⋯⋯
}
可以看到,MyProvider 中新增了四个整型常量,其中 TABLE1_DIR 表示访问 table1 表中的所有数据,TABLE1_ITEM 表示访问 table1 表中的单条数据,TABLE2_DIR 表示访问 table2 表中的所有数据,TABLE2_ITEM 表示访问 table2 表中的单条数据。接着在静态代码块里我们创建了 UriMatcher 的实例,并调用 addURI()方法,将期望匹配的内容 URI 格式传递进去,注意这里传入的路径参数是可以使用通配符的。然后当 query()方法被调用的时候,就会通过 UriMatcher 的 match()方法对传入的 Uri 对象进行匹配,如果发现 UriMatcher 中某个内容 URI 格式成功匹配了该 Uri 对象,则会返回相应的自定义代码,然后我们就可以判断出调用方期望访问的到底是什么数据了。
今天就大致就学习到这。好记性不如烂笔头!