深入探索Android应用数据共享之ContentProvider


本文将深入探讨Android开发中非常重要的数据共享机制 - ContentProvider。

主要内容包括:

  • ContentProvider的基本定义及特点
  • 如何实现一个自定义的ContentProvider
  • ContentProvider对外提供的功能以及对外部应用的权限控制
  • 对ContentProvider的一些常见使用场景
  • 使用ContentProvider的最佳实践及注意事项

一、什么是ContentProvider?


ContentProvider是Android系统提供的一种在应用之间共享数据的机制,也是 Android 的四大组件之一,可见它在 Android 中的作用非同小可。

其核心思想是通过标准化的接口(增删改查等操作),让应用程序可以安全地访问和操作彼此的数据。相比直接访问文件或者数据库,ContentProvider提供了更加安全和标准化的数据共享方式。

ContentProvider 可以理解为 Android 应用对外开放的接口,只要是符合它所定义的 URI 格式的请求,均可以正常访问执行操作。

Android 应用之间可以使用 ContentResolver 对象通过与 ContentProvider 同名的方法请求执行,被执行的就是 ContentProvider 中的同名方法。


如图:

在这里插入图片描述


二、为什么要使用ContentProvider?


1、数据共享的需求

  • 在实际开发中,经常会遇到多个应用之间需要共享数据的场景,比如通讯录应用需要被其他应用访问。

  • 如果直接通过文件或数据库共享数据,会带来一些问题,比如数据访问权限难以控制,数据格式不统一等。

  • ContentProvider提供了一种标准化的数据共享机制,可以方便地在不同应用之间共享数据。


2、权限控制的需求

  • 数据的安全性是移动应用开发中的重中之重。如果直接暴露数据库或文件,很容易造成数据泄露和被篡改的风险。

  • ContentProvider可以对数据的访问权限进行精细化控制,开发者可以根据需求设置读写权限,充分保护数据安全。

  • 当其他应用请求访问数据时,ContentProvider会进行权限验证,阻止未授权的访问。


3、统一接口的需求

  • 不同的应用可能会采用不同的数据存储方式,比如SQLite、文件系统等。这会给数据访问带来一定的复杂性。
  • ContentProvider为数据访问提供了标准化的CRUD接口,开发者不需要关心底层的数据存储细节,可以专注于业务逻辑的实现。
  • 这种统一的接口不仅简化了开发,也大大提高了代码的可维护性。

4、跨进程通信的需求

  • Android应用是基于进程隔离的,不同应用之间的数据是隔离的。
  • ContentProvider基于Binder机制实现了进程间通信,让应用之间的数据交互更加顺畅。
  • 通过ContentProvider,开发者无需关心进程间通信的底层细节,就可以实现应用间的数据共享。

三、如何实现一个自定义的ContentProvider?


实现一个自定义的ContentProvider需要:

  • 第一步,继承ContentProvider类,并实现6个抽象方法。

  • 第二步,在AndroidManifest.xml中声明ContentProvider及其Authority。

  • 第三步,在其他应用中使用ContentResolver访问ContentProvider提供的数据。


1、首先,我们定义一个名为MyContentProvider的ContentProvider类,它继承自ContentProvider基类:


public class MyContentProvider extends ContentProvider {// 声明Authority,这是ContentProvider的唯一标识符public static final String AUTHORITY = "com.example.mycontentprovider";// 声明Uri常量,用于构建Uripublic static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");// 声明表名常量private static final String TABLE_NAME = "users";// 声明数据库相关对象private SQLiteDatabase db;private MySQLiteOpenHelper dbHelper;@Overridepublic boolean onCreate() {// 初始化数据库dbHelper = new MySQLiteOpenHelper(getContext());db = dbHelper.getWritableDatabase();return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// 执行查询操作return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);}@Overridepublic Uri insert(Uri uri, ContentValues values) {// 执行插入操作long id = db.insert(TABLE_NAMEnull, values);return ContentUris.withAppendedId(CONTENT_URI, id);}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// 执行更新操作return db.update(TABLE_NAME, values, selection, selectionArgs);}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// 执行删除操作return db.delete(TABLE_NAME, selection, selectionArgs);}@Overridepublic String getType(Uri uri) {// 返回MIME类型,这里以"vnd.android.cursor.dir/vnd.com.example.mycontentprovider.users"为例return "vnd.android.cursor.dir/vnd." + AUTHORITY + "." + TABLE_NAME;}
}

