本篇不全也不细,只是根据按照个人理解和工作中遇到的问题,总结了个人认为的要点。

1. Android的数据库体系

  • 1.1. 概述
  • 1.2 uri结构

2. ContactsProvider2

  • 2.1. 概述
  • 2.2. Contacts2.db中的表
  • 2.3. ContactProvider2中的实现
  • 2.4. 批量访问

1. Android的数据库体系

1.1. 概述

Android的数据库体系可以分为三个层次:

  1. ContentProvider层;ContentProvider将对数据的增删改查操作进行了抽象,具体实例会注册到AMS中,提供跨进程的服务。ContentProvider可以用于管理各种数据存储源的访问,包括结构化数据(SQLite)和非结构化数据(如图像文件)。
  2. SQLiteDatabase/SQLiteOpenHelper;这部分代码位于“/frameworks/base/core/java/android/database/sqlite/”,ContentProvider要借助于这些框架层的类来和SQLite数据库进行交互。
  3. SQLite数据库;这部分属于native层,代码位于external/sqlite。

ContentProvider不支持线程安全,但SQLite是支持线程安全的(编译前将 宏SQLITE_THREADSAFE 设置为1)。
多个进程可以同时打开一个database,而且可以同时执行SELECT操作,但是同一时间只能有一个进行修改操作。
下面的简图展示了我们使用uri访问database的方式,其中ContentProvider在ActivityManagerService中的register可能是自己所在进程启动时做的,也可能是ActivityManagerService收到uri请求后启动ContentProvider时做的。ContentResolver和ActivityManagerService根据uri中的"authority"来确定要访问的ContentProvider。

1.2 uri结构

基本结构

