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方法获取相应的数据。

你可能感兴趣的:(Android 浅析 ContentProvider (三) 获取原理)