7

Android开发之内容提供者——创建自己的ContentProvider(详解)

 3 years ago
source link: https://blog.csdn.net/dmk877/article/details/50387741
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Android开发之内容提供者——创建自己的ContentProvider(详解)_dmk877的专栏-CSDN博客_android内容提供者

转载请注明出处:http://blog.csdn.net/dmk877/article/details/50387741

苦心人天不负卧薪尝胆三千越甲可吞吴,有志者天不负釜底抽薪百二秦川终属楚。这是一对非常励志的名言,每当读这句话都会被震撼一下,然后接着颓废,哈哈,最近的工作比较忙,也在这里提醒自己,一定要坚持下去,一定要坚持一件对自己有益的事情。

装逼到此进入正题,今天要讨论的主要内容是ContentProvider(内容提供者),ContentProvider也是android的四大组件之一,可见其在android中的重要性,可能大家用ContentProvider比其他三个组件用的少一点,但是ContentProvider同样的非常重要,有的人知道怎么使用ContentProvider,但是对于ContentProvider的原理等,并没有搞清楚,没关系,通过本篇博客相信你会对ContentProvider有一个全新的认识。

通过本篇博客你将学到以下知识

①什么是内容提供者

②为什么会有内容提供者

③怎样使用内容提供者

④ContentProvider中的Uri的详细介绍

⑤ContentResolver讲解

⑥UriMatch用法介绍

⑦ContentObserver用法详解

⑧通过一个案例来讲解自定义ContentProvider的执行过程(下一篇将给大家带来调用系统的ContentProvider)

1.什么是内容提供者?

     首先我们必须要明白的是ContentProvider(内容提供者)是android中的四大组件之一,但是在一般的开发中,可能使用比较少。ContentProvider为不同的软件之间数据共享,提供统一的接口。而且ContentProvider是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。至于如何从URI中识别出外界需要的是哪个“数据库”这就是Android底层需要做的事情了,也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一个对外开放的接口,从而使得其他的应用可以使用我们自己应用中的文件、数据库内存储的信息。当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Android系统中,很多数据如:联系人信息、短信信息、图片库、音频库等,这些信息在开发中还是经常用到的,这些信息谷歌工程师已经帮我们封装好了,我们可以使用谷歌给我的Uri去直接访问这些数据。所以对于ContentProvider我们还是需要认真的学习的,在遇到获取联系人信息,图片库,音视频库等需求的时候,才能更好的实现功能。

2.为什么会有内容提供者?
       当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式,这也是为什么会有内容提供者的原因。
