回头看Android之contentprovider

来源:互联网 发布:淘宝刚开始怎么推广 编辑:程序博客网 时间:2024/04/29 08:03

什么是ContentProvider
ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。例如淘宝和天猫,微信和QQ之间的数据共享(ps:这里说的只是本地共享。当然我知道现在我们常说的数据共享是后台服务器的数据共享。)

为什么我们要使用ContentProvider
之所以使用ContentProvider,主要有以下几个理由:
1、ContentProvider提供了对底层数据存储方式的抽象。比如下图中,底层使用了SQLite数据库,在用了ContentProvider封装后,即使你把数据库换成MongoDB,也不会对上层数据使用层代码产生影响。

2,Android框架中的一些类需要ContentProvider类型数据。如果你想让你的数据可以使用在如SyncAdapter, Loader, CursorAdapter等类上,那么你就需要为你的数据做一层ContentProvider封装。

3,第三个原因也是最主要的原因,是ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题。

ContentProvider的实现思想与流程
我本人认为,这种跨进程间的数据共享思想实际上是借鉴了隐式Intent的思想。
这里写图片描述
如上图所示,我们仔细看看,步骤(1)和步骤(2)是不是和隐式Intent的 工作流程是一模一样的?因此,参考隐式Intent,我们可以肯容易的想到,要使用ContentProvider,必然要在manifest中进行注册。注册的目的是什么?当然是为了匹配呀。因为一个系统中,肯定有很多个被封装了的contentprovider,所以肯定要通过这种机制,来进行匹配操作。
前文说了,contentprovider被设计出来的目的是为了实现不同app之间的数据共享,它的特性可以使得在目标app没有启动的情况下获取它的数据并进行操作。因此仔细看看这张图,你应该对这个流程有了个大概的认识。好了,我们继续往下看。

如何实现匹配?
在使用ContentProvider对数据进行操作的时候,以Uri的形式进行数据交换。
这里写图片描述

对URI不理解的同学,可以想象一下一个网址的构成。
如上图所示,一个URI的组成是由scheme, authority 和 path组成的。
其中,scheme表示上图中的content://,authority表示B部分,path表示C和D部分。

A部分:表示是一个Android内容URI,其实就是一个协议,该部分是固定形式,不可更改的。
B部分:是URI的授权部分,是唯一标识符,用来定位ContentProvider。格式一般是自定义ContentProvider类的完全限定名称,注册时需要用到,如:com.example.transportationprovider
C部分和D部分:是每个ContentProvider内部的路径部分,C和D部分称为路径片段,C部分指向一个对象集合,一般用表的名字,如:/trains表示一个笔记集合;D部分指向特定的记录,如:/trains/122表示id为122的单条记录,如果没有指定D部分,则返回全部记录。

前文说了,contentprovider的匹配机制与隐式的Intent相似。具体针对contentprovider来说,这个匹配机制是通过URI来实现的。

那在AndroidManifest.xml里又要怎么写呢?
再回想下隐式Intent,在隐式Intent中,Intent-fliter是Activity的一部分,专门过滤隐式Intent的匹配请求的,如果Intent-fliter匹配后,就启动对应的Activity;
同样的,把这个URI作为它的一部分。当匹配成功以后,就进这个contentprovider里操作数据库。
ContentProvider在AndroidManifest.xml中的声明方式为:(与上面的URI对应)

<provider      android:name=".NoteContentProvider"      android:authorities="com.example.transportationprovider"      android:exported="true"/>  

所以,我们应该能够理解这里的android:authorities必须与上面URI中的B部分一样,因为这个就是用来全局匹配的authority!只有URI的authority与provider的android:authorities匹配上了,才会进入后面的操作。
现在再看来看一下全局的流程图。应该能够理解了吧。
这里写图片描述

如何操作?
上面,我们说到了第一步和第二步,当匹配成功ContentProvider以后,就开始进入我们指定的ContentProvider进行处理;大家估计也注意到了,我们的ContentURI的完整部分为:content://com.example.transportionprovider/trains/122
到第二步,我们已经匹配到了content://com.example.transportionprovider,那后面的/trains/122的匹配工作就只有交由ContentProvider来处理了。
在ContentProvder中的匹配是利用UriMatcher来完成的!
那么接下来,我们就来讲解一下UriMatcher.

