Android ContentProvider

本文基于 AOSP android-9.0.0_r2

本文依然从Provider的解析,Provider的安装,以及Provider的调用流程来分析Provider相关代码。

一、Provider的解析

Provider也是Android的四大组件之一,如果开发者要使用它,需继承ContentProvider, 然后自己实现里面的query/update/delete/insert ...等等相关方法

Provider由PMS中的PackageParser的parseProvider 解析

图1 Provider的解析

二、Provider的安装

之前在将system_server进程配置成Android Application进程笔记中已经提到过了安装系统进程的Provider, 而一般的Application的安装流程也基本一样。

2.1 Provider的安装流程

图2 Provider的安装流程

下面来看下generateApplicationProvidersLocked

    private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
        List<ProviderInfo> providers = null;
        try {
            providers = AppGlobals.getPackageManager()
                    .queryContentProviders(app.processName, app.uid,
                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
                                    | MATCH_DEBUG_TRIAGED_MISSING, /*metadastaKey=*/ null)
                    .getList();
        } catch (RemoteException ex) {
        }
        int userId = app.userId;
        if (providers != null) {
            int N = providers.size();
            app.pubProviders.ensureCapacity(N + app.pubProviders.size());
            for (int i=0; i<N; i++) {
                ProviderInfo cpi = (ProviderInfo)providers.get(i);
                boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags);
                if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_SYSTEM) {
                    providers.remove(i);
                    N--;
                    i--;
                    continue;
                }

                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
                ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
                if (cpr == null) {
                    cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
                    mProviderMap.putProviderByClass(comp, cpr);
                }
                app.pubProviders.put(cpi.name, cpr);
                if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
                    app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,mProcessStats);
                }
                notifyPackageUse(cpi.applicationInfo.packageName,PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER);
            }
        }
        return providers;
    }
图3 ContentProviderRecord

generateApplicationProvidersLocked的主要的目的就是生成图3的数据结构,将ProviderInfo从PMS查询出来,生成 ContentProviderRecord 然后保存在 AMS 的ProviderMap中

2.2 Provider的安装

如图2所示,ActivityManagerService 将App的信息,包括app中Provider的信息通过bindApplication函数bind到App所在的进程时发生的,这个时间点基本算的上是应用程序的最早的阶段,它都比Application的onCreate还在前面。更不用说其它三大组件都早。

installContentProvider到底发生了什么呢?

    private void installContentProviders(Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();
        for (ProviderInfo cpi : providers) {
            ContentProviderHolder cph = installProvider(context, null, cpi,false , true , true);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManager.getService().publishContentProviders(getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

从代码上看, installContentProviders遍历所有的Provider, 然后分别安装,最后将这些安装好的Provider publish到AMS中。

installProvider分为两部分,第一部分是实例化ContentProvider, 另外一部分是将ContentProvider加入到各种链表当中去。

  • 实例化ContentProvider
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            ...
            try {
                ...
                localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                localProvider.attachInfo(c, info);
            } 
        } else {
            provider = holder.provider;
        }

instantiateProvider函数通过反射机制生成Provider的实例, 然后通过 attachInfo 将ProviderInfo保存到ContentProvider实例当中, 最后调用 ContentProvider的onCreate

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
                mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
                setAuthorities(info.authority);
            }
            ContentProvider.this.onCreate();
        }
    }
  • 将ContentProvider加入到各种数据结构当中
        ContentProviderHolder retHolder;
        synchronized (mProviderMap) {
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    provider = pr.mProvider;
                } else {
                    holder = new 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 {
              ...
            }
        }
        return retHolder;
    }
图4 Provider的数据结构

2.3 向System publish Provider

Application 将ContentProvider 安装好后,最后通过publishContentProviders往AMS中publish,

    public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
        synchronized (this) {
            final ProcessRecord r = getRecordForAppLocked(caller);
            final long origId = Binder.clearCallingIdentity();
            final int N = providers.size();
            for (int i = 0; i < N; i++) {
                ContentProviderHolder src = providers.get(i);
                ...
               ContentProviderRecord dst = r.pubProviders.get(src.info.name);
                if (dst != null) {
                    ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                    mProviderMap.putProviderByClass(comp, dst);
                    String names[] = dst.info.authority.split(";");
                    for (int j = 0; j < names.length; j++) {
                        mProviderMap.putProviderByName(names[j], dst);
                    }
                    ...
                    synchronized (dst) {
                        dst.provider = src.provider;
                        dst.proc = r;
                        dst.notifyAll();
                    }
                    updateOomAdjLocked(r, true);
                }
            }
        }
    }
图5 publish provider

至此, Application已经将ContentProvider安装好,且已经向AMS publish了Application所提供的 ContentProvider了, 下面介绍下其它APP访问别的APP里的 Provider的流程。

