`
wxlgzxx_1988
  • 浏览: 66221 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Android 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;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        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;
        }

        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        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不在一个进程里面。
至于ActivityThread和ActivityManagerService的关系,可以参考我以前的这篇帖子:
http://bbs.51cto.com/thread-1008812-1.html

而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的代码,比如查询,更新,删除等等,必须注意线程安全的问题。
那么单例下,我们怎么注意线程安全问题呢?
1. ContentProvider尽量少用成员变量,因为我们用的是单例,所以成员变量是共享的。
2. 所以真的用到了共享资源,建议用synchronized或者TheadLocal来解决。至于synchronized和TheadLocal的区别,这篇文章就不讨论了,下次有机会再写吧。。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics