Android 浅析 ContentProvider (三) 获取原理

Android 浅析 ContentProvider (三) 获取原理

前言

Linus Benedict Torvalds : RTFSC – Read The Fucking Source Code

ContentProvider下文将会简称CP。
ContentResolver下文将会简称CR。

概括

本文首先从ContentResolver一路深入浅析CP客户端如何找到对应的provider。

CP 获取原理

Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);

这是CP客户端用来启动服务端的代码。在获取到cursor后就可以从中取出数据集。

我们首先通过getContentResolver()来获取一个ContentResolve对象。

private final ApplicationContentResolver mContentResolver;
public ContentResolver getContentResolver() {return mContentResolver;}

通过代码我们可以知道返回的mContentResolver对象是ApplicationContentResolver类,而ApplicationContentResolver类又是继承于ContentResolver的。所以我们接下来首先分析下ContentResolver。

Step1、ContentResolver

SDK:provides applications access to the content model.
翻译:提供app访问内容模型。

CR 通过一套标准及统一的接口获取其他应用程序暴露的数据,那个标准就是URI,除了URI以外,还必须知道需要获取的数据段的名称,以及此数据段的数据类型。

主要方法

因为CP是以类似数据库中表的方式将数据暴露出去,那么CR也将采用类似数据库的操作来从CP中获取数据。

insert
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values){}
public final int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] values){}

insert:
Inserts a row into a table at the given URL.
If the content provider supports transactions the insertion will be atomic.
翻译:在给定的URL插入一行到表里面,如果CP支持,处理过程将是原子操作。

bulkInsert:
Inserts multiple rows into a table at the given URL.
This function make no guarantees about the atomicity of the insertions.
翻译:在给定的URL插入多行到表里面,处理过程不对原子操作作保证。

delete
public final int delete(Uri url, String where, String[] selectionArgs){}

Deletes row(s) specified by a content URI.
If the content provider supports transactions, the deletion will be atomic.
翻译:删除一行或多行由uri指定,如果CP支持,处理过程将是原子操作。

update
public final int update(Uri uri, ContentValues values, String where,
            String[] selectionArgs) {}

Update row(s) in a content URI.
If the content provider supports transactions the update will be atomic.
翻译:在给定的URL更新多行,如果CP支持,处理过程将是原子操作。

Query
public final Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder) {}
public final Cursor query(final Uri uri, String[] projection,
            String selection, String[] selectionArgs, String sortOrder,
            CancellationSignal cancellationSignal) {}

Query the given URI, returning a Cursor over the result set.
翻译:从给定的URI中查询,从结果集中返回一个Cursor对象。
注意:
1:提供一个明确的空间,防止从不希望被使用的内存中读取数据。
2:使用问号参数标记,如“电话=?”而不是在选择参数中的显式值,因此,不同的值的查询将被确认为缓存的目的相同的。

call
public final Bundle call(Uri uri, String method, String arg, Bundle extras) {}

Call a provider-defined method. This can be used to implement read or write interfaces which are cheaper than using a Cursor and/or do not fit into the traditional table model.
翻译:
调用提供者定义方法。这可以用来实现读写接口,比使用游标和/或不符合传统的表模型更好。

核心方法

在insert、query、updata这些方法中都调用一个最主要的方法acquireProvider()。

acquireProvider
//Returns the content provider for the given content URI.
public final IContentProvider acquireUnstableProvider(Uri uri) {
    ...
    return acquireUnstableProvider(mContext, uri.getAuthority());
}

acquireProvider()是一个抽象函数,由子类去实现细节。那么在android中,实现细节的子类就是ApplicationContentResolver

Step2、ApplicationContentResolver

ApplicationContentResolver是contextimpl的内部类,继承ContentResolver。其内部封装了一个ActivityThread对象,最后调用的方法都是调用ActivityThread的方法,所以ApplicationContentResolver就是一个中间过度类。

接着我们来看下ActivityThread关于acquireProvider()的核心方法:

Step3、ActivityThread

acquireProvider:

