ContentProvider学习笔记

一、什么ContentProvider

。。。

二、如何使用ContentProvider

。。。

三、沙场练兵-实例操练

。。。

四、深入理解ContentProvider原理

为什么使用ContentProvider可以实现跨进程的通讯,第一反应肯定是这货和binder有关,因为android中只有稍微跟跨进程搭上边的,必定想到binder。

下面就来分析ContentProvider是怎么一步一步利用binder实现跨进程通信的:

1、首先你得创建一个ContentProvider运行在进程A,如上篇博客        

  AndroidManifest.xml中定义provider:

<provider
    android:name=".MyContentProvider"
    android:authorities="telefk"//这个很重要,Uri中的主机名,查询数据就靠它
    android:enabled="true"
    android:exported="true">
</provider>

 

2、进程B中通过如下接口来访问进程A的数据:

    getContentResolver().query(uri,columns,null,null,null);

getContentResolver()实际调用的ContextImpl.java的方法返回mContentResolver对象

此对象在ContextImpl的构造方法中实例化

    mContentResolver = new ApplicationContentResolver(this, mainThread, user);

现在来看query方法,其在ContentResolver.java中定义:

public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
        @Nullable String selection, @Nullable String[] selectionArgs,
        @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
    Preconditions.checkNotNull(uri, "uri");
    IContentProvider unstableProvider = acquireUnstableProvider(uri);//这个函数很关键,通过指定的Uri找到我们刚刚定义的provider
    if (unstableProvider == null) {
           return null;
    }
    IContentProvider stableProvider = null;
      ......

接下来看acquireUnstableProvider()函数:

百转千回,最终走到了刚刚返回的ApplicationContentResolver.java中

@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
    return mMainThread.acquireProvider(c,
            ContentProvider.getAuthorityWithoutUserId(auth),
            resolveUserIdFromAuthority(auth), false);
}

这里又调用了ActivityThread.java 的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; } // 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(), auth, userId, stable);//很关键,请注意这里跨进程调到ActivityManagerService.java的方法 } catch (RemoteException ex) { } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); 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; }

  我们继续来看ActivityManagerService.java 的getContentProvider()方法:

    @Override
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
        enforceNotIsolatedCaller("getContentProvider");
        if (caller == null) {
            String msg = "null IApplicationThread when getting content provider "
                    + name;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
        // with cross-user grant.
        return getContentProviderImpl(caller, name, null, stable, userId);
    }

    肾呐~还没到头!又继续往下走,大家坚持:

    