三、Provider的访问

如图4所示,ContentProvider里的Transport间接实现了IContentProvider, IContentPrvoider定义了如下的接口 query/insert/update/delete/getType/call 等Transport最终会将这些接口调用route到自己实现的MyProvider里。

这里以call为例来看下Provider是怎么样跨进程通信的。

Client App通过以下代码去访问图4所示的Provider.

            Uri uri = Uri.parse("content://bar");
            getContentResolver().call(uri, "from_test2", "arg", null);

先来看下getContentResolver

    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

    mContentResolver = new ApplicationContentResolver(this, mainThread);

来看下ContentResolver的call的函数

    public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method,
            @Nullable String arg, @Nullable Bundle extras) {
        IContentProvider provider = acquireProvider(uri);
        try {
            final Bundle res = provider.call(mPackageName, method, arg, extras);
            Bundle.setDefusable(res, true);
            return res;
        } catch (RemoteException e) {
            return null;
        } finally {
            releaseProvider(provider);
        }
    }

call函数会先通过acquireProvider去获得Provider的Proxy代理,然后通过Provider的代理去请求MyProvider的call函数。

图6 call请求流程

如图6中 4所示 acquireProvider会先从本地已经安装好的Provider来查请求的Provider是否已经安装,如果已经安装,直接返回,这样就省去向AMS查询获得

相反,如果本地并没有安装,此时就要通过binder向AMS查询获得。

3.1 AMS返回ContentProvider proxy

    private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
        ...
        synchronized(this) {
            cpr = mProviderMap.getProviderByName(name, userId);
            ...
            //Phase1 Provider是否已经安装好,且可运行了,直接返回
            boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;
            if (providerRunning) {
                cpi = cpr.info;
                if (r != null && cpr.canRunHere(r)) {
                    holder.provider = null;
                    return holder;
                }
            }

            if (!providerRunning) {
                ...
                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
                cpr = mProviderMap.getProviderByClass(comp, userId);
                final boolean firstClass = cpr == null;

                //Phase2  如果Provider还没有运行,说明对应的进程还没有启动,此时将Provider所在的进程
                //先启动起来
                if (i >= N) {
                    try {
                        // Use existing process if already started
                        ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);
                        if (proc != null && proc.thread != null && !proc.killed) {
                            ...
                        } else {
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);
                        }
                        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;
                }
            }
        }

        //如果Provider并没有被publish, 此时要等着Provider publish
        synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    return null;
                }
                try {
                    if (conn != null) {
                        conn.waiting = true;
                    }
                    cpr.wait();
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }
        return cpr != null ? cpr.neowHolder(conn) : null;
    }

getContentProviderImpl做完减法后的代码如上,其实归根结底,如果Provider已经publish, 则直接返回,如果还没有publish, 那就先启动进程,等着provider的publish. 最后将ContentProviderHolder返回给Client App.

图7 Client App获得Provider代理

注意: Client App安装的Provider, 有一个引用计数 ProviderRefCount, 当引用计数为0时,会释放掉已经安装的Provider, 这个引用计数的初始化值取决于installProvider. 这个不细说了。

3.2 call函数调用是在binder 线程中

Transport.java
public Bundle call(
        String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) {
    Bundle.setDefusable(extras, true);
    final String original = setCallingPackage(callingPkg);
    try {
        return ContentProvider.this.call(method, arg, extras);
    } finally {
        setCallingPackage(original);
    }
}

如上代码所示, Transport直接调用ContentProvider的call, 并没有将call route到主线程中去.
同理,query/insert/delete/update .... 都是在binder线程中操作的,而ContentProvider.onCreate在主线程中操作.

四 Provider ANR

4.1 AMS设置的超时

回到 attachApplicationLocked, 当AMS将Provider信息bind给Application时,会设置CONTENT_PROVIDER_PUBLISH_TIMEOUT(10s)的超时。

private final boolean attachApplicationLocked(IApplicationThread thread,
         int pid, int callingUid, long startSeq) {
     ...
     if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
         Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
         msg.obj = app;
         mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
     }
    ...
    thread.bindApplication(...)
    ...
}

当Application在Timeout之前publish成功, 会将Timeout message remove掉。

public final void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) {
   final int N = providers.size();
   for (int i = 0; i < N; i++) {
      ...
      if (wasInLaunchingProviders) {
            mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
      }
      ...
    }
}

如果在Timeout之前还没有publish, 会发生什么呢?

            case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
                ProcessRecord app = (ProcessRecord)msg.obj;
                synchronized (ActivityManagerService.this) {
                    processContentProviderPublishTimedOutLocked(app);
                }
            }
    private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
        cleanupAppInLaunchingProvidersLocked(app, true); // 清除信息
        removeProcessLocked(app, false, true, "timeout publishing content providers"); //kill app,可能会重启
    }

如果超时了,AMS会kill掉已经启动的App, 然后根据需要决定是否重启App.

4.2 ContentProviderClient

通过下面的代码

getContentResolver().acquireContentProviderClient("bar").call();

可以使用到ContentProviderClient, ContentProviderClient是Provider的Wrapper, ContentProviderClient有一个hide的方法来enable ANR

    /** {@hide} */
    public void setDetectNotResponding(long timeoutMillis) {
        synchronized (ContentProviderClient.class) {
            mAnrTimeout = timeoutMillis;

            if (timeoutMillis > 0) {
                if (mAnrRunnable == null) {
                    mAnrRunnable = new NotRespondingRunnable();
                }
                if (sAnrHandler == null) {
                    sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
                }
            } else {
                mAnrRunnable = null;
            }
        }
    }

当调用该函数设置的Timeout时间 >0 时, ANR 机制就启动了

    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
            @Nullable Bundle extras) throws RemoteException {
        beforeRemote();
        try {
            return mContentProvider.call(mPackageName, method, arg, extras);
        } catch (DeadObjectException e) {
            if (!mStable) {
                mContentResolver.unstableProviderDied(mContentProvider);
            }
            throw e;
        } finally {
            afterRemote();
        }
    }

ContentProviderClient在具体的IContentProvider定义的函数之前与之后加入了 beforeRemote/afterRemote

    private void beforeRemote() {
        if (mAnrRunnable != null) {
            sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
        }
    }

    private void afterRemote() {
        if (mAnrRunnable != null) {
            sAnrHandler.removeCallbacks(mAnrRunnable);
        }
    }

这两个函数是Enable ANR超时,如果在设定的TIMEOUT时间内IContentProvider中的方法还没有返回,此时便触发ANR.

    private class NotRespondingRunnable implements Runnable {
        @Override
        public void run() {
            mContentResolver.appNotRespondingViaProvider(mContentProvider);
        }
    }

最终会触发AMS调用appNotRespondingViaProvider

    public void appNotRespondingViaProvider(IBinder connection) {
        final ContentProviderConnection conn = (ContentProviderConnection) connection;
        final ProcessRecord host = conn.provider.proc;
        if (host == null) {
            return;
        }

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mAppErrors.appNotResponding(host, null, null, false, "ContentProvider not responding");
            }
        });
    }

这里特别需要注意的是这个ANR针对的 Provider的提供方,并不是对于 Client 这端。

五 小结

  • Provider的authority在整个Android系统中是唯一的,且支持多个authority, 每个authority由 ";"隔开
  • Provider提供者在Application onCreate之前已经完成初始化
  • Provider的query/insert/call/update/delete ...等等都是运行在binder线程中
  • Provider使用者通过安装Provider的proxy在本地,然后通过本地proxy获得 Provider的相关服务
  • Provider在publish阶段有个10ms的timeout, 如果timeout时间内没有完成,AMS会kill掉该App
  • ContentProviderClient加入了ANR机制 注意,这个ANR机制不是default,需要通过setDetectNotResponding来启用,可以使用AMS对于Provider发出ANR
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 175,490评论 5 419
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 74,060评论 2 335
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 124,407评论 0 291
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 47,741评论 0 248
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 56,543评论 3 329
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 43,040评论 1 246
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 34,107评论 3 358
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 32,646评论 0 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 36,694评论 1 271
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 32,398评论 2 279
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 33,987评论 1 288
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 30,097评论 3 285
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 35,298评论 3 282
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 27,278评论 0 14
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 28,413评论 1 232
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 38,397评论 2 309
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 38,099评论 2 314

推荐阅读更多精彩内容

  • DAY1-6月1日 第一天共读引言部分。 按照读书的惯例,开始读书前先看看封目序尾,应了解这本书整体的框架。 读后...
    三月暖阳2017阅读 245评论 0 1
  • 这一天 一个趋于饱满的节点 来不及将脚下到田间 布谷端坐在麦芒 东南风掳走稻花 身体不接风雨 不接土地 将你触过的...
    野马王阅读 1,178评论 15 44
  • 青春是差一口吃完的苹果 我脑海里的青春跟金钱无关。不知道天高地厚。不知道人情冷暖。 只有一片草原。一抹夕阳。和几个...
    烂人張c阅读 221评论 0 2
  • 妈妈昨天说给她端臭臭,宝宝很乖,拉了臭臭,一会之后又自己蹲下拉尿尿。现在宝宝越来越懂事了!昨晚带宝宝去玩,她看...
    jr812阅读 152评论 0 0
  • 2017年11月2日 生活记录第34篇 40年前的今天,父母把我带到这个美好的世界,给了我一个健康...
    崔槐春阅读 286评论 2 1