读前思考
学习一门技术或者看一篇文章最好的方式就是带着问题去学习,这样才能在过程中有茅塞顿开、灯火阑珊的感觉,记忆也会更深刻。
- ContentProvider 是什么?
- ContentProvider 如何使用?
- ContentProvider 和其他通信方式比有什么区别与优点?
- 什么是 URI ?怎么书写?有什么含义?
认识 URI
通用资源标志符(Universal Resource Identifier, 简称 "URI")
Uri 代表要操作的数据,Android 上可用的每种资源、图像、视频片段等都可以用 Uri 来表示。Uri 唯一标识每种资源。
Uri一般由三部分组成: 1.访问资源的命名机制。 2.存放资源的主机名。 3.资源自身的名称,由路径表示。
android 的 Uri 由以下三部分组成:
"content://"、数据的路径、标示 ID(可选)
一个标准的内容 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/#
什么是 ContentProvider ?
作为四大组件之一,ContentProvider 主要负责存储和共享数据。与文件存储、SharedPreferences 存储、SQLite 数据库存储这几种数据存储方法不同的是,后几者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。
ContentProvider 有两种形式:
- 可以使用现有的内容提供者来读取和操作相应程序中的数据。
- 也可以创建自己的内容提供者给这个程序的数据提供外部访问接口。
从系统提供的 Provider 访问数据
例子:读取联系人的电话
1.清单文件中添加读取权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
- 针对 6.0+ 系统动态权限申请
//判断是否有读取联系人权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
readContacts();
}
- 重写 onRequestPermissionsResult( ) 方法获取权限申请返回
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "获取权限失败!", Toast.LENGTH_SHORT);
}
break;
default:
break;
}
}
- 获取手机通讯录
private void readContacts() {
List contactsList=null;
Cursor cursor=null;
try {
contactsList=new ArrayList();
//查询联系人数据,使用了getContentResolver().query方法来查询系统的联系人的数据
//CONTENT_URI就是一个封装好的Uri,是已经解析过得常量
cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
//对cursor进行遍历,取出姓名和电话号码
if (cursor!=null){
while (cursor.moveToNext()){
//获取联系人姓名
String displayName=cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
));
//获取联系人手机号
String number=cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
));
//把取出的两类数据进行拼接,中间加换行符,然后添加到listview中
contactsList.add(displayName+"\n"+number);
LogUtils.i("姓名:"+displayName+"\n"+"电话:"+number);
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//记得关掉cursor
if (cursor!=null){
cursor.close();
}
}
}
- 最后的结果输出
com.keven.jianshu I/TAG: 姓名:孙**
电话:+86157****429
com.keven.jianshu I/TAG: 姓名:井**
电话:+861836****299
com.keven.jianshu I/TAG: 姓名:张**
电话:+861866****830
com.keven.jianshu I/TAG: 姓名:钉**
电话:0105****898
com.keven.jianshu I/TAG: 姓名:彩**
电话:0108****604
创建自己的 Provider
- 自定义类继承 ContentProvider,重写六个方法
public class Part1dMyProvider extends ContentProvider {
/**
* 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,
* 返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有
* 当存在 ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。
*/
@Override
public boolean onCreate() {
return false;
}
/**
* 从内容提供器中查询数据。使用 uri 参数来确定查询哪张表,projection 参数用
* 于确 定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,
* sortOrder 参数用于 对结果进行排序,查询的结果存放在 Cursor 对象中返回。
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
/**
* 向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据
* 保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
/**
* 更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数
* 据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,
* 受影响的 行数将作为返回值返回。
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
/**
* 从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection
* 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
/**
* 根据传入的内容 URI 来返回相应的 MIME 类型。 可以看到,几乎每一个方法都会
* 带有 Uri 这个参数,这个参数也正是调用 ContentResolver的增删改查方法时传
* 递过来的。而现在,我们需要对传入的 Uri 参数进行解析,从中分析出 调用方
* 期望访问的表和数据。
*/
@Override
public String getType(Uri uri) {
return null;
}
}
- 使用 UriMatcher
public class Part1dMyProvider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher; static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.keven.jianshu.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.keven.jianshu.provider ", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.keven.jianshu.provider ", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.keven.jianshu.provider ", "table2/#", TABLE2_ITEM);
}
......
}
- 以 query( ) 方法为例示范(insert()、update()、delete() 实现类似)
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1表中的所有数据
break;
case TABLE1_ITEM:
// 查询table1表中的单条数据
break;
case TABLE2_DIR:
// 查询table2表中的所有数据
break;
case TABLE2_ITEM:
// 查询table2表中的单条数据
break;
default:
break;
}
……
}
……
}
除此之外,还有一个方法你会比较陌生,即 getType() 方法。它是所有的内容提供器都必 须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。
- 必须以 vnd 开头。
- 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾, 则后接 android.cursor.item/。
- 最后接上 vnd.<authority>.<path>。
所以,对于 content://com.example.app.provider/table1 这个内容 URI,它所对应的 MIME
类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于 content://com.example.app.provider/table1/1 这个内容 URI,它所对应的 MIME 类型 就可以写成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
则我们的自定义 Provider 可以完善为
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver 来访问我们程序中的数据。
文章已经读到末尾了,不知道最初的几个问题你都会了吗?如果不会的话?可以再针对不会的问题进行精读哦!答案都在文中,相信你肯定可以解决的!














网友评论