//这个方法很长,我们挑选重点的分析,方法第二个参数name就是我们刚刚query传进来的uir的authority
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, String name, IBinder token, boolean stable, int userId) { ContentProviderRecord cpr;//AMS中用来记录provider的,还有其他三大组件AcitityRecord、ServiceRecord、BroadcastRecord ContentProviderConnection conn = null; ProviderInfo cpi = null;//这个应该是PMS解析应用的AndroidManifest得来的信息
......

     // First check if this content provider has been published...
     cpr = mProviderMap.getProviderByName(name, userId);

     ......

如果发现对应的provider 已经运行,这个我们之后再分析,先分析没有运行的情况:

  boolean providerRunning = cpr != null;
            if (providerRunning) {
                cpi = cpr.info;
                String msg;
                checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
                        != null) {
                    throw new SecurityException(msg);
                }
                checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");

                if (r != null && cpr.canRunHere(r)) {
                    // This provider has been published or is in the process
                    // of being published...  but it is also allowed to run
                    // in the caller's process, so don't make a connection
                    // and just let the caller instantiate its own instance.
                    ContentProviderHolder holder = cpr.newHolder(null);
                    // don't give caller the provider object, it needs
                    // to make its own.
                    holder.provider = null;
                    return holder;
                }

                final long origId = Binder.clearCallingIdentity();

                checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");

                // In this case the provider instance already exists, so we can
                // return it right away.
                conn = incProviderCountLocked(r, cpr, token, stable);
                if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
                        // If this is a perceptible app accessing the provider,
                        // make sure to count it as being accessed and thus
                        // back up on the LRU list.  This is good because
                        // content providers are often expensive to start.
                        checkTime(startTime, "getContentProviderImpl: before updateLruProcess");
                        updateLruProcessLocked(cpr.proc, false, null);
                        checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
                    }
                }

                if (cpr.proc != null) {
                    if (false) {
                        if (cpr.name.flattenToShortString().equals(
                                "com.android.providers.calendar/.CalendarProvider2")) {
                            Slog.v(TAG, "****************** KILLING "
                                + cpr.name.flattenToShortString());
                            Process.killProcess(cpr.proc.pid);
                        }
                    }
                    checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
                    boolean success = updateOomAdjLocked(cpr.proc);
                    maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
                    checkTime(startTime, "getContentProviderImpl: after updateOomAdj");
                    if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success);
                    // NOTE: there is still a race here where a signal could be
                    // pending on the process even though we managed to update its
                    // adj level.  Not sure what to do about this, but at least
                    // the race is now smaller.
                    if (!success) {
                        // Uh oh...  it looks like the provider's process
                        // has been killed on us.  We need to wait for a new
                        // process to be started, and make sure its death
                        // doesn't kill our process.
                        Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString()
                                + " is crashing; detaching " + r);
                        boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
                        checkTime(startTime, "getContentProviderImpl: before appDied");
                        appDiedLocked(cpr.proc);
                        checkTime(startTime, "getContentProviderImpl: after appDied");
                        if (!lastRef) {
                            // This wasn't the last ref our process had on
                            // the provider...  we have now been killed, bail.
                            return null;
                        }
                        providerRunning = false;
                        conn = null;
                    }
                }

                Binder.restoreCallingIdentity(origId);
            }

  

            if (!providerRunning) {
                try {
                    checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
                    cpi = AppGlobals.getPackageManager().
                        resolveContentProvider(name,
                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
                    checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
                } catch (RemoteException ex) {
                }
                if (cpi == null) {
                    return null;
                }
                // If the provider is a singleton AND
                // (it's a call within the same user || the provider is a
                // privileged app)
                // Then allow connecting to the singleton provider
                singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                        cpi.name, cpi.flags)
                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
                if (singleton) {
                    userId = UserHandle.USER_OWNER;
                }
                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
                checkTime(startTime, "getContentProviderImpl: got app info for user");

                String msg;
                checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
                        != null) {
                    throw new SecurityException(msg);
                }
                checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");

                if (!mProcessesReady && !mDidUpdate && !mWaitingUpdate
                        && !cpi.processName.equals("system")) {
                    // If this content provider does not run in the system
                    // process, and the system is not yet ready to run other
                    // processes, then fail fast instead of hanging.
                    throw new IllegalArgumentException(
                            "Attempt to launch content provider before system ready");
                }

                // Make sure that the user who owns this provider is running.  If not,
                // we don't want to allow it to run.
                if (!isUserRunningLocked(userId, false)) {
                    Slog.w(TAG, "Unable to launch app "
                            + cpi.applicationInfo.packageName + "/"
                            + cpi.applicationInfo.uid + " for provider "
                            + name + ": user " + userId + " is stopped");
                    return null;
                }

                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
                checkTime(startTime, "getContentProviderImpl: before getProviderByClass");
                cpr = mProviderMap.getProviderByClass(comp, userId);
                checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
                final boolean firstClass = cpr == null;
                if (firstClass) {
                    final long ident = Binder.clearCallingIdentity();
                    try {
                        checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
                        ApplicationInfo ai =
                            AppGlobals.getPackageManager().
                                getApplicationInfo(
                                        cpi.applicationInfo.packageName,
                                        STOCK_PM_FLAGS, userId);
                        checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");
                        if (ai == null) {
                            Slog.w(TAG, "No package info for content provider "
                                    + cpi.name);
                            return null;
                        }
                        ai = getAppInfoForUser(ai, userId);
     cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);//很关键的,首先创建cpr,然后再启动目标进程


 

客户端(应用B)获得cotentprovider 客户端的代理ContentProviderProxy对象,然后调用它的query方法,切记此时还是运行在自己进程中!

final class ContentProviderProxy implements IContentProvider
{
    public ContentProviderProxy(IBinder remote)
    {
        mRemote = remote;
    }

    public IBinder asBinder()
    {
        return mRemote;
    }
    public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
            String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
        ......
     data.writeString(sortOrder);      data.writeStrongBinder(adaptor.getObserver().asBinder());      data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null);      mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); ......

  经过query方法,终于走到了应用A进程

    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        try {
            switch (code) {
                case QUERY_TRANSACTION:
                {
                    data.enforceInterface(IContentProvider.descriptor);

                    String callingPkg = data.readString();
                    Uri url = Uri.CREATOR.createFromParcel(data);
                    ......
                    String sortOrder = data.readString();
                    IContentObserver observer = IContentObserver.Stub.asInterface(
                            data.readStrongBinder());
                    ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
                            data.readStrongBinder());

                    Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs,
                            sortOrder, cancellationSignal);//经过这个query方法的转换,最终会走到我们自己定义Contentrovider的query方法
                    try {
                        adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
                                getProviderName());
                        cursor = null;

                        BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
                        adaptor = null;

                        reply.writeNoException();
                        reply.writeInt(1);

  cursor从进程A传到进程B,BulkCursorDescriptor肯定是Parcelable类型

 大体流程是这样,难点是中间的细节,特别是获得provider这个过程非常繁琐,设计到很多数据结构和其他模块的内容,这块需要慢慢啃。

 

 

 

一.Android四大组件

Android四大组件是Activity, Service, Content Provider, Broadcast Receiver。

Activity作为程序界面,直接与用户交互

Service运行在后台,没有界面,完成特定的功能

ContentProvider维护应用数据,方便应用本身或其它应用访问

Broadcast Receiver提供异步广播消息接收机制,便于各应用/组件进行交互

 

通过AndroidManifest.xml,  可以看到一个应用使用了哪些组件:

 Android <wbr>ContentProvider详解
   attribute的定义可以参考http://developer.android.com/guide/topics/manifest/manifest-intro.html

   下面重点探讨Content Provider的实现和使用。

 

二. 什么是ContentProvider

Content Provider维护特定的应用数据,并可以让其它应用轻松访问该数据。对数据使

用者来说它是数据提供者。它提供统一的接口对数据进行操作,使用者不用关心数据到底是如何存储的以及数据类型到底是什么。也就是说,Content Provider作为数据提供者,提供了对外共享本地数据一种机制,使Android应用能方便地基于该机制进行数据访问。

    为了便于管理和访问,每个Content Provider必须有唯一标示,用Uri表示。Uri类似http url, 构成如下:

    content://authority/path

    所有Content Provider的Uri必须以content://开头,这是Android规定的。

    authority是个字符串,它由开发者自己定义,用于来唯一标示一个ContentProvider。系统会根据这个标示查找ContentProvider。

    path也是字符串,表示要操作的数据。可根据自己的实现逻辑来指定:
content://contacts/people表示要操作ContentProvider为contacts下的people表

content://com.android.contacts/people/#表示要操作表people中特定id的行(记录)。

content://downloads/download/10/name表示要操作id为10的行的name字段。

content://downloads/download/*表示操作download表中的所有字段。

总之,#匹配一个数字字符串,*匹配一个文本字符串。

 

三.ContentProvider 的实现和使用    

Android <wbr>ContentProvider详解

    可以看出 , 实现一个自定义的Content Provider,要基于系统提供的基类ContentProvider,需要实现6个接口。大部分接口就是类似数据库的数据操作接口,实际上Content Provider是需要创建数据库并对数据库进行操作的。完成实现之后,在Androidmanifest.xml中声明自己的Content Provider以及与Provider相关的permission声明(可以没有permission定义)。例如:

Android <wbr>ContentProvider详解
    最后整个应用被编译成apk。安装之后,该应用里的contentProvider就可以被其它应用访问了。对于Provider使用者来说,  如果特定Provider有permission要求,则要在自己的Androidmanifest.xml中添加指定Permission引用, 如:

Android <wbr>ContentProvider详解

    使用非常简单,Android提供了Context级别的ContentResolver对象来对Content Provider进行操作。正是因为有了ContentResolver, 使用者才不用关心Provider到底是哪个应用或哪个类实现的。只要知道它的uri就能访问。ContentResolver对象存在于每个Context中。几乎所有对象都有自己的Context。

Android <wbr>ContentProvider详解
     有些情况下,Content Provider使用者想监听数据的变化,可以注册一个Observer:

 

Android <wbr>ContentProvider详解


 

四. ContentProvider内部机制

 

1.ContentProvider接口调用过程

    ContentProvider依赖ContentResolver/ActivityThread/ActivityManagerService对外提供

服务。虽然ContentProvider的用法以及表现形式不是一个Service,实际上它可以看作是ActivityManagerService提供的一种服务, 它实现了IBinder接口。

    首先调用者通过特定uri调用特定ContentProvider的接口函数,比如insert(),  此时ContentResolver会通过uri获取特定ContentProvider的实例,ActivityThread检查本地Cache,如果发现此ContentProvider已经被引用过,则直接直接取出ContentProvider返回给调用者。如果没有发现,由于 ContentProvider可能已经被load了,可能还没有load;可能要创建Process,可能要检查permission,所以ActivityThread调用到ActivityManagerService来进行相关处理/检查。如果该Provider是Single Process,ActivityManagerService会为ContentProvider创建一个独立Process;如果是MultiProcess,说明每个调用者可以拥有独立的ContentProvider实例,于是ActivityManagerService只是返回ContentProvider的相关信息给ActivityThread,由ActivityThread负责ContentProvider的实例化,此时ContentProvider运行在调用者Process中。实例化后,IConentProvider会返回给调用者,通过该接口可以调用所需功能。

    ActivityThread本地维护一个mProviderMap <ProviderName, ProviderRecord >,记录已被引用的ContentProvider,  同时使用引用计数mProviderRefCountMap <IBinder, ProviderRefCount>记录特定ContentProvider的引用情况。

Android <wbr>ContentProvider详解

 

2.ContentProvider实例创建过程

     ContentProvider实例的创建与multiprocess属性有关系(Androidmanifest.xml里指定),个人认为理解成多进程并不准确。应该理解为ContentProvider的多实例,不会存在多个ContentProvider进程的情况,ContentProvider 可能存在多个实例。

1) 对于android:multiprocess=true的ContentProvider,意味着可以多实例,那么由调用者在自己的进程空间实例化一个ContentProvider对象,此时定义ContentProvider的App可能并没有启动

