ContentProvider笔记[转]

关于ContentProvider网上有很多文章进行了详细的分析,这里我初略地画了一个getContentProvider时序图,帮助整体上进行理解把握。其中ActivityThread_A代表进程A的ActivityThread,ActivityThread_B代表进程B的ActivityThread,这里是在进程A中去get进程B中的ContentProvider。有四个关键点,下面将一一详细说明下。


节点1、在getContentProviderImpl()函数中会判断ContentProvider所在的目标进程B是否已经启动,如果未启动,那么会先启动目标进程B,然后当前线程会wait在一个ContentProviderRecord对象上,只有等到进程B启动并将ContentProvider publish到AMS中,通过notifyAll()唤醒,前面那个线程才会继续执行。
节点2、进程B启动过程中,在handleBindApplication()函数中进行ContentProvider安装,安装函数便是installProvider(),我们可以看到关键点4也是调用installProvider()进行ContentProvider安装,为什么会有两次ContentProvider安装呢?是因为同时需要在进程A和进程B中进行安装,那么进程A、B中安装有何不同?答案就在installProvider()函数中,installProvider()具体分析见下文。
节点3、当进程B本地安装完ContentProvider,然后夸Binder调用AMS.publishContentProviders()接口将ContentProvider中Transport 的Binder实体传递到AMS中,并调用notifyAll()进行唤醒上面所说的等待线程。
节点4、进程A中调用完AMS.getContentProvider()后立即调用installProvider()进行ContentProvider本地安装,这是第二次调用installProvider(),具体分析见下面。

installProvider()函数分析:
在分析这个函数之前我们先要搞清楚ContentProviderHolder类的两个成员变量provider、connection。在进程B启动过程中,会实例化ContentProvider对象, ContentProvider中有一个Transport类型的Binder本地对象mTransport,进程A就是要拿到这个mTransport的代理来与B进程中的ContentProvider建立Binder通信。
那进程A是如何拿到进程B中ContentProvider.mTransport的代理的呢?mTransport是一个匿名Binder对象,我们知道匿名Binder对象是可以在已经建立起的Binder通信的链路中进行传递的,于是,mTransport便是这样传递到进程A并保存在ContentProviderHolder.provider中:
①进程B启动安装ContentProvider时通过publishContentProviders()接口将mTransport传递到AMS,并保存在ContentProviderRecord.provider中;
②进程A调用AMS.getContentProvider()接口,AMS会将ContentProviderRecord.provider和新创建的ContentProviderConnection匿名Binder对象一并传递到进程A中,分别保存在ContentProviderHolder.provider和ContentProviderHolder.connection中。
注意,进程A中ContentProviderHolder.provider与AMS中的ContentProviderRecord.provider均是进程B中mTransport的Binder代理。进程A拿到了进程B中ContentProvider.mTransport代理,便建立Binder通信。更具体的ContentProvider操作在自定义的ContentProvider中实现,Transport只是通信的封装。



了解了上面这些信息我们再来看installProvider()函数。在上面的时序图中有两次调用,一次在进程B中,一次在进程A中。这里我们只关心installProvider(..,holder,..)函数的第二个参数holder。holder是一个ContentProviderHolder类型参数,如果holder.provider为null,那边需要在本次installProvider()函数调用中实例化ContentProvider对象,如果holder.provider不为null,则无需实例化ContentProvider对象。在进程B中installProvider()函数调用参数holder.provider=null,在进程A中installProvider()函数调用参数holder.provider!=null,因此进程B中会实例化ContentProvider对象,而进程A中holder.provider保存着进程B中ContentProvider.mTransport的代理,这一点读者稍微跟下代码便可发现。

