前言

ContentProvider 是 Android 的四大组件之一,有时候我们需要操作其他应用程序的一些数据,就会用到 ContentProvider,ContentProvider 本质上是一个中间者,真正 存储和操作数据 的数据源还是原来存储数据的方式,如数据库、文件或网络等。

ContentProvider 以相对安全的方式封装了数据并提供简易的处理机制和统一的访问接口供其他程序调用。它的底层采用了 Binder 机制来实现,ContentProvider 为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用进行增删改查,而不用担心因为直接开放数据库权限而带来的安全问题。当然,ContentProvider不仅可以实现跨进程通信,也可实现进程内的通信。

在 Android 中,为一些常见的数据提供了默认的 ContentProvider,如通讯录等。

ContentProvider 是一个抽象类,如果我们需要自定义内容提供者我们就需要继承 ContentProvider 类并复写它的方法,如下:

class MyContentProvider : ContentProvider() {override fun onCreate(): Boolean {// 在创建 ContentProvider 时使用,通常会在这里完成对数据库的创建和升级等操作,返回true表示初始化成功,返回false则表示失败。}override fun query(uri: Uri,projection: Array<out String>?,selection: String?,selectionArgs: Array<out String>?,sortOrder: String?): Cursor? {// 用于查询指定 uri 的数据,返回一个 Cursor}override fun getType(uri: Uri): String? {// 用于返回指定的 Uri 中的数据 MIME 类型}override fun insert(uri: Uri, values: ContentValues?): Uri? {// 用于向指定uri的 ContentProvider 中添加数据}override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {// 用于删除指定 uri 的数据}override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {// 用户更新指定 uri 的数据}
}

注意:访问数据的方法 insert,delete 和 update 可能被多个线程同时调用,操作数据时,务必要保证线程是安全的

在了解 ContentProvider 使用之前,我们需要对其涉及到的一些概念有一定的了解,如 URI、MIME 等。

URI

URI,Universal Resource Identifier,统一资源定位符。其它应用可以通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据。

URI 为系统中的每一个资源赋予一个名字,比方说通话记录。

每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。

URI 的格式如下:

