Android-Framework(二十)-ContentProvider 的通信原理

文章内容

本文整理自 corpus 仓库中的以下三集课程:

本文源码基准为 AOSP android-6.0.0_r1。一次 ContentResolver.insert() 看上去是普通的方法调用,开始数据操作前,客户端却要先取得一个 IContentProvider。这个接口可能指向本进程对象,也可能是另一个进程中 ContentProvider 的 Binder 代理。

代理的取得过程由客户端两级缓存、ActivityManagerService(下文简称 AMS)的三条处理分支、应用侧反射安装,以及发布后的等待唤醒共同完成。若 provider 声明了 android:multiprocess="true",对象还可能在调用方进程中创建:调用方使用目标 APK 的 ClassLoader 加载 provider 类。下面沿 getContentResolver()、acquireProvider()、getContentProviderImpl()、installProvider() 和 publishContentProviders() 这条调用链分析对象从哪里来、由谁持有。

一、ContentResolver 与 ContentProvider 之间的 Binder 层

1.1 一次 insert 涉及的三类对象

ContentResolver 是应用侧入口,负责解析 URI、取得 provider、发起调用并管理引用;IContentProvider 是跨进程接口;ContentProvider 承载具体的数据访问实现。前两个对象出现在调用方进程中,ContentProvider 实例则由安装路径决定,通常位于 provider 所属进程,也可能因 multiprocess 配置而位于调用方进程。

insert() 的结构把这几项职责分得很明确。它取得 IContentProvider 后调用 provider.insert(...),并在 finally 中释放引用。远程调用抛出的 RemoteException 在该版本实现中被转换为 null 返回值。

java
// 代码位置 frameworks/base/core/java/android/content/ContentResolver.java 第 1223—1242 行// 声明插入方法并接收目标 URI 与待写入数据。public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {    // 检查 URI 参数不能为空。    Preconditions.checkNotNull(url, "url");    // 根据 URI 获取 IContentProvider。    IContentProvider provider = acquireProvider(url);    // 检查系统是否找到了对应的 provider。    if (provider == null) {        // 找不到 provider 时报告未知 URI。        throw new IllegalArgumentException("Unknown URL " + url);    // 结束空值判断分支。    }    // 进入远程调用与耗时记录范围。    try {        // 记录调用开始时间。        long startTime = SystemClock.uptimeMillis();        // 通过 IContentProvider 执行插入操作。        Uri createdRow = provider.insert(mPackageName, url, values);        // 计算本次调用耗时。        long durationMillis = SystemClock.uptimeMillis() - startTime;        // 按框架规则记录更新事件。        maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);        // 返回新建记录的 URI。        return createdRow;    // 捕获 Binder 远程调用异常。    } catch (RemoteException e) {        // 说明这里不必详细记录该异常。        // Arbitrary and not worth documenting, as Activity        // 说明 ActivityManager 很快会结束这个进程。        // Manager will kill this process shortly anyway.        // 远程调用失败时返回空值。        return null;    // 无论调用结果如何都执行引用释放。    } finally {        // 释放此前取得的 provider 引用。        releaseProvider(provider);    // 结束异常处理结构。    }// 结束 insert 方法。}

同文件第 1415—1424 行的 acquireProvider(Uri) 会校验 scheme 是否为 content,提取 authority,再调用 acquireProvider(mContext, auth)。第 294 行只有抽象方法声明,实际实现因此要到 ContentResolver 的子类中查找。

1.2 ContentProviderHolder 如何跨进程传递

AMS 返回的是 IActivityManager.ContentProviderHolder,不是单独的接口对象。这个 Parcelable 同时携带 ProviderInfo、IContentProvider、连接凭据和 noReleaseNeeded 标记。provider 写入 Parcel 时转换为强 Binder,读取时由 ContentProviderNative.asInterface() 还原为本地接口或代理;connection 则供 AMS 管理调用方持有的引用。

java
// 代码位置 frameworks/base/core/java/android/app/IActivityManager.java 第 526—573 行// 保存 provider 的声明信息。public final ProviderInfo info;// 保存本地接口或跨进程 Binder 代理。public IContentProvider provider;// 保存 AMS 用于引用计数的连接凭据。public IBinder connection;// 标记该引用是否不需要释放。public boolean noReleaseNeeded;// 将 ProviderInfo 写入 Parcel。info.writeToParcel(dest, 0);// 将 provider 接口对应的 Binder 写入 Parcel。dest.writeStrongBinder(provider.asBinder());// 将连接凭据写入 Parcel。dest.writeStrongBinder(connection);// 从 Parcel 读取 Binder,并恢复 IContentProvider 接口。provider = ContentProviderNative.asInterface(source.readStrongBinder());

一次 provider 获取涉及的三个进程与关键对象:调用方应用进程、system_server 中的 AMS、provider 所在进程,以及 Binder 驱动的两个方向

二、客户端的两级查找

2.1 getContentResolver 返回的具体实现

ContextImpl 在第 1863 行创建 ApplicationContentResolver,第 223—225 行的 getContentResolver() 返回该实例。第 1997—2002 行实现了抽象方法 acquireProvider():它解析 authority 中的用户信息,并把请求交给当前进程的 ActivityThread。

java
// 代码位置 frameworks/base/core/java/android/app/ContextImpl.java 第 1997—2002 行// 覆盖 ContentResolver 中的抽象获取方法。@Override// 声明按 authority 获取 provider 的实现。protected IContentProvider acquireProvider(Context context, String auth) {    // 把去掉用户标识的 authority 与解析出的 userId 交给 ActivityThread。    return mMainThread.acquireProvider(context,            // 去除 authority 中携带的用户标识。            ContentProvider.getAuthorityWithoutUserId(auth),            // 解析 authority 对应的用户 ID,并请求稳定引用。            resolveUserIdFromAuthority(auth), true);// 结束 acquireProvider 方法。}

同一内部类还把 acquireExistingProvider()、releaseProvider() 和 acquireUnstableProvider() 分别委托给 ActivityThread。普通 acquireProvider() 请求 stable 引用,unstable 版本把最后一个参数设为 false。

2.2 本地缓存命中时

ActivityThread.acquireProvider() 先调用 acquireExistingProvider()。缓存中已经存在 authority 与 userId 对应的记录时,该方法增加相应引用计数并返回 IContentProvider,不再请求 AMS。这是客户端查找的第一级。

2.3 本地缓存未命中时

缓存没有记录时,客户端通过 ActivityManagerNative.getDefault().getContentProvider(...) 向 system_server 发起请求。AMS 返回 ContentProviderHolder 后,客户端仍会调用 installProvider()。这个方法负责创建并登记本地 provider,或者把远程代理纳入当前进程的缓存与引用计数体系。

java
// 代码位置 frameworks/base/core/java/android/app/ActivityThread.java 第 4763—4792 行// 声明 ActivityThread 的 provider 获取入口。public final IContentProvider acquireProvider(        // 接收 Context、authority、用户 ID 与引用类型。        Context c, String auth, int userId, boolean stable) {    // 查询当前进程已有的 provider 缓存。    final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);    // 判断缓存是否命中。    if (provider != null) {        // 命中后直接返回缓存中的接口。        return provider;    // 结束缓存命中分支。    }     // 说明并发线程可能同时获取同一个 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    // 说明同进程 provider 会形成重入场景。    // be re-entrant in the case where the provider is in the same process.    // 初始化 AMS 返回的 holder。    IActivityManager.ContentProviderHolder holder = null;    // 进入 Binder 调用保护范围。    try {        // 通过 IActivityManager 向 AMS 请求 provider。        holder = ActivityManagerNative.getDefault().getContentProvider(                // 传入应用线程、authority、用户 ID 与引用类型。                getApplicationThread(), auth, userId, stable);    // 捕获远程异常。    } catch (RemoteException ex) {    // 结束异常处理。    }    // 检查 AMS 是否返回了 holder。    if (holder == null) {        // 记录 authority 查找失败。        Slog.e(TAG, "Failed to find provider info for " + auth);        // 无可用 provider 时返回空值。        return null;    // 结束空 holder 分支。    }     // 说明安装动作会增加引用计数。    // 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);    // 返回安装后的 IContentProvider。    return holder.provider;// 结束 acquireProvider 方法。}

insert() 调用结束后,finally 中的 releaseProvider(provider) 释放此前取得的引用,避免调用方无期限持有可释放的 provider 连接。

客户端的两级查找:insert 取得 IContentProvider,本地缓存命中直接返回,未命中经 Binder 请求 AMS

三、AMS 侧的记录与三条出口

3.1 ContentProviderRecord 保存什么

AMS 用 ContentProviderRecord 表示一个 provider 的服务端状态。info 是组件声明,uid 与 appInfo 描述所属应用,name 是组件名,provider 保存发布后的 Binder 接口,proc 指向承载它的进程,launchingApp 则记录正在启动但尚未发布 provider 的进程。

newHolder() 从这份记录构造跨进程返回值。canRunHere() 的判断涉及三项:组件允许 multiprocess,或者组件声明的进程名与调用方进程名相同;以上两项满足一项后,provider 的 uid 还必须与调用方 uid 相同。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ContentProviderRecord.java 第 78—89 行// 根据连接记录构造一个 holder。public ContentProviderHolder newHolder(ContentProviderConnection conn) {    // 用 ProviderInfo 创建返回载体。    ContentProviderHolder holder = new ContentProviderHolder(info);    // 写入当前记录持有的 provider 接口。    holder.provider = provider;    // 复制无需释放标记。    holder.noReleaseNeeded = noReleaseNeeded;    // 写入调用方连接凭据。    holder.connection = conn;    // 返回构造完成的 holder。    return holder;// 结束 newHolder 方法。}// 判断该 provider 能否运行在指定应用进程中。public boolean canRunHere(ProcessRecord app) {    // 检查 multiprocess 或同进程名条件,并要求 uid 相同。    return (info.multiprocess || info.processName.equals(app.processName))            // 完成 uid 相等条件的判断。            && uid == app.info.uid;// 结束 canRunHere 方法。}

3.2 两张查找表与启动队列

getContentProviderImpl() 在同步块中执行。它用 mProviderMap.getProviderByName(name, userId) 按 authority 查找。需要创建记录时,代码还会按组件名查询 byClass 表。byName 服务于外部 URI 查找;byClass 用组件类归并多个 authority,避免重复创建同一个 provider 类。mLaunchingProviders 保存已经发起安装、尚未完成发布的记录,使并发请求可以等待同一轮启动。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9407—9445 行// 声明 AMS 内部的 provider 获取实现。private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,        // 接收 authority、外部 token、引用类型和用户 ID。        String name, IBinder token, boolean stable, int userId) {    // 声明服务端 provider 记录。    ContentProviderRecord cpr;    // 初始化调用方与 provider 的连接记录。    ContentProviderConnection conn = null;    // 初始化解析得到的组件信息。    ProviderInfo cpi = null;     // 在 AMS 锁内读取和修改 provider 状态。    synchronized(this) {        // 记录查找开始时间。        long startTime = SystemClock.elapsedRealtime();         // 初始化调用方进程记录。        ProcessRecord r = null;        // 有应用线程调用方时解析其进程记录。        if (caller != null) {            // 根据应用线程查找进程。            r = getRecordForAppLocked(caller);            // 校验调用方是否属于已登记的应用进程。            if (r == null) {                // 找不到调用方时抛出安全异常。                throw new SecurityException(                        // 拼接调用方对象说明。                        "Unable to find app for caller " + caller                      // 拼接调用进程 ID。                      + " (pid=" + Binder.getCallingPid()                      // 拼接所请求的 provider 名称。                      + ") when getting content provider " + name);            // 结束调用方校验分支。            }        // 结束 caller 非空分支。        }         // 初始化跨用户检查标记。        boolean checkCrossUser = true;         // 说明先查询已经发布的 provider。        // First check if this content provider has been published...        // 按 authority 与 userId 查询记录。        cpr = mProviderMap.getProviderByName(name, userId);        // 说明未命中时还会检查 owner 用户的单例 provider。        // If that didn't work, check if it exists for user 0 and then        // 说明复用前需要验证 singleton 属性。        // verify that it's a singleton provider before using it.        // 当前用户未命中且不是 owner 时进入回退查询。        if (cpr == null && userId != UserHandle.USER_OWNER) {            // 查询 owner 用户名下的记录。            cpr = mProviderMap.getProviderByName(name, UserHandle.USER_OWNER);            // 省略与结论无关的 singleton 校验细节。            ...        // 结束 owner 回退分支。        }

3.3 出口一:provider 已发布

byName 查询到记录时,providerRunning 被初始化为 true。AMS 读取 cpr.info,通过 incProviderCountLocked() 创建连接或增加引用。第 9503 行的 updateOomAdjLocked(cpr.proc) 会结合新引用调整目标进程的 oom_adj。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9451—9480 行// 根据记录是否存在判断 provider 是否已经运行。boolean providerRunning = cpr != null;// 已有记录时处理快速返回路径。if (providerRunning) {    // 读取 provider 的组件信息。    cpi = cpr.info;    // 省略权限与运行状态检查。    ...    // 为调用方增加 stable 或 unstable 引用。    conn = incProviderCountLocked(r, cpr, token, stable);

记录存在不代表承载进程仍然存活。第 9506—9524 行检测到 provider 进程死亡时,代码调用 decProviderCountLocked() 撤销计数,再由 appDiedLocked() 清理状态。providerRunning 随后恢复为 false,获取流程重新进入启动路径。

3.4 出口二:provider 可以在调用方进程运行

canRunHere() 在两处被检查。服务端已有记录时,AMS 用该记录构造 holder,再把 holder.provider 设为 null。尚无运行记录时,代码新建 ContentProviderRecord 后直接返回 cpr.newHolder(null),此时记录里的 provider 原本就是空值。两处分支都只交付 ProviderInfo,由调用方创建实例。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9457—9472 行与第 9619—9625 行// 标记这一段属于 providerRunning 为真的分支。// providerRunning 为 true 时// 检查现有 provider 是否可以运行在调用方进程。if (r != null && cpr.canRunHere(r)) {    // 说明该 provider 已发布或正在发布过程中。    // 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.    // 根据已有记录创建不带连接的 holder。    ContentProviderHolder holder = cpr.newHolder(null);    // 说明这里不会把 provider 对象交给调用方。    // don't give caller the provider object, it needs    // 说明调用方需要自己创建一个。    // to make its own.    // 清空 holder 中已有的 provider 接口。    holder.provider = null;    // 返回只携带组件信息的 holder。    return holder;// 结束已发布记录的 canRunHere 分支。}// 表示中间省略了 provider 解析与记录创建代码。...// 标记这一段属于 providerRunning 为假的分支。// providerRunning 为 false、刚 new 出 ContentProviderRecord 时// 检查新记录能否直接运行在调用方进程。if (r != null && cpr.canRunHere(r)) {    // 说明 multiprocess provider 只返回组件信息。    // If this is a multiprocess provider, then just return its    // 说明调用方负责实例化 provider。    // info and allow the caller to instantiate it.  Only do    // 说明该路径还受用户身份约束。    // this if the provider is the same user as the caller's    // 说明以 root 身份运行时可以不受进程限制。    // process, or can run as root (so can be in any process).    // 返回 provider 字段尚为空的 holder。    return cpr.newHolder(null);// 结束新记录的 canRunHere 分支。}

3.5 出口三:安装或启动目标进程

前两条路径均不适用时,AMS 查询目标进程。进程已存在且 IApplicationThread 可用,AMS 就调用 scheduleInstallProvider();否则调用 startProcessLocked() 启动进程。两种情况下,cpr.launchingApp 都指向目标进程,相应记录也会加入 mLaunchingProviders。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9648—9691 行// 表示省略了包运行状态与已有连接的处理代码。...// 说明优先复用已经启动的进程。// Use existing process if already started// 按进程名和 uid 查找目标进程。ProcessRecord proc = getProcessRecordLocked(        // 传入 provider 进程名、应用 uid 与查找标记。        cpi.processName, cpr.appInfo.uid, false);// 目标进程及其应用线程存在时直接调度安装。if (proc != null && proc.thread != null) {    // 尚未登记该 provider 时添加记录。    if (!proc.pubProviders.containsKey(cpi.name)) {        // 把 provider 记录放入目标进程的发布表。        proc.pubProviders.put(cpi.name, cpr);        // 保护跨进程调度调用。        try {            // 通知目标应用线程安装 provider。            proc.thread.scheduleInstallProvider(cpi);        // 捕获调度过程中的远程异常。        } catch (RemoteException e) {        // 结束异常处理。        }    // 结束未登记分支。    }// 目标进程不存在或应用线程不可用时启动进程。} else {    // 为 content provider 启动目标应用进程。    proc = startProcessLocked(cpi.processName,            // 传入应用信息、启动参数与启动原因。            cpr.appInfo, false, 0, "content provider",            // 传入 provider 的组件名。            new ComponentName(cpi.applicationInfo.packageName,                    // 指定 provider 类名及其余启动标记。                    cpi.name), false, false, false);    // 检查目标进程是否启动成功。    if (proc == null) {        // 记录应用进程无法启动。        Slog.w(TAG, "Unable to launch app " + ... + ": process is bad");        // 进程启动失败时返回空值。        return null;    // 结束进程启动失败分支。    }// 结束进程选择分支。}// 记录正在为该 provider 启动的进程。cpr.launchingApp = proc;// 把 provider 加入等待发布的集合。mLaunchingProviders.add(cpr);

getContentProviderImpl 的三条出口:记录已存在、canRunHere 成立、需要安装或启动目标进程

四、等待与唤醒

4.1 发起查询的 Binder 线程在哪里等待

AMS 把新记录写入 byClass 和 byName 表,增加调用方引用,并将 conn.waiting 标为 true。随后,处理 getContentProvider() 的 AMS Binder 线程进入 synchronized (cpr)。只要 cpr.provider 仍为空,它就调用 cpr.wait()。

调用方此时尚未拿到 getContentProvider() 的返回值,发起请求的线程仍在等待这次 Binder 调用完成。第一次访问的延迟可能来自目标进程启动、provider 实例化和发布。这里的等待没有在所列代码中设置超时;失败路径能否退出,取决于 launchingApp 状态及相关进程清理逻辑。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9696—9744 行// 表示省略了恢复调用身份与耗时记录的代码。...// 说明这里要确保 provider 已经完成发布(同一个 provider 类// Make sure the provider is published (the same provider class// 说明可能以多个名字发布)。// may be published under multiple names).// 第一次遇到该组件类时写入 byClass 表。if (firstClass) {    // 按组件名保存 provider 记录。    mProviderMap.putProviderByClass(comp, cpr);// 结束首次组件类分支。} // 按本次查询的 authority 写入 byName 表。mProviderMap.putProviderByName(name, cpr);// 为调用方建立或增加引用计数。conn = incProviderCountLocked(r, cpr, token, stable);// 存在调用方连接时记录等待状态。if (conn != null) {    // 标记该连接正在等待 provider 发布。    conn.waiting = true;// 结束连接判断。}// 表示中间省略了与等待结论无关的代码。...// 说明此处等待 provider 发布。// Wait for the provider to be published...// 使用 provider 记录本身作为等待锁。synchronized (cpr) {    // provider 尚未发布时持续检查。    while (cpr.provider == null) {        // 启动进程记录丢失时结束等待。        if (cpr.launchingApp == null) {            // 记录启动进程已丢失。            Slog.w(TAG, "Unable to launch app " + ... + ": launching app became null");            // 写入 provider 丢失进程事件。            EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS, ...);            // 返回空值通知调用方获取失败。            return null;        // 结束启动进程检查。        }        // 进入等待操作的异常处理范围。        try {            // 存在连接时保持等待标记。            if (conn != null) {                // 将连接标记为等待中。                conn.waiting = true;            // 结束连接判断。            }            // 释放 cpr 监视器并等待发布方唤醒。            cpr.wait();        // 捕获线程中断。        } catch (InterruptedException ex) {        // 每轮等待结束后恢复连接状态。        } finally {            // 存在连接时清除等待标记。            if (conn != null) {                // 标记该连接已不再等待。                conn.waiting = false;            // 结束连接判断。            }        // 结束等待异常处理。        }    // 结束 provider 空值循环。    }// 结束 cpr 同步块。}// 返回包含连接和 provider 接口的 holder。return cpr != null ? cpr.newHolder(conn) : null;

4.2 目标进程安装并发布 provider

新进程启动后,应用绑定流程进入 handleBindApplication(),installContentProviders() 随之遍历本应用的 provider。若目标进程已经存在,scheduleInstallProvider() 在 ActivityThread 第 1195—1197 行向主线程发送 H.INSTALL_PROVIDER 消息,消息处理同样进入 installProvider()。

批量安装得到的 holder 会通过 publishContentProviders() 发回 AMS,完成 provider 获取过程中的第二次跨进程调用。

java
// 代码位置 frameworks/base/core/java/android/app/ActivityThread.java 第 4734—4761 行// 声明批量安装 provider 的方法。private void installContentProviders(        // 接收应用 Context 与 provider 声明列表。        Context context, List<ProviderInfo> providers) {    // 创建待发布 holder 的结果列表。    final ArrayList<IActivityManager.ContentProviderHolder> results =        // 初始化结果列表。        new ArrayList<IActivityManager.ContentProviderHolder>();     // 遍历当前应用需要安装的 provider。    for (ProviderInfo cpi : providers) {        // 安装 provider 并取得 holder。        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,                // 应用启动安装时不输出 noisy 日志,且引用无需释放并按 stable 处理。                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);        // 安装成功后加入发布列表。        if (cph != null) {            // 标记该 holder 无需释放。            cph.noReleaseNeeded = true;            // 保存待发布的 holder。            results.add(cph);        // 结束安装结果判断。        }    // 结束 provider 遍历。    }     // 保护向 AMS 发起的发布调用。    try {        // 通过 IActivityManager 批量发布 provider。        ActivityManagerNative.getDefault().publishContentProviders(            // 传入当前应用线程和安装结果。            getApplicationThread(), results);    // 捕获发布过程中的远程异常。    } catch (RemoteException ex) {    // 结束异常处理。    }// 结束 installContentProviders 方法。}

4.3 AMS 发布时完成的状态变更

publishContentProviders() 找到目标进程的 pubProviders 记录后,会处理四组状态:按组件类和所有 authority 更新 mProviderMap;从 mLaunchingProviders 移除相应记录;将接口与进程写回 ContentProviderRecord 并调用 notifyAll();更新目标进程的 oom_adj 与 provider 使用统计。

java
// 代码位置 frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 第 9844—9901 行// 声明接收应用侧 provider 发布结果的方法。public final void publishContentProviders(IApplicationThread caller,        // 接收一组已经安装的 holder。        List<ContentProviderHolder> providers) {    // 空列表不需要处理。    if (providers == null) {        // 直接结束本次发布。        return;    // 结束空列表判断。    }     // 禁止隔离进程调用发布接口。    enforceNotIsolatedCaller("publishContentProviders");    // 在 AMS 锁内修改 provider 全局状态。    synchronized (this) {        // 根据应用线程取得发布方进程记录。        final ProcessRecord r = getRecordForAppLocked(caller);        // 省略发布方合法性检查。        ...        // 取得 holder 数量。        final int N = providers.size();        // 逐个处理应用发布的 holder。        for (int i=0; i<N; i++) {            // 读取当前 holder。            ContentProviderHolder src = providers.get(i);            // 跳过缺少必要字段的结果。            if (src == null || src.info == null || src.provider == null) {                // 继续处理下一个 holder。                continue;            // 结束字段校验。            }            // 从发布方进程记录中查找服务端目标记录。            ContentProviderRecord dst = r.pubProviders.get(src.info.name);            // 找到目标记录后写回发布状态。            if (dst != null) {                // 构造 provider 组件名。                ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);                // 更新按组件类索引的 provider 表。                mProviderMap.putProviderByClass(comp, dst);                // 将声明中的多个 authority 拆开。                String names[] = dst.info.authority.split(";");                // 遍历 provider 声明的 authority。                for (int j = 0; j < names.length; j++) {                    // 更新按 authority 索引的 provider 表。                    mProviderMap.putProviderByName(names[j], dst);                // 结束 authority 遍历。                }                 // 取得等待发布集合的当前大小。                int NL = mLaunchingProviders.size();                // 声明遍历下标。                int j;                // 查找所有指向当前 provider 记录的等待项。                for (j=0; j<NL; j++) {                    // 当前项匹配时将其移除。                    if (mLaunchingProviders.get(j) == dst) {                        // 从等待集合删除该记录。                        mLaunchingProviders.remove(j);                        // 回退下标以适配列表收缩。                        j--;                        // 同步更新待遍历长度。                        NL--;                    // 结束等待项匹配判断。                    }                // 结束等待集合遍历。                }                // 锁定 provider 记录以写入接口并唤醒等待者。                synchronized (dst) {                    // 保存应用发布的 IContentProvider。                    dst.provider = src.provider;                    // 保存承载 provider 的进程。                    dst.proc = r;                    // 唤醒等待当前记录发布的线程。                    dst.notifyAll();                // 结束 provider 记录同步块。                }                // 根据新增 provider 引用更新进程 oom_adj。                updateOomAdjLocked(r);                // 更新 provider 使用统计。                maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,                        // 传入 provider 的 authority。                        src.info.authority);            // 结束目标记录判断。            }        // 结束 holder 遍历。        }    // 结束 AMS 同步块。    }// 结束 publishContentProviders 方法。}

dst.notifyAll() 与前面的 cpr.wait() 使用同一个 ContentProviderRecord 监视器。等待线程被唤醒后重新检查 cpr.provider,条件满足才返回 holder,因此即使发生无关唤醒也不会提前返回。

4.4 失败路径

目标进程启动失败时,startProcessLocked() 返回空值,AMS 直接结束获取。等待期间若 cpr.launchingApp 被清空,循环会记录 provider 丢失进程事件并返回空值。若已经发布的 provider 进程中途死亡,已发布分支会撤销引用并处理进程死亡状态,再转入新的解析与启动流程。这几条路径都可能使上层最终得到空 holder 或空 provider。

provider 的等待与唤醒:AMS 在 cpr.wait() 等待,目标进程安装后经 publishContentProviders 写回并唤醒

五、应用进程侧的安装:installProvider

5.1 Context 与 ClassLoader 的选择

installProvider() 先确定实例化组件使用的 Context。provider 与传入 Context 同包时直接复用;与 mInitialApplication 同包时使用初始 Application;其他情况调用 createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE)。第三条分支让当前进程获得目标 APK 的代码加载环境及其 ClassLoader。

java
// 代码位置 frameworks/base/core/java/android/app/ActivityThread.java 第 5117—5153 行// 初始化用于实例化 provider 的 Context。Context c = null;// 读取 provider 所属应用信息。ApplicationInfo ai = info.applicationInfo;// provider 与传入 Context 同包时复用该 Context。if (context.getPackageName().equals(ai.packageName)) {    // 保存调用方传入的 Context。    c = context;// provider 与初始 Application 同包时改用初始 Application。} else if (mInitialApplication != null &&        // 比较初始 Application 与 provider 的包名。        mInitialApplication.getPackageName().equals(ai.packageName)) {    // 保存初始 Application。    c = mInitialApplication;// provider 属于其他包时创建目标包 Context。} else {    // 捕获目标包不存在的异常。    try {        // 创建包含目标 APK 代码的 Context。        c = context.createPackageContext(ai.packageName,                // 请求加载目标包代码。                Context.CONTEXT_INCLUDE_CODE);    // 捕获目标包查找失败。    } catch (PackageManager.NameNotFoundException e) {        // 保留源码对该异常的忽略说明。        // Ignore    // 结束异常处理。    }// 结束 Context 选择分支。}// 表示中间省略了 Context 有效性检查。...// 取得所选 Context 的 ClassLoader。final java.lang.ClassLoader cl = c.getClassLoader();// 加载并实例化 ProviderInfo 指定的类。localProvider = (ContentProvider)cl.    // 根据类名创建 ContentProvider 实例。    loadClass(info.name).newInstance();// 从本地 provider 取得 Binder 接口。provider = localProvider.getIContentProvider();// 表示中间省略了初始化检查。...// 把 Context 与 ProviderInfo 附加到实例。localProvider.attachInfo(c, info);

5.2 反射实例与 Binder 接口

ClassLoader.loadClass(info.name).newInstance() 创建 ContentProvider Java 实例。getIContentProvider() 取得对外通信的 Binder 接口,attachInfo(c, info) 再把运行 Context 和组件配置交给实例。远程调用方不会直接持有这个 Java 对象,而会持有 holder 反序列化后得到的 IContentProvider 代理。

5.3 本地实例与远程代理使用不同缓存

本进程创建的 provider 进入 mLocalProviders 和 mLocalProvidersByName。从其他进程取得的接口则以 Binder 为键进入 mProviderRefCountMap,并由 ProviderRefCount 分别保存 stable 与 unstable 计数。noReleaseNeeded 为 true 时,初始计数使用 1000, 1000;普通 stable 引用从 1, 0 开始,unstable 引用从 0, 1 开始。

java
// 代码位置 frameworks/base/core/java/android/app/ActivityThread.java 第 5184—5221 行// 用 ProviderInfo 创建本地 holder。holder = new IActivityManager.ContentProviderHolder(info);// 把安装得到的接口写入 holder。holder.provider = provider;// 本地安装的 provider 标记为无需释放。holder.noReleaseNeeded = true;// 登记 authority,并构造本地 provider 记录。pr = installProviderAuthoritiesLocked(provider, localProvider, holder);// 按 Binder 保存本地 provider 记录。mLocalProviders.put(jBinder, pr);// 按组件名保存本地 provider 记录。mLocalProvidersByName.put(cname, pr);// 表示中间省略了本地与远程分支的衔接代码。...// 按 Binder 查找远程 provider 的引用计数记录。ProviderRefCount prc = mProviderRefCountMap.get(jBinder);// 表示中间省略了已有计数记录的更新分支。...// 无需释放时用较大的 stable 与 unstable 初始计数。prc = new ProviderRefCount(holder, client, 1000, 1000);   // noReleaseNeeded 时// 普通引用按 stable 参数选择初始计数。prc = stable ? new ProviderRefCount(holder, client, 1, 0)             // unstable 引用从 unstable 计数 1 开始。             : new ProviderRefCount(holder, client, 0, 1);// 保存远程 provider 的引用计数记录。mProviderRefCountMap.put(jBinder, prc);

5.4 安装结果如何返回 AMS

应用启动时,installContentProviders() 批量安装 manifest 中的 provider,再统一调用 publishContentProviders()。按需安装时,AMS 可能向已有进程发送 scheduleInstallProvider(),也可能启动新进程。目标进程完成发布后会填充 AMS 中的 cpr.provider,并唤醒正在等待的获取请求。

installProvider 内部:Context 与 ClassLoader 的选择、反射实例化,以及本地实例缓存与远程引用计数两条缓存

六、android:multiprocess="true" 时的本地创建路径

6.1 AMS 只返回 ProviderInfo

canRunHere() 成立时,AMS 不必启动 provider 声明的目标进程。返回的 holder 携带 ProviderInfo,但 provider 字段为空。ActivityThread.acquireProvider() 仍会把 holder 交给 installProvider();后者发现 provider 为空,便进入本地实例化分支。

6.2 调用方加载目标 APK 的类

provider 不属于调用方包时,installProvider() 调用 createPackageContext(),再从返回的 Context 取得 ClassLoader。ContextImpl.createPackageContext() 位于第 1677 行,它转入第 1684 行的 createPackageContextAsUser(),后者在第 1692 行通过 mMainThread.getPackageInfo(...) 取得目标包信息。最终参与 loadClass(info.name) 的是目标 APK 对应的 ClassLoader。

这种配置允许同一个 provider 类在多个符合条件的调用进程里独立创建实例。各实例的状态属于所在进程,不能将它们当成单一远程进程中的共享对象。

6.3 演示 provider 与 manifest 声明

目标应用中的 TestContentProvider 是一个空实现骨架,已确认的方法体不含业务逻辑。onCreate() 的返回值无法确认,因此不作为本文结论。

java
// 代码位置演示应用 com.example.myapplication.TestContentProvider// 声明目标应用包名。package com.example.myapplication; // 导入 ContentValues。import android.content.ContentValues;// 导入 Cursor。import android.database.Cursor;// 导入 Uri。import android.net.Uri; // 声明用于演示的 ContentProvider。public class TestContentProvider extends ContentProvider {     // 覆盖 provider 创建回调。    @Override    // 声明 onCreate 方法,无法确认的返回值不作为本文依据。    public boolean onCreate() {        // 省略无法确认的具体返回表达式。        ...    // 结束 onCreate 方法。    }     // 标记 query 可以返回空值。    @Nullable    // 覆盖查询接口。    @Override    // 声明查询方法及其参数。    public Cursor query(@NonNull Uri uri, @Nullable String[] projection,            // 接收筛选条件、参数与排序规则。            @Nullable String selection, @Nullable String[] selectionArgs,            // 接收排序规则。            @Nullable String sortOrder) {        // 空实现返回 null。        return null;    // 结束 query 方法。    }     // 覆盖 MIME 类型查询接口。    @Override    // 声明 getType 方法。    public String getType(@NonNull Uri uri) {        // 空实现返回 null。        return null;    // 结束 getType 方法。    }     // 标记 insert 可以返回空值。    @Nullable    // 覆盖插入接口。    @Override    // 声明 insert 方法。    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {        // 空实现返回 null。        return null;    // 结束 insert 方法。    }// 结束 TestContentProvider 类。}

manifest 中能够确认 multiprocess、process 和 name 三个属性,authority 的取值无法确认,因此未写入代码片段。

xml
<!-- 代码位置演示应用 AndroidManifest.xml 的 provider 声明 --><!-- 声明 TestContentProvider 组件。 --><provider    <!-- 允许符合框架条件时在调用方进程创建实例。 -->    android:multiprocess="true"    <!-- 指定组件声明使用的进程名资源。 -->    android:process="@string/app_name"    <!-- 指定 provider 类名。 -->    android:name=".TestContentProvider"    <!-- authorities 属性的取值无法确认,此处省略。 -->    />

6.4 从 provider 推广到普通类加载

同一套 createPackageContext(..., CONTEXT_INCLUDE_CODE) 与目标包 ClassLoader 也能加载已安装 APK 中的普通类。演示代码在 MainActivity.onCreate() 中创建目标包 Context,加载 MathUtils 并实例化;后续通过 getDeclaredMethod() 取出方法,再用 invoke() 调用。参数列表没有纳入本文代码。

java
// 代码位置演示应用 MainActivity.onCreate 与 testClick// 覆盖 Activity 创建回调。@Override// 声明 onCreate 方法。protected void onCreate(Bundle savedInstanceState) {    // 调用父类实现。    super.onCreate(savedInstanceState);    // 设置 Activity 布局。    setContentView(R.layout.activity_main);    // 创建用于绑定服务的 Intent。    Intent intent = new Intent(this, MessageService.class);    // 自动创建并绑定目标服务。    bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);    // 创建广播过滤器。    IntentFilter mFilter = new IntentFilter(MESSAGE_SERVICE_FILTER);    // 注册广播接收器。    registerReceiver(mMusicBr, mFilter);     // 捕获跨包加载期间可能出现的异常。    try {        // 创建包含目标应用代码的 Context。        Context context = createPackageContext("com.example.myapplication",                // 请求把目标 APK 代码加入加载环境。                Context.CONTEXT_INCLUDE_CODE);        // 从目标包 ClassLoader 开始加载类并创建实例。        Object object = context.getClassLoader()                // 加载目标 APK 中的 MathUtils。                .loadClass("com.example.myapplication.MathUtils")                // 调用无参构造方法创建对象。                .newInstance();        // 省略无法确认的后续反射参数细节。        ...    // 捕获反射与包加载异常。    } catch (Exception e) {        // 输出异常堆栈。        e.printStackTrace();    // 结束异常处理。    }// 结束 onCreate 方法。} // 声明界面点击回调。public void testClick(View view) {// 结束 testClick 方法。}

MathUtils 位于另一个 APK 中,它的可辨认实现只有一个加法方法:

java
// 代码位置演示应用 com.example.myapplication.MathUtils// 声明目标应用包名。package com.example.myapplication; // 声明待反射加载的普通类。public class MathUtils {    // 声明接收两个整数的 add 方法。    public int add(int num1, int num2) {        // 返回两个参数的和。        return num1 + num2;    // 结束 add 方法。    }// 结束 MathUtils 类。}

这种能力要求当前环境能够加载目标包代码,同时受平台版本和应用边界约束。修改其他 APK 中的实现还可能带来侵权风险。

multiprocess 为 true 时 provider 在调用方进程本地创建,同一 ClassLoader 也能加载目标 APK 中的普通类

七、小结

ContentResolver.insert() 使用的对象来自 acquireProvider():客户端缓存命中时直接取得已有接口,未命中时通过 Binder 请求 AMS。AMS 持有 ContentProviderRecord,并在已发布、本进程可运行、需要安装或启动三类路径中选择处理方式。

远程 provider 由所属应用进程反射创建,publishContentProviders() 把 IContentProvider 写回 AMS 并唤醒等待者;调用方最终持有反序列化得到的 Binder 代理和对应连接。canRunHere() 成立时,AMS 只交付组件信息,调用方改用目标 APK 的 ClassLoader 在本进程创建实例。对象的创建位置、持有者与等待时机,都由这条获取和发布链共同决定。

7.1 第一次访问为什么可能阻塞

本地缓存未命中且 provider 尚未发布时,调用方等待 AMS 的 Binder 返回,AMS 中处理该请求的线程又在 cpr.wait() 中等待目标进程发布。进程创建、主线程调度或 provider 初始化中的延迟都会延长这次获取。排查偶发 ANR 时,不能只检查 provider.insert() 的业务实现,还需要核对请求是否停在 provider 获取阶段。

7.2 stable 与 unstable 引用

ActivityThread.acquireProvider(..., stable) 通过布尔参数选择引用类型,incProviderRefLocked() 从第 4794 行开始维护客户端计数。AMS 侧也把 stable 交给 incProviderCountLocked()。调用结束后的 releaseProvider() 与取得引用配对;AMS 的 removeContentProvider() 位于第 9779—9803 行,它调用 decProviderCountLocked(),需要时再执行 updateOomAdjLocked()。

stable 与 unstable 并非两套 provider 对象,它们表示调用方连接在目标进程存活与故障处理中的不同引用语义。所列代码可以确认两类计数分别保存;其他恢复策略不在本文范围内。

7.3 provider 进程死亡后的回退

AMS 在已发布分支发现目标进程已经死亡时,会撤销刚增加的 provider 引用,调用 appDiedLocked() 清理进程状态,并把 providerRunning 改为 false。因此,本次获取可能转入解析组件、创建记录与启动进程的路径。若重新启动仍失败,上层会收到空结果,而不会继续使用失效代理。

7.4 没有 IApplicationThread 的外部调用

普通应用入口 getContentProvider() 会校验非空 caller,再调用 getContentProviderImpl(caller, name, null, stable, userId)。第 9762—9771 行的 getContentProviderExternal() 与 getContentProviderExternalUnchecked() 则传入空 caller,并使用外部 token 与 stable 引用。服务端实现已经为这类没有 IApplicationThread 的调用方保留独立入口,分析调用来源时应区分二者。


本文由 AgentWorkFlow 生成

觉得不错?给作者点个赞吧

分类:Android系统

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Android-Framework(二十)-ContentProvider 的通信原理 · Yinchen 的博客