private IActivityManager.ContentProviderHolder installProvider(Context context,  
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,  
        boolean noisy, boolean noReleaseNeeded, boolean stable) {  
    ContentProvider localProvider = null;  
    IContentProvider provider;  
    if (holder == null || holder.provider == null) {    //holder.provider=null,意味着需要在此实例化ContentProvider对象。进程B中调用该函数满足这个条件。  
        if (DEBUG_PROVIDER || noisy) {  
            Slog.d(TAG, "Loading provider " + info.authority + ": "  
                    + info.name);  
        }  
        Context c = null;  
        ApplicationInfo ai = info.applicationInfo;  
        Slog.d(TAG, "installProvider: context.getPackageName()=" + context.getPackageName());  
        if (context.getPackageName().equals(ai.packageName)) {  
            c = context;  
        } else if (mInitialApplication != null &&  
                mInitialApplication.getPackageName().equals(ai.packageName)) {  
            c = mInitialApplication;  
        } else {  
            try {  
                c = context.createPackageContext(ai.packageName,  
                        Context.CONTEXT_INCLUDE_CODE);    //创建ContentProvider所在的package的Context;  
            } catch (PackageManager.NameNotFoundException e) {  
                // Ignore  
            }  
        }  
        if (c == null) {  
            Slog.w(TAG, "Unable to get context for package " +  
                  ai.packageName +  
                  " while loading content provider " +  
                  info.name);  
            return null;  
        }  
        try {  
            final java.lang.ClassLoader cl = c.getClassLoader();  
            localProvider = (ContentProvider)cl.  
                loadClass(info.name).newInstance();    //实例化ContentProvider对象;  
            provider = localProvider.getIContentProvider();  
            if (provider == null) {  
                Slog.e(TAG, "Failed to instantiate class " +  
                      info.name + " from sourceDir " +  
                      info.applicationInfo.sourceDir);  
                return null;  
            }  
            if (DEBUG_PROVIDER) Slog.v(  
                TAG, "Instantiating local provider " + info.name);  
            // XXX Need to create the correct context for this provider.  
            localProvider.attachInfo(c, info);  
        } catch (java.lang.Exception e) {  
            if (!mInstrumentation.onException(null, e)) {  
                throw new RuntimeException(  
                        "Unable to get provider " + info.name  
                        + ": " + e.toString(), e);  
            }  
            return null;  
        }  
    } else {                        //holder.provider!=null,进程A中调用该函数时满足这个逻辑,holder.provider指向mTransport的代理;  
        provider = holder.provider;  
        if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "  
                + info.name);  
    }  
  
    IActivityManager.ContentProviderHolder retHolder;  
    //下面的逻辑主要是进行本地保存provider,方便第二次调用同一个ContentProvider时,无需重新到AMS中去查询。  
    synchronized (mProviderMap) {  
        if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider  
                + " / " + info.name);  
        IBinder jBinder = provider.asBinder();  
        if (localProvider != null) {  
            ComponentName cname = new ComponentName(info.packageName, info.name);  
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);  
            if (pr != null) {  
                if (DEBUG_PROVIDER) {  
                    Slog.v(TAG, "installProvider: lost the race, "  
                            + "using existing local provider");  
                }  
                provider = pr.mProvider;  
            } else {  
                holder = new IActivityManager.ContentProviderHolder(info);  
                holder.provider = provider;  
                holder.noReleaseNeeded = true;  
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);  
                mLocalProviders.put(jBinder, pr);  
                mLocalProvidersByName.put(cname, pr);  
            }  
            retHolder = pr.mHolder;  
        } else {  
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);  
            if (prc != null) {  
                if (DEBUG_PROVIDER) {  
                    Slog.v(TAG, "installProvider: lost the race, updating ref count");  
                }  
                // We need to transfer our new reference to the existing  
                // ref count, releasing the old one...  but only if  
                // release is needed (that is, it is not running in the  
                // system process).  
                if (!noReleaseNeeded) {  
                    incProviderRefLocked(prc, stable);  
                    try {  
                        ActivityManagerNative.getDefault().removeContentProvider(  
                                holder.connection, stable);  
                    } catch (RemoteException e) {  
                        //do nothing content provider object is dead any way  
                    }  
                }  
            } else {  
                ProviderClientRecord client = installProviderAuthoritiesLocked(  
                        provider, localProvider, holder);  
                if (noReleaseNeeded) {  
                    prc = new ProviderRefCount(holder, client, 1000, 1000);  
                } else {  
                    prc = stable  
                            ? new ProviderRefCount(holder, client, 1, 0)  
                            : new ProviderRefCount(holder, client, 0, 1);  
                }  
                mProviderRefCountMap.put(jBinder, prc);  
            }  
            retHolder = prc.holder;  
        }  
    }  
  
    return retHolder;  
}  

上面所说的只是普通情况,当然还有特殊情况。比如这个ContentProvider定义时设置了multiprocess=true,那么ContentProvider便会在进程A中进行实例化,而是不是在进程B中实例化,同时也没必要启动进程B了,意味着哪个进程去get ContentProvider,哪个进程自己实例化ContentProvider,这个读者可以仔细分析AMS.getContentProviderImpl()函数什么时候return的holder.provider = null来验证这一点。

附getContentProviderImpl()函数分析

private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,  
        String name, IBinder token, boolean stable, int userId) {  
    ContentProviderRecord cpr;  
    ContentProviderConnection conn = null;  
    ProviderInfo cpi = null;  
  
    synchronized(this) {  
        ProcessRecord r = null;  
        if (caller != null) {  
            r = getRecordForAppLocked(caller);       //①获取调用进程记录信息块  
            if (r == null) {  
                throw new SecurityException(  
                        "Unable to find app for caller " + caller  
                      + " (pid=" + Binder.getCallingPid()  
                      + ") when getting content provider " + name);  
            }  
        }  
  
        // First check if this content provider has been published...  
        cpr = mProviderMap.getProviderByName(name, userId);   //②根据authority获取ContentProviderRecord,如果之前publish过ContentProvider,那么返回值必然不为null。  
        boolean providerRunning = cpr != null;  
        if (providerRunning) {         //②providerRunning=true表示ContentProvider已经启动过了;  
            cpi = cpr.info;  
            String msg;  
            if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {  
                throw new SecurityException(msg);  
            }  
  
            if (r != null && cpr.canRunHere(r)) {            //③如果ContentProvider允许多进程实例化或目标进程就是调用进程,那么直接返回。返回的时候将ContentProviderHolder. Provider置空,这样调用进程便会在自己的进程中实例化ContentProvider。  
                // 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();  
  
            // In this case the provider instance already exists, so we can  
            // return it right away.  
            conn = incProviderCountLocked(r, cpr, token, stable);<span style="white-space:pre">   </span>//④创建一个ContentProviderConnection;  
            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.  
                    updateLruProcessLocked(cpr.proc, false, null);  
                }  
            }  
  
            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);  
                    }  
                }  
                boolean success = updateOomAdjLocked(cpr.proc);    //返回false表示cpr.proc被杀了  
                if (DEBUG_PROVIDER) Slog.i(TAG, "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);  
                    appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);  
                    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);  
        }  
  
        boolean singleton;  
        if (!providerRunning) {        // ⑤providerRunning=false,有两种情况:ContentProvider所在进程没有启动过、ContentProvider所在进程启动了,并publish了ContentProvider,但是挂掉了。  
            try {  
                cpi = AppGlobals.getPackageManager().  
                    resolveContentProvider(name,  
                        STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);  
            } catch (RemoteException ex) {  
            }  
            if (cpi == null) {  
                return null;  
            }  
            singleton = isSingleton(cpi.processName, cpi.applicationInfo,  
                    cpi.name, cpi.flags);   
            if (singleton) {  
                userId = 0;  
            }  
            cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);  
  
            String msg;  
            if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {  
                throw new SecurityException(msg);  
            }  
  
            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 started.  If not,  
            // we don't want to allow it to run.  
            if (mStartedUsers.get(userId) == null) {  
                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);  
            cpr = mProviderMap.getProviderByClass(comp, userId);          
            final boolean firstClass = cpr == null;  
            if (firstClass) {                        //这是第一种情况,ContentProvider所在进程未启动过,那么new ContentProviderRecord  
                try {  
                    ApplicationInfo ai =  
                        AppGlobals.getPackageManager().  
                            getApplicationInfo(  
                                    cpi.applicationInfo.packageName,  
                                    STOCK_PM_FLAGS, userId);  
                    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);  
                } catch (RemoteException ex) {  
                    // pm is in same process, this will never happen.  
                }  
            }  
  
            if (r != null && cpr.canRunHere(r))  {       //⑤如果ContentProvider允许多进程实例化,那么直接返回,让调用进程自己实例化ContentProvider;  
                // If this is a multiprocess provider, then just return its  
                // info and allow the caller to instantiate it.  Only do  
                // this if the provider is the same user as the caller's  
                // process, or can run as root (so can be in any process).  
                return cpr.newHolder(null);  
            }  
  
  
            // This is single process, and our app is now connecting to it.  
            // See if we are already in the process of launching this  
            // provider.  
            final int N = mLaunchingProviders.size();  
            int i;  
            for (i=0; i<N; i++) {       //查询是否有其他进程正在加载该Provider  
                if (mLaunchingProviders.get(i) == cpr) {  
                    break;  
                }  
            }  
  
            // If the provider is not already being launched, then get it  
            // started.  
            if (i >= N) {      
                final long origId = Binder.clearCallingIdentity();  
  
                try {  
                    // Content provider is now in use, its package can't be stopped.  
                    try {  
                        AppGlobals.getPackageManager().setPackageStoppedState(  
                                cpr.appInfo.packageName, false, userId);  
                    } catch (RemoteException e) {  
                    } catch (IllegalArgumentException e) {  
                        Slog.w(TAG, "Failed trying to unstop package "  
                                + cpr.appInfo.packageName + ": " + e);  
                    }  
  
                    // Use existing process if already started  
                    ProcessRecord proc = getProcessRecordLocked(  
                            cpi.processName, cpr.appInfo.uid, false);  
                    if (proc != null && proc.thread != null) {  
                        if (DEBUG_PROVIDER) {  
                            Slog.d(TAG, "Installing in existing process " + proc);  
                        }  
                        proc.pubProviders.put(cpi.name, cpr);  
                        try {  
                            proc.thread.scheduleInstallProvider(cpi);  
                        } catch (RemoteException e) {  
                        }  
                    } else {       //⑥启动Provider所在的目标进程;  
                        proc = startProcessLocked(cpi.processName,  
                                cpr.appInfo, false, 0, "content provider",  
                                new ComponentName(cpi.applicationInfo.packageName,  
                                        cpi.name), false, false, false);  
                        if (proc == null) {  
                            Slog.w(TAG, "Unable to launch app "  
                                    + cpi.applicationInfo.packageName + "/"  
                                    + cpi.applicationInfo.uid + " for provider "  
                                    + name + ": process is bad");  
                            return null;  
                        }  
                    }  
                    cpr.launchingApp = proc;  
                    mLaunchingProviders.add(cpr);  
                } finally {  
                    Binder.restoreCallingIdentity(origId);  
                }  
            }  
  
            // Make sure the provider is published (the same provider class  
            // may be published under multiple names).  
            if (firstClass) {  
                mProviderMap.putProviderByClass(comp, cpr);  
            }  
  
            mProviderMap.putProviderByName(name, cpr);  
            conn = incProviderCountLocked(r, cpr, token, stable);  
            if (conn != null) {  
                conn.waiting = true;  
            }  
        }  
    }  
  
    // Wait for the provider to be published...  
    synchronized (cpr) {        //⑦等待Provider被Publish,目标进程publish后会调用notifiyAll()接口,此时当前线程便可返回;  
        while (cpr.provider == null) {  
            if (cpr.launchingApp == null) {  
                Slog.w(TAG, "Unable to launch app "  
                        + cpi.applicationInfo.packageName + "/"  
                        + cpi.applicationInfo.uid + " for provider "  
                        + name + ": launching app became null");  
                EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,  
                        UserHandle.getUserId(cpi.applicationInfo.uid),  
                        cpi.applicationInfo.packageName,  
                        cpi.applicationInfo.uid, name);  
                return null;  
            }  
            try {  
                if (DEBUG_MU) {  
                    Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp="  
                            + cpr.launchingApp);  
                }  
                if (conn != null) {  
                    conn.waiting = true;  
                }  
                cpr.wait();  
            } catch (InterruptedException ex) {  
            } finally {  
                if (conn != null) {  
                    conn.waiting = false;  
                }  
            }  
        }  
    }  
    return cpr != null ? cpr.newHolder(conn) : null;  
}  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容