  • scheme,标准前缀,一般就是 content://
  • host:port,URI 的标识,如:com.yang.provider.myprovider 。用于标识 ContentProvider,外部调用者可根据标识来找到它。标识必须是完整的小写的类名。一般是 包.类
  • path,表示要操作的数据库中表的名字,也可以自己定义,记得在使用的时候保持一致就可以了
  • query,表示要查询的表中的某条索引对应的数据。如果不写这个参数,就表示返回表中全部数据

URI 的示例如下:

// 规则
[scheme:][//host:port][path][?query]
// 示例
content://com.yang.provider.myprovider/tablename/id

MIME

MIME 是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看 PDF 格式的文件,浏览器会选择合适的应用来打开一样。

Android ContentProvider 会根据 URI 来返回 MIME 类型,ContentProvider 会返回一个包含两部分的字符串。

MIME 类型一般包含两部分,如:

text/html
text/css
text/xml
application/pdf

分为类型和子类型,Android 遵循类似的约定来定义MIME类型,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。

  • 集合记录(dir):vnd.android.cursor.dir/自定义
  • 单条记录(item):vnd.android.cursor.item/自定义

vnd 表示这些类型和子类型具有非标准的供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写。

在使用 Intent 时,也会用到 MIME,根据 Mimetype 打开符合条件的活动。

ContentProvider 中,我们可以根据 URI 返回MIME类型,代码如下:

val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
val type: String? = requireActivity().contentResolver.getType(uri)

UriMatcher and ContentUris

Uri 代表要操作的数据,在开发过程中对数据进行获取时需要解析 Uri 。

Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。

UriMatcher

UriMatcher 类用于匹配 Uri,它的使用步骤如下:

**步骤一:**将需要匹配的Uri路径进行注册,代码如下:

// 常量 UriMatcher.NO_MATCH 表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 如果 match() 方法匹配 content://com.wang.provider.myprovider/tablename 路径,返回匹配码为 1
sMatcher.addURI("content://com.yang.provider.myprovider", " tablename ", 1);
// 如果 match() 方法匹配 content://com.wang.provider.myprovider/tablename/11 路径,返回匹配码为 2
sMatcher.addURI("com.yang.provider.myprovider", "tablename/#", 2);

路径后面的 id 采用了通配符形式 # ,表示只要前面三个部分都匹配上就算是匹配成功。

**步骤二:**使用 sMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码,即 addURI() 方法的第三个参数

int code = sMatcher.match(Uri.parse("content://com.yang.provider.myprovider/tablename/100"))
switch (code) {case 1:// match 1, todo somethingbreak;case 2// match 2, todo somethingbreak;default:// match nothing, todo somethingbreak;
}

ContentUris

ContentUris 类用于操作 Uri 路径后面的 ID 部分,它有两个比较实用的方法:

  • withAppendedId(Uri uri, long id) :用于为路径加上 ID 部分,如:
// 生成后的 Uri 为:content://com.yang.provider.myprovider/user/7
Uri uri = Uri.parse("content://com.yang.provider.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
  • parseId(Uri uri):用于从路径中获取 ID 部分,如:
Uri uri = Uri.parse("com.yang.provider.myprovider/user/7")
// 获取的结果为 7
long personid = ContentUris.parseId(uri);

ContentResolver

其他 app 或进程想要操作 ContentProvider,需要先获取其相应的 ContentResolver,再利用 ContentResolver 类来完成对数据的增删改查操作。

为什么要使用通过 ContentResolver 类从而与 ContentProvider 类进行交互,而不直接访问ContentProvider类呢?ContentResolver 类是对所有的 ContentProvider 进行统一管理的,这样,调用者就不必了解每个不同的 ContentProvider 的实现,只需要关注如何通过 ContentResolver 操作 ContentProvider 就行了。

使用 ContentResolver 对 ContentProvider 中的数据进行操作的代码如下:

val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")// 构建 ContentResolver
val resolver: ContentResolver = requireActivity().contentResolver// 添加一条记录
resolver.insert(uri, ContentValues().apply {put("name", "yang")put("age", 18)
})// 获取 tablename 表中所有记录
val cursor = resolver.query(uri, null, null, null, "tablename data")
while (cursor!!.moveToNext()) {// todo use data..
}// 把 id 为 1 的记录的 name 字段值更改新为 zhang
val updateIdUri = ContentUris.withAppendedId(uri, 1)
resolver.update(updateIdUri, ContentValues().apply {put("name", "zhang1")
}, null, null)// 删除 id 为 2 的记录
val deleteIdUri = ContentUris.withAppendedId(uri, 2)
resolver.delete(deleteIdUri, null, null)

如果 ContentProvider 的访问者需要知道数据发生的变化,可以在 ContentProvider 发生数据变化时调用如下代码通知注册在此 URI 上的访问者:

val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")
// notifyChange 方法的第二个参数为 observer对象,如果传 null 表示发送消息给任何人
requireActivity().contentResolver.notifyChange(uri, null)

同时,访问者需使用 ContentObserver 对数据进行监听:

private val observer = object : ContentObserver(null) {override fun onChange(selfChange: Boolean, uri: Uri?) {// todo}
}
private val uri = Uri.parse("content://com.yang.provider.myprovider/tablename")// 注册监听
// 其中,第二个参数为 false 表示精确匹配,即只匹配该Uri;为 true 表示可以同时匹配其派生的 Uri
requireActivity().contentResolver.registerContentObserver(uri, true, observer)
// 解除注册
requireActivity().contentResolver.unregisterContentObserver(observer)

代码演示

进程内通信 Demo 演示

步骤一:创建数据库类,数据库中存在两个表,分别是 user 和 job 表。

class DBHelper(context: Context
) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {override fun onCreate(db: SQLiteDatabase) {// 创建两个表格:用户表 和 职业表db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")db.execSQL("CREATE TABLE IF NOT EXISTS $JOB_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, job TEXT)")}override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}companion object {// 数据库名private const val DATABASE_NAME = "finch.db"// 表名const val USER_TABLE_NAME = "user"const val JOB_TABLE_NAME = "job"//数据库版本号private const val DATABASE_VERSION = 1}
}

步骤二:创建一个 MyContentProvider ,继承自 ContentProvider 抽象类。其中,在 onCreate() 方法中,先对数据库初始化,并往数据库中的 user 表和 job 表分别添加两条数据。

class MyContentProvider : ContentProvider() {private lateinit var mDbHelper: DBHelperprivate lateinit var db: SQLiteDatabaseprivate val mMatcher: UriMatcher by lazy {val matcher = UriMatcher(UriMatcher.NO_MATCH)matcher.addURI(AUTHORITY, "user", User_Code)matcher.addURI(AUTHORITY, "job", Job_Code)matcher}override fun onCreate(): Boolean {mDbHelper = DBHelper(context!!)db = mDbHelper.writableDatabase// 初始化两个表的数据 (先清空两个表,再各加入一个记录)db.execSQL("delete from user")db.execSQL("insert into user values(1,'yang');")db.execSQL("insert into user values(2,'zhang');")db.execSQL("delete from job")db.execSQL("insert into job values(1,'Android');")db.execSQL("insert into job values(2,'iOS');")return true}override fun insert(uri: Uri, values: ContentValues?): Uri {val table = getTableName(uri)db.insert(table, null, values)// 通知外部调用者数据发生变化context?.contentResolver?.notifyChange(uri, null)return uri}override fun query(uri: Uri, projection: Array<String>?, selection: String?,selectionArgs: Array<String>?, sortOrder: String?): Cursor? {val table = getTableName(uri)return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null)}override fun update(uri: Uri, values: ContentValues?, selection: String?,selectionArgs: Array<String>?): Int {return 0}override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {return 0}override fun getType(uri: Uri): String? {return null}private fun getTableName(uri: Uri): String? {var tableName: String? = nullwhen (mMatcher.match(uri)) {User_Code -> tableName = DBHelper.USER_TABLE_NAMEJob_Code -> tableName = DBHelper.JOB_TABLE_NAME}return tableName}companion object {private const val AUTHORITY = "com.yang.provider.myprovider"const val User_Code = 1const val Job_Code = 2}
}

步骤三:在 androidManifest.xml 中注册 MyContentProvider

<providerandroid:name=".fragment.MyContentProvider"android:authorities="com.yang.provider.myprovider" />

步骤四:进程内使用 ContentResolver 操作 ContentProvider。

private fun operateData() {val userUri = Uri.parse("content://com.yang.provider.myprovider/user")val jobUri = Uri.parse("content://com.yang.provider.myprovider/job")// 向 user 表插入一条数据contentResolver.insert(userUri, ContentValues().apply {put("_id", 3)put("name", "chen")})// 查询 user 表数据val cursor = contentResolver.query(userUri, arrayOf("_id", "name"), null, null, null)while (cursor!!.moveToNext()) {println("query user:" + cursor.getInt(0) + " " + cursor.getString(1))}// 主动关闭游标cursor.close()// 向 job 表插入一条数据contentResolver.insert(jobUri, ContentValues().apply {put("_id", 3)put("job", "Web")})// 查询 job 表数据val cursor2 = contentResolver.query(jobUri, arrayOf("_id", "job"), null, null, null)while (cursor2!!.moveToNext()) {println("query job:" + cursor2.getInt(0) + " " + cursor2.getString(1))}cursor2.close()
}// 执行 operateData() 方法,输出结果如下:
I/System.out: query user:1 yang
I/System.out: query user:2 zhang
I/System.out: query user:3 chen
I/System.out: query job:1 Android
I/System.out: query job:2 iOS
I/System.out: query job:3 Web

跨进程通信 Demo 演示

步骤一:创建数据库类,数据库中存在两个表,分别是 user 和 job 表。

class DBHelper(context: Context
) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {override fun onCreate(db: SQLiteDatabase) {// 创建两个表格:用户表 和 职业表db.execSQL("CREATE TABLE IF NOT EXISTS $USER_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")db.execSQL("CREATE TABLE IF NOT EXISTS $JOB_TABLE_NAME(_id INTEGER PRIMARY KEY AUTOINCREMENT, job TEXT)")}override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}companion object {// 数据库名private const val DATABASE_NAME = "finch.db"// 表名const val USER_TABLE_NAME = "user"const val JOB_TABLE_NAME = "job"//数据库版本号private const val DATABASE_VERSION = 1}
}

步骤二:创建一个 MyContentProvider ,继承自 ContentProvider 抽象类。其中,在 onCreate() 方法中,先对数据库初始化,并往数据库中的 user 表和 job 表分别添加两条数据。

class MyContentProvider : ContentProvider() {private lateinit var mDbHelper: DBHelperprivate lateinit var db: SQLiteDatabaseprivate val mMatcher: UriMatcher by lazy {val matcher = UriMatcher(UriMatcher.NO_MATCH)matcher.addURI(AUTHORITY, "user", User_Code)matcher.addURI(AUTHORITY, "job", Job_Code)matcher}override fun onCreate(): Boolean {mDbHelper = DBHelper(context!!)db = mDbHelper.writableDatabase// 初始化两个表的数据 (先清空两个表,再各加入一个记录)db.execSQL("delete from user")db.execSQL("insert into user values(1,'yang');")db.execSQL("insert into user values(2,'zhang');")db.execSQL("delete from job")db.execSQL("insert into job values(1,'Android');")db.execSQL("insert into job values(2,'iOS');")return true}override fun insert(uri: Uri, values: ContentValues?): Uri {val table = getTableName(uri)db.insert(table, null, values)// 通知外部调用者数据发生变化context?.contentResolver?.notifyChange(uri, null)return uri}override fun query(uri: Uri, projection: Array<String>?, selection: String?,selectionArgs: Array<String>?, sortOrder: String?): Cursor? {val table = getTableName(uri)return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null)}override fun update(uri: Uri, values: ContentValues?, selection: String?,selectionArgs: Array<String>?): Int {return 0}override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {return 0}override fun getType(uri: Uri): String? {return null}private fun getTableName(uri: Uri): String? {var tableName: String? = nullwhen (mMatcher.match(uri)) {User_Code -> tableName = DBHelper.USER_TABLE_NAMEJob_Code -> tableName = DBHelper.JOB_TABLE_NAME}return tableName}companion object {private const val AUTHORITY = "com.yang.provider.myprovider"const val User_Code = 1const val Job_Code = 2}
}

步骤三:在 androidManifest.xml 中注册 MyContentProvider

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="xxx.xxxx"><!--声明使用 MyContentProvider 的通信的权限 (开放所有权限) --><permissionandroid:name="com.yang.provider.myprovider.PROVIDER"android:protectionLevel="normal" /><!--声明使用 MyContentProvider 的通信的权限 (开放读权限) --><permissionandroid:name="com.yang.provider.myprovider.Read"android:protectionLevel="normal" /><!--声明使用 MyContentProvider 的通信的权限 (开放写权限) --><permissionandroid:name="com.yang.provider.myprovider.Write"android:protectionLevel="normal" /><application><!--通过 android:permission、readPermission、writePermission 等属性定义 MyContentProvider 的通信权限--><providerandroid:name=".MyContentProvider"android:authorities="com.yang.provider.myprovider"android:exported="true"android:permission="com.yang.provider.myprovider.PROVIDER"// android:readPermission="com.yang.provider.myprovider.Read"// android:writePermission="com.yang.provider.myprovider.Write"/></application>
</manifest>

至此,创建进程一的代码编写完毕。

接下来继续编写进程二的代码,步骤如下。

步骤一:声明访问进行 1 中的 ContentProvider 所需要的权限

<!--
声明本应用可允许通信的权限(全权限)
另外,还可以单独声明读或写权限,如:
<uses-permission android:name="com.yang.provider.myprovider.Read" />
<uses-permission android:name="com.yang.provider.myprovider.Write" />
注:声明的权限必须与进程1中设置的权限对应
-->
<uses-permission android:name="com.yang.provider.myprovider.PROVIDER" />

步骤二:使用 ContentResolver 操作 进程 1 的 ContentProvider。

private fun operateData() {val userUri = Uri.parse("content://com.yang.provider.myprovider/user")val jobUri = Uri.parse("content://com.yang.provider.myprovider/job")// 对 user 表进行操作contentResolver.insert(userUri, ContentValues().apply {put("_id", 4)put("name", "huang")})val cursor = contentResolver.query(userUri, arrayOf("_id", "name"), null, null, null)while (cursor!!.moveToNext()) {println("ipc query user:" + cursor.getInt(0) + " " + cursor.getString(1))}cursor.close()// 对 job 表进行操作contentResolver.insert(jobUri, ContentValues().apply {put("_id", 4)put("job", "algorithm")})val cursor2 = contentResolver.query(jobUri, arrayOf("_id", "job"), null, null, null)while (cursor2!!.moveToNext()) {println("ipc query job:" + cursor2.getInt(0) + " " + cursor2.getString(1))}cursor2.close()
}// 执行 operateData() 方法,输出结果如下:
I/System.out: ipc query user:1 yang
I/System.out: ipc query user:2 zhang
I/System.out: ipc query user:4 huang
I/System.out: ipc query job:1 Android
I/System.out: ipc query job:2 iOS
I/System.out: ipc query job:4 algorithm

【Android】四大组件之 ContentProvider相关推荐

  1. Android四大组件之ContentProvider 全面解析,ContentResolver源码解析如何调用其它APP的ContentProvider

    今天来总结下Android中的ContentProvider(以下简称CP),具体代码请见https://github.com/Mangosir/ContentProviderReview/tree/ ...

  2. 戏说Android四大组件之ContentProvider

    戏说江湖静如水,游荡江湖才有情.我就是江湖中的一个戏子. 俗话说,入行先入门.作为一名android学习者,四大组件是android中的核心组件,岂有不学之理.然而,本人才疏学浅,叙述略有不当之处,敬 ...

  3. Android四大组件之ContentProvider详解

    1. 为什么需要内容提供者contentProvider? 为不同的应用之间数据共享提供统一的访问接口,内容提供者的作用 把私有的数据给暴露出来 2. 内容提供者原理? 原理:可以把ContentPr ...

  4. Android四大组件之ContentProvider

    1.ContentProvider定义 这里通过一个实际的例子来说明ContentProvider(内容提供者)是什么,作用是什么 短信应用要访问通讯录应用中的数据,是不能直接访问的,应用通讯录的中的 ...

  5. Android四大组件之ContentProvider(二)读取设备上的图片、音频和视频

    Android系统提供了MediaScanner,MediaProvider,MediaStore等接口,通过Content Provider的方式提供给用户.当设备开机或者有SD卡插拔等事件发生时, ...

  6. Android 四大组件之ContentProvider 访问通讯录进行增删改查操作

    博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家,

  7. Android——四大组件、六大布局、五大存储

    一.android四大组件 (一)android四大组件详解 Android四大组件分别为activity.service.content provider.broadcast receiver. 1 ...

  8. Android 四大组件 与 MVC 架构模式

    作为一个刚从JAVA转过来的Android程序员总会思考android MVC是什么样的? 首先,我们必须得说Android追寻着MVC架构,那就得先说一下MVC是个啥东西! 总体而来说MVC不能说是 ...

  9. android四大组件小整

    原文来自http://www.jianshu.com/p/478a34af17df 所谓的android四大组件一次是Activity.Service.BroadcastReceiver和Conten ...

  10. Android添加手机黑名单,手机来电拦截实现详解与Demo,一个不错的练手项目,涵盖Android四大组件。

    简介 这是一个小应用的详解,这个应用可以添加手机黑名单,拦截手机黑名单的来电.通过这个小demo,我们可以对Android四大组件的应用场景有个具体的了解,可以说是一个不错的练手项目. 下面给出下载地 ...

最新文章

  1. 针对《评人工智能如何走向新阶段》一文,继续发布国内外的跟贴留言第二部552-556条
  2. BugKuCTF WEB 成绩单
  3. Android官方开发文档Training系列课程中文版:通知用户之在通知中显示进度
  4. 听障学生计算机课本,面向听障学生程序设计的计算机教学辅助系统
  5. Behavior Targeting - 技术研究
  6. iOS播放器 - AVPlayer
  7. Silverlight 里获取摄像头视频
  8. Python argv小结
  9. 【数据结构】--章节2.3----线性表的链式表示和实现
  10. 麦子学院python百度云_麦子学院python
  11. 三星4k3d电视测试软件,三大硬性指标 揭开伪4K电视真实面目
  12. 直播卖货流程思维导图
  13. Web前端大作业 HTML+CSS+JS 防天天生鲜官网 9页
  14. 2021年超全微博营销全攻略抢先看!
  15. 曲苑杂坛--DML操作中如何处理那些未提交的数据
  16. Matlab识别拨号音,电话拨号音识别全解.ppt
  17. C++小系统——餐馆员工管理系统及餐馆点菜系统(一)
  18. 安卓下微信内置浏览器视频出现解析错误
  19. Window系统电脑登录过个微信方法,微信多开(实测)
  20. 报盘、还盘的英语短句(建议收藏)

热门文章

  1. 【讲坛实录】知识图谱的探索与应用
  2. 蓝桥杯Python题目(一)
  3. 洗衣机(Washing machine)
  4. SpringBoot @Scheduled注解(cron、fixedRate、fixedDelay、initialDelay)各个参数区别
  5. java 获取当前年_Java获取当前时间的年月日方法
  6. 汽车电子行业入门指南「汽车行业的英语要求与学习方法」
  7. 深度学习与围棋:神经网络入门
  8. 磁盘阵列RAID理解
  9. 企业呼叫中心中的隐性成本
  10. python 制作影视动画、电影特效工具