顾名思义,UriMatcher就是用来匹配URI的。要执行匹配,首先第一步需要做的工作是注册。即将所需要的匹配的URI使用addURI()添加到UriMatcher中

public void addURI(String authority, String path, int code)  

其中第一个参数authority:就是URI对应的authority
path:就是我们在URI中 authority后的那一串
code:表示匹配成功以后的返回值;
下面以我们的URI=content://com.example.transportionprovider/trains/122来演示一下

public static final String AUTHORITY = "com.alexzhou.provider.NoteProvider";  private static final UriMatcher sUriMatcher;  static {      sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);      sUriMatcher.addURI(AUTHORITY, "trains", 1);      sUriMatcher.addURI(AUTHORITY, "trains/#", 2);  }  

上段代码中:
sUriMatcher.addURI(AUTHORITY, “trains”, 1);
表示匹配content://com.example.transportionprovider/trains,如果匹配成功返回1
sUriMatcher.addURI(AUTHORITY, “trains/#”, 2);
其中
’#‘表示匹配任意的数据ID
’*‘表示匹配任意文本
所以这句的意思就是匹配content://com.example.transportionprovider/trains/任意数字ID 比如我们的content://com.example.transportionprovider/trains/122

OK,注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.example.transportionprovider/trains路径,返回的匹配码为1。
这里多提一句,ContentUris这个类。
由于所有的数据操作都要通过Uri的形式进行传递,那么当执行完操作之后,我们往往想要返回我们增加后数据的ID以Uri的形式进行返回,那么就可以使用这个类来完成操作。
它有两个比较实用的方法:
• withAppendedId(uri, id)用于为路径加上ID部分
• parseId(uri)方法用于从路径中获取ID部分
OK,到这里整个流程就讲解完了。
实际上来说,两个app之间通过这样的方式来实现数据共享几乎是不可能的。我这里说的不可能不是指技术上,而是从实际情况来说,基本上不会有人这样去做。因为有更好的,更安全的方式去实现数据共享。比如说后台服务器之间的数据共享什么的,这里不多提。那么既然基本上没有人这么做,那contentprovider存在的意义是什么呢?它主要用在什么地方?
就我个人经验来看,contentprovider用的比较多的地方就是在获取系统数据上。比如读取本机的联系人信息,短信记录,通话记录,图片 ,视频,音频等等等等。所以,因该很自然的想到,Android肯定会像intent那样,提供很多的系统类的URI。事实也是如此。
好的,我们接下来将如何对contentprovider进行操作。

ContentResolver
有些人可能会疑惑,为什么我们不直接访问Provider,而是又在上面加了一层ContentResolver来进行对其的操作,这样岂不是更复杂了吗?其实不然,大家要知道一台手机中可不是只有一个Provider内容,它可能安装了很多含有Provider的应用,比如联系人应用,日历应用,字典应用等等。有如此多的Provider,如果你开发一款应用要使用其中多个,如果让你去了解每个ContentProvider的不同实现,岂不是要头都大了。所以Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作。

在Context.java的源码中有一段
/* Return a ContentResolver instance for your application's package. /
public abstract ContentResolver getContentResolver();

所以我们可以通过在所有继承Context的类中通过调用getContentResolver()来获得ContentResolver。

 String CONTENT_URI = content://com.example.transportionprovider/trains/122;  ContentResolver cr =getContentResolver();  ContentValues values = new ContentValues();  values.put("title", "hello");//数据库的键值对  values.put("content", "my name is harvic");  Uri uri = cr.insert(CONTENT_URI, values);  

总结
总的来说,为了实现跨进程的数据共享,Android采用了contentprovider这一种机制来进行封装。正如我在本文中放的第一张图一样,contentprovider就相当于一个数据库操作抽象层。当然,针对sqlite(或者其他)的具体数据操作肯定还是要编码实现的,只是说它被封装了,放在了一个黑盒里面,对外只暴露接口而已。
具体的编码操作,可以查看这篇文章,点击这里查看

PS:文章参考了CPPAlien发表在简书的文章《ContentProvider从入门到精通》以及启舰的文章
具体实现可以参考此处的源码

原创粉丝点击