[scheme:]scheme-specific-part[#fragment]

进一步划分

[scheme:][//authority][path][?query][#fragment]
path可以有多个,每个用/连接,比如scheme://authority/path1/path2/path3?query#fragment。

query参数可以带有对应的值,也可以不带,如果带对应的值用=表示,如:
scheme://authority/path1/path2/path3?id = 1#fragment,这里有一个参数id,它的值是1
query参数可以有多个,每个用&连接,如
scheme://authority/path1/path2/path3?id = 1&name = xiaofang&age#fragment
这里有三个参数:
参数1:id,其值是:1
参数2:name,其值是:xiaofang
参数3:age,没有对它赋值

在android中,scheme、authority都是必须要有的,而至于path、query和fragment,它们都是选择性的,可以有也可以没有,但顺序不能变,比如:
"path"可不要:scheme://authority?query#fragment
"path"和"query"可都不要:scheme://authority#fragment
"query"和"fragment"可都不要:scheme://authority/path
“path”,“query”,"fragment"都不要:scheme://authority
等等……

终极划分

[scheme:][//host:port][path][?query][#fragment]


2. ContactsProvider2

2.1. 概述

ContactsProvider2是Android一个很成熟的源生组件,用于管理联系人数据相关的存储区。Google对ContactsProvider2有一个比较详细的介绍,下面是相关链接:
https://developer.android.com/guide/topics/providers/contacts-provider

ContactsProvider2管理了两个database,一个是"contacts2.db",另外一个是"profile.db";本篇着重写contacts.db相关的内容。

2.2. Contacts2.db中的表

“contacts2.db”数据库中存储了联系人、通话记录和Account等数据。
由于联系人的信息字段比较多,包含了号码、地址、Email和头像等信息,所以数据库中的数据表也比较多;为了提高查询速度,还创建了视图和索引;另外还有触发器。

Android P源码中数据表有30多个,视图有11个;这里就不一一列出了,太占地方了,哈哈。
我们需要搞清楚这些表之间的关系,特别是几个存储重要信息的表,下面是几个重要表的ER图

表“Contacts”中的数据是在数据插入表“raw_contact”之后,在Transaction的回调onCommit调用序列中插入的。

联系人相关的数据并没有存储在raw_contact表中,而是存储在了Data表中;一条raw_contact数据在Data表中对应多条数据。Data表中有15个data字段,每个字段存储的是什么信息要根据列"mimetype_id"来决定,该列的取值来自"account"表的主键"_ID"。
协定类ContactsContract.java中为data字段定义了映射名称类,如下表:

映射类 数据类型 备注
ContactsContract.CommonDataKinds.StructuredName 与该数据行关联的raw contact的姓名数据。 一位raw contact只有其中一行。
ContactsContract.CommonDataKinds.Photo 与该数据行关联的raw contact的主要照片。 一位raw contact只有其中一行。
ContactsContract.CommonDataKinds.Email 与该数据行关联的raw contact的电子邮件地址。 一位raw contact可有多个电子邮件地址。
ContactsContract.CommonDataKinds.StructuredPostal 与该数据行关联的raw contact的邮政地址。 一位raw contact可有多个邮政地址。
ContactsContract.CommonDataKinds.GroupMembership 将raw contact链接到联系人提供程序内某个组的标识符。 组是帐户类型和帐户名称的一项可选功能。联系人组部分对其进行了更详细的描述。

以“ContactsContract.CommonDataKinds.Email”和“ContactsContract.CommonDataKinds.StructuredName”为例,前者用于联系人的Email信息,后者用于联系人的名字信息; 它们分别在Data表中有一条记录,而且都使用了“data1”、“data2”和“data3”这三个字段,但是每个字段在两条记录中分别存储了不同的信息,如下图:


2.3. ContactProvider2中的实现

下面是ContactsProvider2的类继承关系图:

ContactsProvider2直接继承了AbstractContactsProvider.java这个类,它的很多功能都继承自这个抽象父类,看代码时这个抽象父类的代码也是重点。
ContactsProvider2中定义了UriMatcher对象sUriMatcher,并在static块中进行了初始化:

matcher.addURI(ContactsContract.AUTHORITY, "contacts", CONTACTS);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/data", CONTACTS_ID_DATA);
...
matcher.addURI(ContactsContract.AUTHORITY, "data", DATA);

ContractsProvider2会将接收到的uri和sUriMatcher进行匹配,即根据uri中的path确定要访问的table。
比如,我们向Data表中增加一条数据记录,那么我们会使用

ContactsContract.Data.CONTENT_URI,即"content://com.android.contacts/data"

insertInTransaction方法使用sUriMatcher进行匹配后返回”DATA”,然后在switch的对应case语句中处理这条增加数据的请求。

由于"contacts2.db"中的表比较多,而App层只是通过uri发送请求,那么相关表直接的数据维护逻辑是在ContactsProvider2中完成的,这也方便了App层的使用。为了维护数据一致性和原子性等,ContactsProvider2.java的增/删/改都使用了事务(Transaction),下面的流程图展示了insert操作的事务处理流程,其他操作类似:

2.4. 批量访问

可以使用 ContentProviderOperation 类中的方法创建一批访问调用,然后通过 ContentResolver.applyBatch(…) 应用这些调用。如果某次批量修改需执行大量操作,则可能会阻塞其他进程,导致整体用户体验不佳。可以将想执行的修改操作尽量分散到各个集合中,并为一项或多项操作设置挂起点。挂起点是一个 ContentProviderOperation 对象,并且其 isYieldAllowed() 值设置为 true。当ContactsProvider2遇到挂起点时,它会暂停其工作,让其他进程运行,并关闭当前事务。当ContactsProvider2再次启动时,它会继续执行 ArrayList 中的下一项操作,并启动新事务。

挂起点会导致每次调用 applyBatch() 时产生多个事务。因此,应为一组相关数据行的最后一项操作设置挂起点。例如,应该为一组操作中添加raw contact行及其关联数据行的最后一项操作设置挂起点,或者针对一组与某位联系人相关的行的最后一项操作设置挂起点。
挂起点也是一个原子操作单元。无论访问成功还是失败,两个挂起点之间的所有访问均以一个单元的形式呈现。如果不设置任何挂起点,则最小的原子操作即为整个批量操作。如果使用挂起点,则可以防止操作降低系统性能,还可确保部分操作为原子操作。


结束

ContactsProvider2相关推荐

  1. ContentProvider源码分析(原)

    一.前言 ContentProvider作为Android四大组件之一,承担着数据存储的作用,本文用一个最典型的删除(delete)操作,按照Android源码, 从应用层的getContentRes ...

  2. 桌面快捷键和桌面livefolder

    // to create live folder on "home" screen Java代码   if (LiveFolders.ACTION_CREATE_LIVE_FOLD ...

  3. Android bootchart(二)

    这篇文章讲一下MTK8127开机启动的时间 MTK8127发布版本开机时间大约在20秒左右,如果发现开机时间变长,大部分是因为加上了客户订制的东西,代码累赘太多了. 1.下面看一下MTK开机花时间的是 ...

  4. android联系人源码分析,android 联系人源码分析 新字段的添加流程

    android 联系人 代码主要分布在四个地方(有的代码会有关于sim卡的加载,各个平台实现方式不同,就不提了): 1 framwork/base/core/java/android/provider ...

  5. Android 系统(191)---ODM 开发用户常见需求文档(九)

    Android 系统(191)---ODM 开发用户常见需求文档(九) 阅读数:1122 一:去除摄像头的假对焦框 (vendor/) (mediatek/proprietary/packages/a ...

  6. Android 系统 (129)---ODM 开发用户常见需求文档(三)

    一:修改client ids (device/mediatek/common/device.mk) [java] view plaincopy diff --git a/mediatek/common ...

  7. android权限检查

    1.checkpermission部分写到服务端的是: writeInterfaceToken -->> android.app.IActivityManager writeString- ...

  8. Android权限管理之Permission权限机制及使用

    前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...

  9. [高通SDM450][Android9.0]CTA认证--去掉彩信、短信、通话功能

    文章目录 开发平台基本信息 问题描述 解决方法 开发平台基本信息 芯片: SDM450 版本: Android 9.0 kernel: msm-4.9 问题描述 与去掉录音功能同理,设备在进行入网认证 ...

  10. Telephony之TelephonyManager(原)

    一.TelephonyManager概述 TelephonyManager主要提供Telephony相关实务的处理能力,我们从他所提供的public方法来总览一下其所能提供的功能: //得到软件版本g ...

最新文章

  1. 当AI有了情商,会说话就多说点
  2. poj3159(差分约束)
  3. Server-map
  4. [云炬python学习笔记]Numpy中内置函数min(),max(),sum()与Python中内置函数min(),max(),sum()性能对比分析
  5. xajax中的中文乱码问题
  6. 气味识别应用_解决气味
  7. C++:29 --- C++继承关系下的内存布局(下)
  8. 程序员可以只关心技术么?
  9. 关于 nohup 执行命令以后 需要再按回车才能起效的解决办法
  10. sqoop建表_使用Sqoop创建/导入配置单元表
  11. 管理分支:git branch
  12. 路遥《平凡的世界》读后感
  13. log4j 配置文件路径问题
  14. 网站搭建niushop系统,全面搭建,打包app,h5详细教程
  15. 华为nova2s云相册在哪里_华为nova2s截频图片在哪个文件夹 | 手游网游页游攻略大全...
  16. 几乎零基础的git入门级分享
  17. python之客户流失预警
  18. 如何把地址导航生成二维码?
  19. 在职考研读计算机科学,能否以在职读研的形式学习在职研究生计算机科学与技术专业?...
  20. css 文本超出就隐藏并且显示省略号

热门文章

  1. 软考高项论文写作技巧
  2. iOS 抓包工具限免,速度下载!【附使用教程】
  3. 笔记:算法笔记-胡凡、曾磊
  4. 微信小程序搭建tabbar
  5. docker镜像下载到本地,并导入其他服务器
  6. python导入win32com.client出错
  7. LX04 小米触屏音箱刷机教程
  8. AMESim 14.0 win10环境下安装教程
  9. 小技巧 - LeetCode 如何查看他人耗时更优的代码答案?
  10. Android原生人脸识别Camera2示例