public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
    final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
    if (provider != null) {return provider;}

    IActivityManager.ContentProviderHolder holder = null;
    holder = ActivityManagerNative.getDefault().getContentProvider(
            getApplicationThread(), auth, userId, stable);
    if (holder == null) {return null;}

    holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

这段函数主要流程:
1、从已经保存的本地provider中查找是否有对应的provider,有则将其返回退出。
2、从AMS中找到对应的provider。
3、安装从AMS中找到的provider。并且将provider保存在本地。
4、返回此provider。

最后我们来看下ActivityManagerService关于getContentProvider()的核心方法:

Step4、ActivityManagerService

getContentProvider:

public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
    enforceNotIsolatedCaller("getContentProvider");
    return getContentProviderImpl(caller, name, null, stable, userId);
}

private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
    ...
    ContentProviderRecord cpr;
    
    // First check if this content provider has been published...
    cpr = mProviderMap.getProviderByName(name, userId);
    ...
    if (!providerRunning) {
        cpi = AppGlobals.getPackageManager().resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
    }         
    ...
    cpr = mProviderMap.getProviderByClass(comp, userId);
    if (firstClass) {
        try {
            ApplicationInfo ai = AppGlobals.getPackageManager().
                getApplicationInfo(cpi.applicationInfo.packageName, STOCK_PM_FLAGS, userId);
            cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
    ...
    if (firstClass) {
        mProviderMap.putProviderByClass(comp, cpr);
    }
    mProviderMap.putProviderByName(name, cpr);
    ...
    // Wait for the provider to be published... 
    synchronized (cpr) {  
        while (cpr.provider == null) {  
            ......  
            try {  
                cpr.wait();  
            } 
        }  
    }  
    return cpr != null ? cpr.newHolder(conn) : null;
}

这段函数主要从AMS中查找已经注册了的provider,然后将provider对象返回给客户端。
在ActivityManagerService中,有两个成员变量是用来保存系统中的Content Provider信息的,一个是mProvidersByName,一个是mProvidersByClass,前者是以Content Provider的authoriry值为键值来保存的,后者是以Content Provider的类名为键值来保存的。这里要用两个Map来保存,这里为了方便根据不同条件来快速查找而设计的。
如果在mProviderMap里找不到对应的provider,会通过AppGlobals.getPackageManager()从PackageManagerService里获取。然后保存到mProviderMap里面。

Step5、PackageManagerService

resolveContentProvider:

public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
    synchronized (mPackages) {
        final PackageParser.Provider provider = mProvidersByAuthority.get(name);
        PackageSetting ps = mSettings.mPackages.get(provider.owner.packageName);
        return PackageParser.generateProviderInfo(provider, flags,
                        ps.readUserState(userId), userId);
    }
}

查找的最底层就是在PMS里面,PMS里面的mProvidersByAuthority保存了本机所有apk包含的provider定义。通过它可以找到所有对应的provider。(终)

小结

ContentProvider的整个获取原理比较简单,并没有太难的地方,主要还是一层层调用比较费劲,封装了几层。
再来总结下获取的整个流程(以query函数为例):
1、首先每个context类都会内部包含了一个ContentResolver的子对象ApplicationContentResolver。
2、通过调用ApplicationContentResolver的主要方法query来获取CP的数据库数据。
3、调用的过程首先会调用ContentResolver的核心方法acquireProvider()。而acquireProvider()方法是一个抽象方法,其实现是交由子类实现。
4、通过子类的acquireProvider()方法实现了解到主要的实现是交由ActivityThread类来完成。
5、ActivityThread类会做出一个判断,如果本地保存一个需要获取的CP对象实例,就会直接返回这个对象实例,如果没有保存,则会访问AMS对象去查找获取一个对象的CP对象实例,当找到这个对象实例后会保存到本地以便日后快速获取。
6、如果在AMS里面没有找到,就会继续深入到PMS里去从全部的provider中查找。
7、获取到CP对象实例后会通过层层返回,最后再调用该CP对象的query方法获取相应的数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容