3.怎么使用内容提供者?
         在理解了什么是内容提供者,为什么会有内容提供者之后,我想在大家脑海中的浮现的一个问题就是怎么使用内容提供者,这也是今天我们要讨论的重点内容。在前面的博客我也说到学习这种东西的最好方法是看谷歌给出的官方文档,那么好我们先来翻译一段谷歌给出的介绍(注:这是本地的文档,我采用的是脱机工作地址(file:///D:/adt-bundle-windows-x86_64_20140101/sdk/docs/reference/android/content/ContentProvider.html))。
内容提供者是android应用程序的基本构建块之一,它们封装数据并将封装的数据通过单一的ContentResolver接口提供给应用程序。当你需要在多个应用之间共享数据的时候就需要用到内容提供者。例如,手机中的联系人数据会被多个应用所用到所以必须要用内容提供者存储起来。如果你不需要在多个应用之间共享数据,你可以使用一个数据库,直接通过SQLite数据库。  当通过content resolver发送一个请求时,系统会检查给定的URI并把请求传给有注册授权的Contentprovider。 UriMatcher类有助于解析uri。
需要实现的主要方法是:
public boolean  onCreate () 在创建ContentProvider时调用
public Cursor  query (Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor
public Uri  insert (Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中,(外部应用向ContentProvider中添加数据)
public int  update (Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据
public int  delete (Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据
public String  getType (Uri) 用于返回指定的Uri中的数据的MIME类型
数据访问的方法(如:insert(Uri, ContentValues) and update(Uri, ContentValues, String, String[]))可能被多个线程同时调用,此时必须是线程安全的。其他方法(如: onCreate())只能被应用的主线程调用,它应当避免冗长的操作。 ContentResolver(内容解析者)请求被自动转发到合适的内容提供者实例 ,所以子类不需要担心跨进程调用的细节。
唉,每次最头疼的就是看全英文的文档,发现自己的英语水平太差,最近也在空闲时间学习学习英语,但是对于英语从来就没感兴趣过,希望自己可以坚持一段时间吧,其实在开发过程中遇到那些比较难解决的问题,在国外的有名的网站中基本都是可以查找到的,但是对于我这样一个英语水平差的人来说,说多了其实都是眼泪。。。,我先哭一会。。。
关于怎样使用ContentProvider后面有实例帮助大家理解。
4.Uri详解
       在上面的翻译中如果你认真看的话你会发现在谷歌的官方文档中提到了ContentResolver(内容解析者),外界可以通过ContentResolver接口来访问ContentProvider(内容提供者)中的数据。但是在详细了解ContentResolver之前有一项工作是必须要做的,那就是先理解Uri,在谷歌文档中也有介绍,接下来我们就来详细的学习下Uri这个类
Uri 通用资源标志符(Universal Resource Identifier)Uri代表要操作的数据,Android中可用的每种资源 - 图像、视频片段等都可以用Uri来表示。Uri的结构由以下几个部分组成
scheme、authority、path、query和fragment组成。其中authority又分为host和port。它的格式根据划分的详细程度可以分为三种
[scheme:][scheme-specific-part][#fragment]
[scheme:][//authority][path][?query][#fragment] 
[scheme:][//host:port][path][?query][#fragment]——最详细的划分形式
看到这里肯定有人糊里糊涂的,接着我们就来举一个例子来帮助大家详细的理解Uri这个类的结构
假如有这么一个Uri:http://www.baidu.com:8080/yourpath/fileName.html?id=15&name=du#dmk
你能将上述Uri进行提取吗?接着我们就比着标准的格式[scheme:][//host:port][path][?query][#fragment]来将这个Uri各个部分提取出来
scheme:根据标准格式可以看出这里的scheme就是Uri前面//前面的部分这里也就是http:。
fragment:dmk这个也是比较容易找到的,在#后面
query:id=15&name=du#dmk。从标准格式可以看到在"#"之前"?"之后的部分是query,在这里当然就是id=15&name=du#dmk了。
authority:从格式二中可以看到authority是在//后的部分,它的终点就是在path之前所以这里的authority就是www.baidu.com:8080
path:path就是?之前,主机之后的部分那就是yourpath/fileName.html
host和port:因为主机可以分为host和port所以这里的host和port分别为:www.baidu.com和8080
这里要提醒大家注意的是:在Uri中并不是上述所有的字段都必须有的除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变,比方说在上述Uri中没有path那它的格式就为:http://www.baidu.com:8080/?id=15&name=du#dmk。
在理解了Uri的格式之后,有的人可能会说Uri的各个字段能否用代码获取?答案是肯定的
这里我们同样以http://www.baidu.com:8080/yourpath/fileName.html?id=15&name=du#dmk为例
  • getScheme() :获取Uri中的scheme字符串部分,在这里是http
  • getSchemeSpecificPart():获取Uri中的scheme-specific-part:部分,这里是:http://www.baidu.com:8080/yourpath/fileName.html?
  • getFragment():获取Uri中的fragment部分,即dmk
  • getAuthority():获取Uri中Authority部分,即www.baidu.com:8080
  • getPath():获取Uri中path部分,即/yourpath/fileName.html
  • getQuery():获取Uri中的query部分,即id=15&name=du
  • getHost():获取Authority中的Host字符串,即www.baidu.com
  • getPost():获取Authority中的Port字符串,即8080
5.ContentResolver讲解
       在了解了Uri之后就可以来学习学习ContentResolver了,前面我们说到ContentProvider共享数据是通过定义一个对外开放的统一的接口来实现的。 然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。 当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法。 ContentResolver cr = getContentResolver();在上面我们提到ContentProvider可以向其他应用程序提供数据,与之对应的ContentResolver则负责获取ContentProvider提供的数据,修改、添加、删除更新数据等;
ContentResolver 类也提供了与ContentProvider类相对应的四个方法:
public Uri insert(Uri uri, ContentValues values)    该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs)   该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)   该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)   该方法用于从ContentProvider中获取数据。
这些方法的第一个参数为Uri,代表要操作的是哪个ContentProvider和对其中的什么数据进行操作,假设给定的是 Uri.parse(“content://com.qstingda.provider.personprovider/contact/15”),那么将会对主机名为com.qstingda.provider.personprovider的ContentProvider进行操作,path为contact/15的数据,看到这如果你之前没有接触过ContentProvider肯定一头雾水,没有关系,这很正常,等我们把理论知识讲完后会有实例,相信你看过实例后就会很明白了。
6.UriMatch
      UriMatcher 类主要用于匹配Uri.这里的匹配是发生在ContentProvider中的,假如我们向ContentProvider中插入一条数据,不可能为所欲为的想怎么干就怎么干,在ContentProvider肯定要做一个判断,只有在符合条件下才会去执行你想要执行的操作,这里的判断就是用UriMatch进行匹配,假如是系统的ContentProvider如联系人、图库、视频库等,这些系统都提供了Uri我们可以根据系统提供的Uri来操作相应的数据。其实UriMatch的用法非常简单,查阅谷歌官方文档你会发现有这么几个方法
①publicUriMatcher(int code) 它的作用就是创建一个UriMatch对象
② public void addURI (String authority,String path, int code)

它的作用是在ContentProvider添加一个用于匹配的Uri,当匹配成功时返回code。Uri可以是精确的字符串,Uri中带有*表示可匹配任意text,#表示只能匹配数字。

③public int match(Uri uri) 这里的Uri就是传过来的要进行验证,匹配的Uri假如传过来的是:content://com.example.test/student/#,则content://com.example.test/student/10可以匹配成功,这里的10可以使任意的数字。

7.ContentObserver用法
         ContentObserver——内容观察者,从其名字我们可以看出它的作用就是观察,观察什么?观察指定的Uri引起的数据库的变化,然后通知主线程,根据需求做我们想要做的处理。这样说大家可能理解的不是特别透彻,这样跟大家说它可以实现类似于Adapter的notifyDataSetChanged()这个方法的作用,比方说当观察到ContentProvider的数据变化时会自动调用谷歌工程师给我们提供的好的方法,可以在此方法中通知主线程数据改变等等。那么问题来了,应该怎样实现这样的功能呢?首先要做的就是注册这个观察者,这里的注册是在需要监测ContentProvider的应用中进行注册并不是在ContentProvider中而在ContentProvider中要做的就是当数据变化时进行通知,这里的通知的方法谷歌已经帮我们写好,直接调用就行了,查看谷歌文档你会发现在ContentResolver中有这样的介绍:

public final void registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer)

注册一个观察者实例,当指定的Uri发生改变时,这个实例会回调实例对象做相应处理。

参数:uri:需要观察的Uri

          notifyForDescendents:如果为true表示以这个Uri为开头的所有Uri都会被匹配到,

    如果为false表示精确匹配,即只会匹配这个给定的Uri。

  举个例子,假如有这么几个Uri:

①content://com.example.studentProvider/student

②content://com.example.studentProvider/student/#

③content://com.example.studentProvider/student/10

④content://com.example.studentProvider/student/teacher

假如观察的Uri为content://com.example.studentProvider/student,当notifyForDescendents为true时则以这个Uri开头的Uri的数据变化时都会被捕捉到,在这里也就是①②③④的Uri的数据的变化都能被捕捉到,当notifyForDescendents为false时则只有①中Uri变化时才能被捕捉到。

       看到registerContentObserver 这个方法,根据语言基础我想大家能够想到ContentResolver中的另一个方法

public final voidunregisterContentObserver(ContentObserverobserver)它的作用就是取消对注册的那个Uri的观察,这里传进去的就是在registerContentObserver中传递进去的ContentObserver对象。到这关于注册和解除注册的ContentObserver可能大家都比较清楚了,那么问题来了,怎么去写一个ContentObserver呢?其实它的实现很简单,直接创建一个类继承ContentObserver需要注意的是这里必须要实现它的构造方法

public ContentObserver(Handlerhandler)

这里传进去的是一个Handler对象,这个Handler对象的作用一般要依赖于ContentObserver的另一个方法即

public void onChange(boolean selfChange)

这个方法的作用就是当指定的Uri的数据发生变化时会回调该方法,此时可以借助构造方法中的Handler对象将这个变化的消息发送给主线程,当主线程接收到这个消息之后就可以按照我们的需求来完成相应的操作,比如上面提到的类似于Adapter的notifyDataSetChanged()的作用,下面的案例也是完成了这个功能,准备工作完成之后来看一个案例,相信这个案例会让你对以上知识了解的更加深入。

8.案例(自定义ContentProvider)
    在真正的开发中我们很少去自定义一个ContentProvider因为ContentProvider是为了更好的去共享数据,我们在开发中很少会遇到这种情况,而遇到更多的则是访问系统的ContentProvider,系统的ContentProvider谷歌工程师已经帮我们写好了,我们直接使用就可以了,这里为了让大家能够理解ContentProvider更加彻底,我们自定义一个ContentProvider然后在其它应用中来访问自定义的ContentProvider的数据这个案例的运行效果如下:
Center
    这里的插入数据,是在一个项目中向另一个项目中的ContentProvider中插入一条数据,其他的操作也是,接下来就来看看怎么实现上述的效果。
    在上面我们提到在自定义ContentProvider时需要继承ContentProvider并实现3中所述的那几个方法(系统会自动帮你将要复写的方法罗列出来),那么我们自定义的PeopleContentProvider的代码如下
可以看到在onCreate()方法中创建了一个数据库,关于数据库的操作大家可以看此博客 http://blog.csdn.net/dmk877/article/details/44876805。这里就不多做介绍了。 注意这个案例牵扯到两个项目,一个是包含我们自定义的ContentProvider,另一个项目是访问这个包含ContentProvider项目中的数据。
写好PeopleContentProvider之后千万不要忘了在清单文件中注册
这里的 authorities就是它是唯一标识内容提供者的, 为内容提供者指定一个唯一的标识,这样别的应用才可以唯一获取此provider,exported的值为[flase|true]当为true时: 当前提供者可以被其它应用使用。任何应用可以使用Provider通过URI 来获得它,也可以通过相应的权限来使用Provider。 当为false时: 当前提供者不能被其它应用使用,默认为true。 注册好之后运行到手机上,此时其它的应用就可以通过ContentResolver来访问这个PeopleContentProvider了,具体怎么操作呢?我们 再新建一个项目 在MainActivity的代码如下:
    可以看出若想操作我们想操作的ContentProvider,必须要知道内容提供者的Uri,再正确得到Uri之后,就可以通过ContentResolver对象来操作ContentProvider中的数据了,假如你需要插入数据只需要调用contentResolver.insert(uri, contentValues);把正确的uri和ContentValues键值对传过去就行了。执行这句话系统就会根据我们提供的uri找到对应的ContentProvider,因为我们的uri中包含了authority(主机等各种信息),得到对应的ContentProvider后将调用ContentResolver的与之对应的增删改查方法,并将参数通过ContentResolver的增删改查方法传递到ContentProvider中。在上面用到了CursorAdapter关于CursorAdapter的用法可以参考此博客http://blog.csdn.net/dmk877/article/details/44983491
PersonObserver的代码如下
可以看到,在构造方法中接收了Handler然后当监听到指定的Uri的数据变化时就会通过Handler消息机制发送一条消息,然后的操作就由我们自行完成了。
到这里我们来理一理整个操作的运行流程:首先有两个项目,一个是有ContentProvider的,在这个ContentProvider中初始化了一个数据库,我们的目的就是在另一个项目中来操作这个项目中ContentProvider中的数据,例如插入一条数据,查询等。对于怎么在另一个项目中操作ContentProvider中的数据,是通过ContentResolver(内容解析者)对象来操作的,假如我们要进行insert操作,那么需要调用ContentResolver的insert(uri, ContentValues);将Uri和ContentValues对象经过一系列操作传递到ContentProvider的中,然后在ContentProvider会对这个Uri进行匹配,如果匹配成功则按照我们的需求去执行相应的操作,如:插入数据、查询数据等。如果想进一步理解ContentProvider和ContentResolver之间的关系 http://blog.csdn.net/u010961631/article/details/14227421 (对这个过程从源码进行了解析,不建议初学者阅读)。下面我们来画一张图再来说一下这个过程
Center
从图中可以看出在OtherApplication中注册了ContentObserver之后,当Application1中的数据库发生了变化时,只需要在ContentProvider中调用ContentResolver的notifyChange(Uri, ContentObserver observer),由于在OtherApplication中注册了ContentObserver(注册时用的Uri和ContentProvider中发生变化的Uri一样)因此在ContentObserver中会收到这个变化信息,它就可以将这个消息通过Handler发送给OtherApplication。
好了,本篇关于ContentProvider的介绍主要是ContentProvider的基础知识,以及自定义一个ContentProvider并操作它的执行过程, 如果大家发现博客中有错误的地方,欢迎批评指正,如有疑问欢迎留言,谢谢
如果你觉着本篇博客对你有帮助,就赞一个,顶一下呗。。。。
案例源码地址 (注意案例应先运行含有ContentProvider的项目。)
转载请注明出处: http://blog.csdn.net/dmk877/article/details/50387741

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK