读前思考
学习一门技术或者看一篇文章最好的方式就是带着问题去学习,这样才能在过程中有茅塞顿开、灯火阑珊的感觉,记忆也会更深刻。
- 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,规则如下。- *:表示匹配任意长度的任意字符
- #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*复制代码
而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#复制代码
什么是 ContentProvider ?
作为四大组件之一,ContentProvider 主要负责存储和共享数据。与文件存储、SharedPreferences 存储、SQLite 数据库存储这几种数据存储方法不同的是,后几者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。
ContentProvider 有两种形式:
- 可以使用现有的内容提供者来读取和操作相应程序中的数据。
- 也可以创建自己的内容提供者给这个程序的数据提供外部访问接口。
从系统提供的 Provider 访问数据
例子:读取联系人的电话
1.清单文件中添加读取权限
复制代码
- 针对 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( ) 方法获取权限申请返回
@Overridepublic 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****429com.keven.jianshu I/TAG: 姓名:井** 电话:+861836****299com.keven.jianshu I/TAG: 姓名:张** 电话:+861866****830com.keven.jianshu I/TAG: 姓名:钉** 电话:0105****898com.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() 实现类似)
@Overridepublic 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..。
所以,对于 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 可以完善为
@Overridepublic 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;}复制代码
- 在清单文件中注册
复制代码
- name:ContentProvider 的全称类名。
- authorities:唯一标识了一个 ContentProvider,外部应用通过该属性值来访问我们的 ContentProvider。因此该属性值必须是唯一的,建议在命名时以包名为前缀。
- exported:表明是否允许其他应用调用 ContentProvider,true 表示支持,false 表示不支持。默认值根据开发者的属性设置而会有所不同,如果包含 Intent-Filter 则默认值为 true,否则为 false。
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver 来访问我们程序中的数据。
文章已经读到末尾了,不知道最初的几个问题你都会了吗?如果不会的话?可以再针对不会的问题进行精读哦!答案都在文中,相信你肯定可以解决的!