【第22期】观点:IT 行业加班,到底有没有价值?

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) {
      //此处可以进行相应的业务处理
   }
}






举报

相关文章推荐

内容提供者ContentProvider和内容解析者ContentResolver

来源:http://blog.csdn.net/rankun1/article/details/51439574 简介 ContentProvider 在Android中的作用是对外共...

【攻克Android (45)】四大组件之 ContentProvider

[b][size=large]本文围绕以下两个部分展开:[/size][/b] [b][size=large]一、ContentProvider[/size][/b] [b][size=large]案例一:获得手机通讯录中的所有联系人[/size][/b] [b][...

程序员升职加薪指南!还缺一个“证”!

CSDN出品,立即查看!

ContentProvider 源码分析---之一

1, 相关类图 主要文件路径: packages/providers/ContactsProvider              frameworks/base ContentProvider是四大组...

【Android 开发教程】自定义ContentProvider

本章节翻译自《Beginning-Android-4-Application-Development》,如有翻译不当的地方,敬请指出。 原书购买地址<a target="_blank" href="http://www.amazon.com/Beginning-A...

Android插件化原理解析——ContentProvider的插件化

Android插件化原理解析——ContentProvider的插件化 发表于 2016-07-12   |     |   8700次阅读 目前为止我们已经完成了Android...
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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