Android <wbr>ContentProvider详解

 

注意:ContentProvider是否多实例,还得看contentProvider的uid与调用者的uid是否相同或contentProvider的uid是System user。具体逻辑是:

public boolean canRunHere(ProcessRecord app) {

        return (info.multiprocess || info.processName.equals(app.processName))

                && (uid == Process.SYSTEM_UID || uid == app.info.uid);

}

 

2)对于android:multiprocess=false(默认值)的ContentProvider,由系统把定义该ContentProvider的App启动起来(一个独立的Process)并实例化ContentProvider,这种ContentProvider只有一个实例,运行在自己App的Process中。所有调用者共享该ContentProvider实例,调用者与ContentProvider实例位于两个不同的Process

Android <wbr>ContentProvider详解

 

其中Process.start()->zygoteSendArgsAndGetPid()->ZygoteInit.runSelectLoopMode()

-> ZygoteConnection.runOnce() -> Zygote.forkAndSpecialize()->RuntimeInit.zygoteInit()

-> invokeStaticMain()->MethodAndArgsCaller.run()->Method.invokeNative

->ActivityThread.main(),这是通用的APK启动流程。

 

3)ContentProvider的加载/发布过程

   ContentProvider不能单独发布,总是被打包到某个Android应用(apk)里。APK被安装之后,实例化之后每个Android应用都有一个Application实例,每个Activity或Service有一个ApplicationContext实例。

   Android应用程序的入口函数是ActivityThread.main(), 该函数不仅创建了ActivityThread实例以及消息循环机构,而且创建了ApplicationThread实例,通过此实例向Activity Manager Service(AMS)提供IApplicationThread接口,AMS正是通过该接口调度和管理Activity。  

   ActivityThread通过attachApplication()把自己的ApplicationThread实例告知AMS。

