ContentProvider

转载 2015年11月18日 09:45:59

在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据,例如我们需要操作系统里的媒体库、通讯录等,这时我们就可能通过ContentProvider来满足我们的需求了



ContentProvider 在android中的作用是对外共享数据,提供了数据访问接口,用于在不同应用程序之间共享数据,同时还能保证被访问数据的安全性,它,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。

关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider 对外共享数据呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。使用ContentProvider对外共享数据的好处是统一了数据的访问方式

而且与文件存储和SharedPreference这两种全局可读可写操作模式不同,contentprovider可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会被泄露。

而且与SQLiteDatabase不同,ContentProvider中的CRUD不接收表名参数,而是Uri参数。内容URI是内容提供器中数据的唯一标识符,包括权限和路径。权限Authority用于唯一标识这个ContentProvider,对不同应用程序做区分,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。一般为了保证唯一性,避免冲突,是定义成该ContentProvider的包.类的名称
路径(path),通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义。


当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:

public class PersonContentProvider extends ContentProvider{
   public boolean onCreate()
   public Uri insert(Uri uri, ContentValues values)
   public int delete(Uri uri, String selection, String[] selectionArgs)
   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
   public String getType(Uri uri)}

第二步需要在AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
<manifest .... >
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <provider android:name=".PersonContentProvider" android:authorities="cn.itcast.providers.personprovider"/>
    </application>
</manifest>



调用ContentResolver的query()、insert()、delete()等方法,实质是调用了ContentProvider的响应的方法

ContentProvider是android组件之一,可以提供数据的跨应用程序访问,提供数据的跨进程无缝隙访问,所以是非常重要的东东。使用方法一般是

复制内容到剪贴板
代码:
getContentResolver().query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
那么下面来提几个问题:
1. 在应用程序A里面怎么跨进程拿到ContentProvider的对象呢?
2. ContentProvider实例对象是保存在哪里呢?
3. ContentProvider的方法实现要注意线程安全吗?

如果你能很清晰的回答这几个问题,那么下面的你就不需要继续看了,如果还有疑问,咱们一起往下面学习吧~

(二) 怎么跨进程拿到ContentProvider的对象
1. 我们来看ContentResolver.query方法是怎么实现的
a. 首先它会去找ContentProvider对象,是这样写的
复制内容到剪贴板
代码:
IContentProvider unstableProvider = acquireUnstableProvider(uri);
b. 然后acquireUnstableProvider(uri)方法是这样的:
复制内容到剪贴板
代码:
public final IContentProvider acquireUnstableProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        String auth = uri.getAuthority();//取得ContentProvider名字,拿这个名字去寻找对应的ContentProvider
        if (auth != null) {
            return acquireUnstableProvider(mContext, uri.getAuthority());
        }
        return null;
    }
在这段代码里面,关键地方在这里 String auth = uri.getAuthority();这里取得的auth就是我们在AndroidManifes.xml文件中配置的ContentProvider的android:authorities的值
如:
复制内容到剪贴板
代码:
<provider android:name=".TestProvider"
                android:authorities="com.android.test"></provider>
所以,这个android:authorities属性配置的就是该ContentProvider的名字,是它在Android系统中的名字,我们是通过这个名字去找对应的ContentProvider对象的。

c. ok..既然现在我们拿到ContentProvider的名字了,我们就来看看acquireUnstableProvider方法怎么通过名字来找到ContentProvider对象的。
这个acquireUnstableProvider方法会调用到ActivityThread的acquireProvider方法,这个方法的实现是:
复制内容到剪贴板
代码:
public final IContentProvider acquireProvider(Context c, String name, boolean stable) {
        IContentProvider provider = acquireExistingProvider(c, name, stable);
        if (provider != null) {
            return provider;
        }

        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + name);
            return null;
        }

        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }
这里就是查找ContentProvider实现的精髓所在了。。
首先,它去找acquireExistingProvider方法,这个方法其实就是根据我们传过来的名称在一个map里面找,如:
ProviderClientRecord pr = mProviderMap.get(name);
由于我们的ActivityThread和我们的应用程序还在一个进程里面,所以这个步骤我们可以理解为:在本地缓存中寻找ContentProvider对象
ok...在本地找了之后,如果找到了,就直接返回。
if (provider != null) {
            return provider;
        }
如果没有找到,就继续往下面走:
holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
这个方法就是调用到ActivityManagerService的getContentProvider方法去寻找ContentProvider.这里是一个跨进程调用,因为ActivityThread和ActivityManagerService不在一个进程里面。
而ActivityManagerService会把所有的ContentProvider都实例化出来,并且缓存在一个map里面,所以我们就可以通过
复制内容到剪贴板
代码:
holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), name, stable);
从ActivityManagerService远程得到一个ContentProvider对象。那么这一步,我们可以理解为:从远程服务中寻找ContentProvider对象
ok..从远程ActivityManagerService得到ContentProvider对象之后,我们继续往下面走。
复制内容到剪贴板
代码:
holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
首先,会调用installProvider方法,这个方法其实就是往本地的ContentProvider map缓存中添加一条缓存记录
ok...那么这整个过程,我们就可以理解为这样:
i.  第一步,它从ActivityThread里面本地缓存寻找ContentProvider对象,所以找到了,就一切ok..
ii. 第二步,如果第一步没有找到,那么就去ActivityManagerService远程服务中寻找ContentProvider对象。
iii.第三步,从远程服务中找到ContentProvider对象之后,就把这个对象缓存在本地,那么下次找的话,直接就可以从本地缓存中查找了。
那么,它为什么要有这个机制呢?个人猜测:因为跨进程调用是需要时间和资源消耗的,所以,它才有了本地缓存这么个东东。

(三) ContentProvider实例对象是保存在哪里
那么如果大家看完了上面一篇长篇大论,这个问题就很好回答了。
它储存在两个位置:
1. ActivityThread的本地map缓存中
2. ActivityManagerService的远程服务map缓存中

(四) ContentProvider的方法实现要注意线程安全吗
从上面一段描述来看,我们可以发现一个问题,ContentProvider在某种程度上是单例的,比如我们第一次从本地map缓存里面得到ContentProvider对象,第二次我们在同一个应用程序请求的时候,拿到的肯定是同一个缓存对象。
所以,ContentProvider只能配置进程之间是否是单例,同一个进程里面是不能配置是否是单例的,因为它在同一个进程里面肯定是单例。
配置进程之间是否是单例:
复制内容到剪贴板
代码:
android:multiprocess="true"
所以我们的ContentProvider的代码,比如查询,更新,删除等等,必须注意线程安全的问题
那么单例下,我们怎么注意线程安全问题呢?
1. ContentProvider尽量少用成员变量,因为我们用的是单例,所以成员变量是共享的。

2. 所以真的用到了共享资源,建议用synchronized或者TheadLocal来解决。


ContentProvider用到的UriMatcher。UriMatcher的一个重要的函数是match(Uri uri)。这个函数可以匹配Uri,根据传入的不同Uri返回不同的自定义整形值,以表明Uri访问的不同资源的类型。
例如:

?
1
2
3
4
5
6
public static final UriMatcher uriMatcher;
      static {
                     uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
                     uriMatcher.addURI(Book.AUTHORITY, "item", Book.ITEM);
                     uriMatcher.addURI(Book.AUTHORITY, "item/#", Book.ITEM_ID);
              }

静态代码块中初始化。这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。

addURI()方法是用来增加其他URI匹配路径的,

第一个参数传入标识ContentProvider的AUTHORITY字符串。

第二个参数传入需要匹配的路径,这里的#号为通配符,代表匹配任意数字,另外还可以用*来匹配任意文本。

第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。 例如:sMatcher.addURI(“com.test.provider.personprovider”, “person”, 1);如果match()方法匹配content://com.test.provider.personprovider/person路径,返回匹配码为1。



监听ContentProvider中数据的变化

如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:

?
1
2
3
4
5
6
public class PersonContentProvider extends ContentProvider {
   public Uri insert(Uri uri, ContentValues values) {
      db.insert("person", "personid", values);
      getContext().getContentResolver().notifyChange(uri, null);
   }
}


如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:监听ContentProvider的监听器需要继承ContentObserver并重写onChange方法,这样当CP中有数据变化时会调用onChange方法。用registerContentObserver进行注册

?
1
2
3
4
5
6
7
8
9
10
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
       true, new PersonObserver(new Handler()));
public class PersonObserver extends ContentObserver{
   public PersonObserver(Handler handler) {
      super(handler);
   }
   public void onChange(boolean selfChange) {
      //此处可以进行相应的业务处理
   }
}






Android四大组件——ContentProvider的增删改查和优化

前言ContentProvider步骤:创数据库,写规则匹配,配置manifests,另一个应用使用其实ContentProvider就是:在一个匹配器中写入自己写好的匹配规则,并对外提供写好的匹配规...
  • qq_30379689
  • qq_30379689
  • 2016年08月05日 15:53
  • 1922

ContentProvider的理解与使用

contentProvider的认识,自定义,使用,以及ContentObserver
  • u012858833
  • u012858833
  • 2016年06月19日 11:17
  • 9702

ContentProvider 的创建以及增删改查操作(你想看的都在这里了)

从创建一个ContentProvider开始到另外一个程序利用创建的ContentProvider来访问数据,达到数据的共享...
  • u010651155
  • u010651155
  • 2015年07月25日 10:52
  • 1388

Android 进阶11:进程通信之 ContentProvider 内容提供者

学习启舰大神,每篇文章写一句励志的话,与大家共勉。When you are content to be simply yourself and don’t compare or compete, ev...
  • u011240877
  • u011240877
  • 2017年06月02日 23:24
  • 3190

深入理解ContentProvider共享数据更新通知机制

ContentProvider的共享数据更新通知机制 ContentObserver
  • hehe26
  • hehe26
  • 2016年07月12日 20:17
  • 3669

学习ContentProvider---之一:查询数据库

今天看了android的官方文档中ContentProvider的那部分,因为数据库使用我一直很晕乎,我想要完成自己写一个provider,再写一个工程来使用它读数据,建数据,所以今天先学习了如何查询...
  • xijiaohuangcao
  • xijiaohuangcao
  • 2010年06月29日 17:59
  • 5446

Android之通过ContentProvider共享文件

使用ContentProvider共享文件时,一定要重写openFile方法,否则会报异常。 如: public class ContentProviderDemo extends Content...
  • zhangyongfeiyong
  • zhangyongfeiyong
  • 2016年07月08日 11:09
  • 744

ContentProvider的启动流程分析

ContentProvider是Android系统的四大组件之一,主要用于向外部提供数据。不仅可以向自己应用进程提供数据,也可以向其他进程的提供数据。所以在分析ContentProvider的时候我们...
  • zhenjie_chang
  • zhenjie_chang
  • 2017年03月17日 15:51
  • 763

Android ContentProvider封装数据库和文件读写总结

本文是我各处东拼西凑加上自己实践的一个ContentProvider使用总结,留做后用,主要介绍ContentProvider的集成方法。 一、综述 ContentProvider是Android四...
  • u013478336
  • u013478336
  • 2016年02月23日 12:04
  • 1623

自定义ContentProvider的一些细节探究

1.   适用范围 对于什么情况下才会用到自定义的ContentProvider,官方文档的Dev Guide是这样描述的: 如果你想要提供以下的一种或几种特性的时候你才需要构造一个Content...
  • sadfishsc
  • sadfishsc
  • 2012年04月01日 15:49
  • 8817
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ContentProvider
举报原因:
原因补充:

(最多只允许输入30个字)