2、接下来,我们需要在应用的AndroidManifest.xml文件中声明这个自定义的ContentProvider:


<providerandroid:name=".MyContentProvider"android:authorities="com.example.mycontentprovider"android:exported="true" />

其中,android:authorities属性指定了ContentProvider的Authority,必须与前面定义的AUTHORITY字符串一致。android:exported属性表示ContentProvider是否对其他应用开放,这里设置为true


3、现在,我们可以在其他应用中使用这个自定义的ContentProvider了


比如,在另一个应用中,我们可以通过以下代码访问MyContentProvider中的数据:

// 构建Uri
Uri uri = MyContentProvider.CONTENT_URI;// 执行查询操作
Cursor cursor = getContentResolver().query(uri, nullnullnullnull);// 遍历查询结果
while (cursor.moveToNext()) {int id = cursor.getInt(cursor.getColumnIndex("id"));String name = cursor.getString(cursor.getColumnIndex("name"));// 处理数据Log.d("MyApp""id: " + id + ", name: " + name);
}

在这个示例中,我们首先通过MyContentProvider.CONTENT_URI构建了访问MyContentProvider的Uri。然后,使用getContentResolver().query()方法执行查询操作,并遍历查询结果,获取数据。

类似地,我们还可以使用insert()update()delete()方法对数据进行增删改操作。


四、ContentProvider的常见使用场景


1、访问系统级别的数据

  • Android系统自带了一些ContentProvider,例如Contacts、Media、Calendar等,开发者可以通过这些ContentProvider访问系统级别的数据,如通讯录、图片、日历等。

  • 这些系统级ContentProvider提供了标准化的接口,开发者无需关心底层实现,就可以方便地获取和操作系统数据。


2、实现应用间的数据共享

  • 在实际开发中,经常会有多个应用之间需要共享数据的需求,比如社交应用需要获取用户的联系人信息。
  • 开发者可以自定义ContentProvider,将应用内部的数据暴露给其他应用使用。通过设置合适的读写权限,既可以保证数据安全,又可以实现应用间的数据共享。

3、支持应用内部的数据持久化

  • 除了实现应用间的数据共享,ContentProvider也可以用于应用内部的数据持久化。

  • 例如,开发者可以结合Room或SQLite,将ContentProvider作为应用内部数据库的对外接口,为上层的ViewModel和UI层提供标准化的数据访问方式。


4、配合Android Jetpack实现数据驱动的UI

  • 近年来,随着Android Jetpack的广泛应用,ViewModel和LiveData成为了实现数据驱动型UI的主流方案。
  • 在这种架构中,ContentProvider可以作为数据源,为ViewModel提供标准化的数据访问接口,ViewModel则负责观察数据变化,实时刷新UI。这种设计可以大大提高代码的可维护性和可测试性。

5、实现文件共享

  • ContentProvider不仅可以用于共享结构化数据,也可以用于共享文件数据。

  • 开发者可以自定义ContentProvider,将应用内部的文件资源暴露给其他应用使用,例如实现文件或图片的跨应用共享。


五、使用ContentProvider的最佳实践及注意事项


1、合理设计Authority和Uri ,使用ContentUris管理Uri


  • Authority是ContentProvider的唯一标识符,应该具有唯一性和可读性。通常采用反域名的形式,如"com.example.myprovider"。
  • Uri是用于访问ContentProvider数据的标识,应该遵循一定的命名规范,例如"content://com.example.myprovider/users"。
  • 合理设计Authority和Uri可以提高代码的可读性和可维护性,同时也有利于权限控制。

public class MyContentProvider extends ContentProvider {public static final String AUTHORITY = "com.example.mycontentprovider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");@Overridepublic Uri insert(Uri uri, ContentValues values) {// 执行插入操作long id = db.insert(TABLE_NAMEnull, values);return ContentUris.withAppendedId(CONTENT_URI, id);}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// 判断操作对象是单个记录还是记录集合if (ContentUris.parseId(uri) != -1) {// 查询单个记录selection = BaseColumns._ID + " = ?";selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };}// 执行查询操作return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);}
}

上面示例,我们使用了ContentUris工具类来管理Uri。

insert()方法中,我们使用ContentUris.withAppendedId()方法在基础Uri后附加记录id,生成唯一的Uri。

query()方法中,我们使用ContentUris.parseId()方法解析Uri中的记录id,根据id进行针对性的查询操作。

这种方式可以更好地规范化Uri的使用。


2、合理控制读写权限


  • ContentProvider提供了灵活的权限控制机制,开发者可以针对不同的操作(增删改查)设置不同的权限。
  • 合理控制读写权限可以提高数据的安全性,防止未经授权的访问。同时,也可以根据业务需求,灵活地控制其他应用对数据的访问范围。
public class MyContentProvider extends ContentProvider {public static final String AUTHORITY = "com.example.mycontentprovider";private static final String TABLE_NAME = "users";@Overridepublic boolean onCreate() {// 初始化数据库// ...return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// 检查访问权限if (getContext().checkCallingOrSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Permission denied to access the data");}// 执行查询操作return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);}@Overridepublic Uri insert(Uri uri, ContentValues values) {// 检查访问权限if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Permission denied to access the data");}// 执行插入操作long id = db.insert(TABLE_NAME, null, values);return ContentUris.withAppendedId(CONTENT_URI, id);}// 其他方法的实现类似
}

上面示例中,我们在query()和insert()方法中分别检查了读和写权限。如果调用方没有所需的权限,则抛出SecurityException拒绝访问。这样可以确保数据的安全性。


3、合理使用MIME类型,遵循MIME类型约定


  • MIME类型描述了ContentProvider返回的数据类型,应该根据实际情况设置合理的MIME类型。
  • 正确的MIME类型可以帮助其他应用正确地解析和处理ContentProvider提供的数据。
@Override
public String getType(Uri uri) {// 根据Uri返回对应的MIME类型if (uri.equals(CONTENT_URI)) {return "vnd.android.cursor.dir/vnd." + AUTHORITY + "." + TABLE_NAME;} else if (ContentUris.parseId(uri) != -1) {return "vnd.android.cursor.item/vnd." + AUTHORITY + "." + TABLE_NAME;}throw new IllegalArgumentException("Unknown URI " + uri);
}

在上述实现中,我们根据Uri的类型返回了不同的MIME类型字符串。

当Uri表示记录集合时,我们返回"vnd.android.cursor.dir/"前缀。

当Uri表示单个记录时,我们返回"vnd.android.cursor.item/"前缀。

这样做可以让其他应用更好地识别和处理ContentProvider提供的数据。


4、注意线程安全,合理处理异常情况


  • 注意线程安全,避免多线程访问时出现的数据竞争问题

  • 在实现ContentProvider的各个方法时,应该合理地处理可能出现的异常情况,例如数据库操作异常、权限验证失败等。

  • 合理的异常处理可以提高应用的健壮性,同时也有利于定位和解决问题。

private final ReentrantLock lock = new ReentrantLock();@Override
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {lock.lock();try {// 执行查询操作return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);} finally {lock.unlock();}
}@Override
public Uri insert(Uri uri, ContentValues values) {lock.lock();try {// 执行插入操作long id = db.insert(TABLE_NAMEnull, values);return ContentUris.withAppendedId(CONTENT_URI, id);} finally {lock.unlock();}
}

在上述示例中,我们使用了ReentrantLock来保证ContentProvider各个方法的线程安全。

在执行数据库操作时,我们先获取锁,操作完成后再释放锁。这样可以避免多线程访问时出现的数据竞争问题。


六、结语


ContentProvider作为Android系统提供的标准化数据共享机制,在开发过程中扮演着非常重要的角色。

通过深入理解其工作原理,我们不仅可以轻松实现应用间的数据交互,还能够提升数据访问的安全性和可维护性。

相信通过本文的介绍,你已经掌握了使用ContentProvider的核心知识,相信在后续的Android开发之路上,一定能游刃有余。

那么让我们一起期待下一篇文章的精彩内容吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/834536.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenGL入门第一步:创建窗口、重写虚函数

1、创建一个QOpenGLWidget 子类 2、重写虚函数 initializeGL&#xff1a;设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前被调用一次。 resizeGL &#xff1a;窗口尺寸变化时调用。 paintGL&#xff1a; 窗口更新时调用&#xff0c;渲染 OpenGL 场景。 makeCu…

最详尽的网络安全学习路线!涵盖所有技能点,带你成为网安专家!

目录 零基础小白&#xff0c;到就业&#xff01;入门到入土的网安学习路线&#xff01; 建议的学习顺序&#xff1a; 一、夯实一下基础&#xff0c;梳理和复习 二、HTML与JAVASCRIPT&#xff08;了解一下语法即可&#xff0c;要求不高&#xff09; 三、PHP入门 四、MYSQL…

Marin说PCB之国产电源芯片方案 ---STC2620Q

随着小米加入的造车大家庭&#xff0c;让这个本来就卷的要死的造车大家庭更加卷了。随之带来的蝴蝶效应就是江湖上各个造成门派都开始了降本方案的浪潮啊&#xff0c;开始打响价格战了。各家的新能源车企也是不得不开始启动了降本方案的计划了&#xff0c;为了应对降价的浪潮。…

Window7镜像注入USB驱动,解决系统安装后无法识别USB

Window7镜像注入usb驱动 Window7镜像注入usb驱动方法一方法二 Window7镜像注入usb驱动 一般4代酷睿之后的主机需要安装usb驱动才能驱动usb&#xff0c;导致很多Windows原版镜像安装后无法识别usb键盘 方法一 1.直接采购PS2 接口键盘、PS2 接口鼠标 方法二 使用联想镜像注入…

李飞飞团队 AI4S 最新洞察:16 项创新技术汇总,覆盖生物/材料/医疗/问诊……

不久前&#xff0c;斯坦福大学 Human-Center Artificial Intelligence (HAI) 研究中心重磅发布了《2024年人工智能指数报告》。 作为斯坦福 HAI 的第七部力作&#xff0c;这份报告长达 502 页&#xff0c;全面追踪了 2023 年全球人工智能的发展趋势。相比往年&#xff0c;扩大了…

AOF持久化是怎么实现的?

AOF持久化是怎么实现的&#xff1f; AOF 日志三种写回策略AOF 重写机制AOF 后台重写总结参考资料 AOF 日志 试想一下&#xff0c;如果 Redis 每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到一个文件里&#xff0c;然后重启 Redis 的时候&#xff0c;先去读取这…

在k8s中部署hadoop后的使用,包括服务端及客户端(客户端的安装及与k8s服务的对接)

&#xff08;作者&#xff1a;陈玓玏&#xff09; 在https://blog.csdn.net/weixin_39750084/article/details/136744772?spm1001.2014.3001.5502和https://blog.csdn.net/weixin_39750084/article/details/136750613?spm1001.2014.3001.5502这两篇文章中&#xff0c;说明…

Verilog复习(一)| 模块的定义

模块&#xff08;module&#xff09;是Verilog的基本描述单位&#xff0c;用于描述某个设计的功能或结构&#xff0c;及其与其他模块通信&#xff08;连接&#xff09;的外部端口。 Verilog程序由关键词module和endmodule进行定义。 定义模块的步骤&#xff1a; 定义模块的端…

保研面试408复习 4——操作系统、计网

文章目录 1、操作系统一、文件系统中文件是如何组织的&#xff1f;二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议&#xff08;数据链路层协议&#xff09;二、以太网MAC帧MTU 标记文字记忆&#xff0c;加粗文字注意&#xff0c;普通文字理解。 1、…

全平台 GUI库, 物联网,嵌入式,单片机,桌面应用都行

跨平台最小头文件GUI库 GuiLite是一个轻量级、高效的GUI库&#xff0c;拥有仅4千行的C代码&#xff0c;且零依赖&#xff0c;采用单一头文件库&#xff08;GuiLite.h&#xff09;。这个库不仅提供高效渲染&#xff0c;即使在单片机上也能流畅运行&#xff0c;展现了卓越的性能表…

【比邻智选】MR880A模组

&#x1f680;高性价比&#xff0c;5G/4G双模&#xff0c;稳定可靠 &#x1f310;功能丰富&#xff0c;5G特性一应俱全 &#x1f9e9;多封装兼容&#xff0c;适配性强&#xff0c;灵活升级智能设备

这 7 道 Redis 基础问题,很常见!!

后端项目如果用到分布式缓存的话&#xff0c;一般用的都是 Redis。不过&#xff0c;Redis 不仅仅能做缓存&#xff0c;还能用作分布式锁、延时队列、限流等等。 什么是 Redis&#xff1f; Redis[1] &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的…

Unity数据持久化之Json

目录 Json概述Json文件格式Json配置规则Excel转Json C#读取存储Json文件JsonUtlityJsonUtlity序列化JsonUtility反序列化 LitJsonLitJson序列化LitJson反序列化JsonUtility和LitJson对比 Json概述 Json是什么? 全称:JavaScript对象简谱(JavaScript Object Notation) Json是国…

ESP8266-01s刷入固件报SP8266 Chip efuse check error esp_check_mac_and_efuse

一、遇到的问题 使用ESP8266 固件烧录工具flash_download_tools_v3.6.8 烧录固件报错&#xff1a; 二、解决方法 使用espressif推出发基于python的底层烧写工具&#xff1a;esptool 安装方法&#xff1a;详见https://docs.espressif.com/projects/esptool/en/latest/esp32/ …

子查询之一(单行子查询, 多行子查询)

1. 子查询 子查询是指一个查询语句嵌套在另一个查询语句内部的查询.这个特性在MySQL4.1开始引入. SQL中子查询的使用大大增强了SELECT查询的能力.因为很多时候查询需要从结果集中获取数据&#xff0c;或者需要从同一个表中先计算得到一个数据结果&#xff0c;然后与这个数据结…

【go项目01_学习记录08】

学习记录 1 模板文件1.1 articlesStoreHandler() 使用模板文件1.2 统一模板 1 模板文件 重构 articlesCreateHandler() 和 articlesStoreHandler() 函数&#xff0c;将 HTML 抽离并放置于独立的模板文件中。 1.1 articlesStoreHandler() 使用模板文件 . . . func articlesSt…

最新:Lodash 严重安全漏洞背后你不得不知道的 JavaScript 知识

可能有信息敏感的同学已经了解到&#xff1a;Lodash 库爆出严重安全漏洞&#xff0c;波及 400万 项目。这个漏洞使得 lodash “连夜”发版以解决潜在问题&#xff0c;并强烈建议开发者升级版本。 我们在忙着“看热闹”或者“”升级版本”的同时&#xff0c;静下心来想&#xf…

FebHost:什么是域名DNS服务器?

域名服务器是一种将域名转换为IP地址的计算机。在域名系统&#xff08;DNS&#xff09;中&#xff0c;它起着至关重要的作用。用户只需在浏览器的地址栏输入域名&#xff0c;而无需手动输入网站服务器的IP地址&#xff0c;就可以访问网站。 每个已注册的域名都必须在其DNS记录…

震惊,现在面试都加科技与狠货了

震惊&#xff0c;现在面试都加科技与狠货了 生成式AI盛行的现在&#xff0c;程序员找工作变容易了吗我和老痒喝着大酒&#xff0c;吃着他的高升宴&#xff0c;听他说他面试的各种细节&#xff0c;老狗我只恨自己动作慢了一步&#xff0c;不然现在在那侃侃而谈的就是我了。 面试…