AMS根据thread信息更新进程记录(ProcessRecord)并调用thread的bindApplication()进行初始化工作并创建ApplicationContext和Application实例,然后安装package里声明的所有contentProvider。 主要过程如下:

Android <wbr>ContentProvider详解

AMS维护了很多信息,其中比较重要的有:

mProcessNames:      包名(processName)和进程信息(ProcessRecord)映射表

mProvidersByName:   Provider发布名和Provider信息(ContentProviderRecord)映射表

mProvidersByClass:  Provider类名和Provider信息(ContentProviderRecord)映射表

conProviders:属于ProcessRecord信息,特定Process正在使用的ContentProvider及其个数映射表       

pubProviders:属于ProcessRecord信息,特定Process已经Published的Provider类名和Provider信息

              (ContentProviderRecord)映射表

ActivityThread维护了3个与ContentProvider相关的Map:

mProviderMap:         记录本应用已使用的Provider信息:<Provider发布名, ProviderRecord>

mProviderRefCountMap:  记录本应用已使用的Provider引用计数信息:<IBinder, ProviderRefCount>

mLocalProviders:      记录本地加载的Provider信息:<IBinder, ProviderRecord>

 

4)ContentProvider通知机制

Android <wbr>ContentProvider详解
 

注意:这个通知机制需要ContentProvider的实现者在实现insert/delete/query/update接口时调用ContentResolver的notifyChange(), 否则没法实现数据变化的通知。

 

 

 

 

 

 
 

  

 

转载于:https://www.cnblogs.com/macdroid/p/5624984.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值