Binder通信之ContentProvider

Binder通信之ContentProvider

前言

  • 这是一篇偏实战的文章
  • 深入讨论了ContentProvider如何实现进程通信

大家应该都比较熟悉ContentProvider,通常用于A进程查询B进程内部的数据。其中涉及到进程通信,我们也可以利用这个特点拿到B进程的binder,就变成了更通用的进程通信了。

思路

将binder放在call或者query返回的cursor中,通过cursor.getBinder

  • 通信必须有协议,就像TCP协议一样。这里的协议就是aidl
  • binder是可序列化的,这里就是定义的aidl的示例
  • 在完成后,我们可以用binder去调用aidl定义的任何接口

实现

客户端

协议文件aidl

jsx 复制代码
// IMyAidlInterface.aidl
package com.example.demo.binder;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int getCount();
}
  • 服务端客户端,都需要这样的协议
  • 和服务端协议不一样的是,客户端不用实现这个协议接口。因为我已经拿到了这个解耦的反序列化实例了。

客户端调用

jsx 复制代码
val cursor = contentResolver.query(Uri.parse("content://com.joyzhou.demo"), null, null, null, null)
  try {
      val service = IMyAidlInterface.Stub.asInterface(cursor?.extras?.getBinder(
          "key_binder_count"
      ))
      println("Client: ${service?.count}")
  } catch (e: Exception) {
      e.printStackTrace()
  } finally {
      cursor?.close()
  }
  • 可以看到cursor里带了binder
  • 在cursor的extra带binder会更方便一些

💡 这里大家可以思考下,调用进程获取对端实例(注意不是同一个引用,只是序列化了)的方式除了ContentProvider,还有什么方式?

服务端

协议文件

jsx 复制代码
// IMyAidlInterface.aidl
package com.example.demo.binder;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int getCount();
}

AidlInterfaceImpl

实现协议接口

jsx 复制代码
class AidlInterfaceImpl : IMyAidlInterface.Stub() {
    override fun getCount(): Int {
        return 100
    }
}

Provider片段

jsx 复制代码
override fun query(
    uri: Uri, projection: Array<String>?, selection: String?,
    selectionArgs: Array<String>?, sortOrder: String?
): Cursor {
    val matrixCursor = MatrixCursor(arrayOf("service"))
    matrixCursor.extras = bundleOf(TAG to AidlInterfaceImpl())
    return matrixCursor
}

原理

我们通过上面示例作为引子来看看,query的过程?

java 复制代码
@Override
    public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable Bundle queryArgs,
            @Nullable CancellationSignal cancellationSignal) {
        Objects.requireNonNull(uri, "uri");

    

        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        Cursor qCursor = null;
        try {
            long startTime = SystemClock.uptimeMillis();

            ICancellationSignal remoteCancellationSignal = null;
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
            try {
                qCursor = unstableProvider.query(mPackageName, mAttributionTag, uri, projection,
                        queryArgs, remoteCancellationSignal);
            } catch (DeadObjectException e) {
                // The remote process has died...  but we only hold an unstable
                // reference though, so we might recover!!!  Let's try!!!!
                // This is exciting!!1!!1!!!!1
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                qCursor = stableProvider.query(mPackageName, mAttributionTag, uri, projection,
                        queryArgs, remoteCancellationSignal);
            }
            if (qCursor == null) {
                return null;
            }

            // Force query execution.  Might fail and throw a runtime exception here.
            qCursor.getCount();
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);

            // Wrap the cursor object into CursorWrapperInner object.
            final IContentProvider provider = (stableProvider != null) ? stableProvider
                    : acquireProvider(uri);
            final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
            stableProvider = null;
            qCursor = null;
            return wrapper;
        } catch (RemoteException e) {
            // Arbitrary and not worth documenting, as Activity
            // Manager will kill this process shortly anyway.
            return null;
        } finally {
            if (qCursor != null) {
                qCursor.close();
            }
            if (cancellationSignal != null) {
                cancellationSignal.setRemote(null);
            }
            if (unstableProvider != null) {
                releaseUnstableProvider(unstableProvider);
            }
            if (stableProvider != null) {
                releaseProvider(stableProvider);
            }
        }
    }
  • IContentProvider 是一个binder接口
  • ContentProvider unstableProvider = acquireUnstableProvider(uri); 核心是这一步,即获取IContentProvider ,后续就是常规的调用binder接口了

那下面就来看看contentprovider是如何获取的。我们分几小节首先

  • 注册,
  • 启动
  • query contentprovider

注册contentProvider

contentprovider是四大组件之一,会在androidmanifest里声明,声明一般就代码会在system server里注册。具体是在AMS

启动ContentProvider

这里触发时机是应用启动的时候

ActivityThread → handleBindApplication

jsx 复制代码
app = data.info.makeApplication(data.restrictedBackupMode, null);
if (!data.restrictedBackupMode) {
    if (!ArrayUtils.isEmpty(data.providers)) {
        installContentProviders(app, data.providers);
    }
}
// test thread at this point, and we don't want that racing.
try {
    mInstrumentation.onCreate(data.instrumentationArgs);
}
  • installContentProviders走ContentProvider的生命周期, 可以看到,**contentprovider启动时机在attachbasecontext之后在oncreate之前。**
  • data.providers 是启动的时候从AMS拿到的注册(androidManifest)信息。
jsx 复制代码
// binder服务端会实现stub,说明这个是AMS主动调用的bindApplication。这里不再扩展了
private class ApplicationThread extends IApplicationThread.Stub {
// bindApplication的签名
	public final void bindApplication(String processName, ApplicationInfo appInfo,
                ProviderInfoList providerList, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges)
}

ActivityThread → installContentProviders

jsx 复制代码
@UnsupportedAppUsage
private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<ContentProviderHolder> results = new ArrayList<>();

    for (ProviderInfo cpi : providers) {
        if (DEBUG_PROVIDER) {
            StringBuilder buf = new StringBuilder(128);
            buf.append("Pub ");
            buf.append(cpi.authority);
            buf.append(": ");
            buf.append(cpi.name);
            Log.i(TAG, buf.toString());
        }
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }

    try {
        ActivityManager.getService().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}
  • providers为APP在manifest里声明的所有provider列表,这里都会去install
  • installProvider函数触发了contentprovider的声明周期onCreate
  • cph就是一个provider,info的封装
  • publishContentProviders 主要是将一些信息同步给AMS

ActivityThread → installProviders

java 复制代码
@UnsupportedAppUsage
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) {
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        // ... some checks
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            provider = localProvider.getIContentProvider();
            // oncreate调用在该函数
            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 {
        provider = holder.provider;
        if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                + info.name);
    }

    ContentProviderHolder retHolder;

    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 ContentProviderHolder(info);
                holder.provider = provider;
                holder.noReleaseNeeded = true;
                // 内部做了mProviderMap.put
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            // 非常规情况
        }
    }
    return retHolder;
}
  • installProviderAuthoritiesLocked内部对mProviderMap进行了增删改查, key为 auth + userid, 如下

    java 复制代码
    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
            ContentProvider localProvider, ContentProviderHolder holder) {
    // auth和userid都是从holder里获取的
        final String auths[] = holder.info.authority.split(";");
        final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
    
        if (provider != null) {
            // If this provider is hosted by the core OS and cannot be upgraded,
            // then I guess we're okay doing blocking calls to it.
            for (String auth : auths) {
                switch (auth) {
                    case ContactsContract.AUTHORITY:
                    case CallLog.AUTHORITY:
                    case CallLog.SHADOW_AUTHORITY:
                    case BlockedNumberContract.AUTHORITY:
                    case CalendarContract.AUTHORITY:
                    case Downloads.Impl.AUTHORITY:
                    case "telephony":
                        Binder.allowBlocking(provider.asBinder());
                }
            }
        }
    
        final ProviderClientRecord pcr = new ProviderClientRecord(
                auths, provider, localProvider, holder);
        for (String auth : auths) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord existing = mProviderMap.get(key);
            if (existing != null) {
                Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                        + " already published as " + auth);
            } else {
                mProviderMap.put(key, pcr);
            }
        }
        return pcr;
    }

Query的过程

讨论完了,provider的注册和启动,我们在整体看看query的过程

整体还是A进程请求B进程,比较复杂,其中核心流程如图

  • A进程调用Context的getContentResolver获取ContentResolver对象;
  • 调用ContentResolver的query接口查询数据,
  • ApplicationContentResolver会调用ActivityThread的acquireProvider;
  • A进程请求AMS查询,如果ContentProvider已经running,并且可以运行在调用进程中,则直接返回结果;如果CP需要运行在独立进程,并且进程没有被创建;则去创建进程B
  • 在创建B进程过程中,需要调用AMS中的会调用publicContentProviders发布ContentProvider,接着返回ContentProviderHolder
  • 拿到ContentProviderHolder 后,就是简单的binder函数调用了

ContentResolver → query → ContextImpl$ApplicationContentResolver -> aquireUnstableProvider

jsx 复制代码
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
    return mMainThread.acquireProvider(c,
            ContentProvider.getAuthorityWithoutUserId(auth),
            resolveUserIdFromAuthority(auth), false);
}
  • A进程的mainThread调用acquireProvider

ActivityThread -> acquireProvider

jsx 复制代码
public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
		//A进程通过auth和userid获取provider,在该场景中是找不到provider的,只能通过AMS去找
    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.
    ContentProviderHolder holder = null;
    final ProviderKey key = getGetProviderKey(auth, userId);
    try {
        synchronized (key) {
						// AMS找provider
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            // If the returned holder is non-null but its provider is null and it's not
            // local, we'll need to wait for the publishing of the provider.
            if (holder != null && holder.provider == null && !holder.mLocal) {
                synchronized (key.mLock) {
                    if (key.mHolder != null) {
                        if (DEBUG_PROVIDER) {
                            Slog.i(TAG, "already received provider: " + auth);
                        }
                    } else {
                        key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    }
                    holder = key.mHolder;
                }
                if (holder != null && holder.provider == null) {
                    // probably timed out
                    holder = null;
                }
            }
        }
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    } catch (InterruptedException e) {
        holder = null;
    } finally {
        // Clear the holder from the key since the key itself is never cleared.
        synchronized (key.mLock) {
            key.mHolder = null;
        }
    }
    if (holder == null) {
        if (UserManager.get(c).isUserUnlocked(userId)) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
        } else {
            Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
        }
        return null;
    }

    // Install provider will increment the reference count for us, and break
    // any ties in the race.
		// 这里仍然是A进程在获取到provider后,对providerMap做一些增删查改操作
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}
  • getContentProvider最终会调用到ContentProviderHelper-> getContentProviderImpl

  • holder.provider类型是IContentProvider, 这个类的来源其实是ContentProvider内部暴露的接口。

    java 复制代码
    public IContentProvider getIContentProvider() {
    // mTransport类型是binder。所以在整个流程上看到的IContentProvider的实例都是mTransport
        return mTransport;
    }

ContentProviderHelper (SDK33) -> getContentProviderImpl

java 复制代码
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
        String name, IBinder token, int callingUid, String callingPackage, String callingTag,
boolean stable, int userId) {
    // conn就是binder
    ContentProviderConnection conn = null;
    
    ProcessRecord proc = mService.getProcessRecordLocked(
        cpi.processName, cpr.appInfo.uid);
       IApplicationThread thread;
    if (proc != null && (thread = proc.getThread()) != null
            && !proc.isKilled()) {
        if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
            Slog.d(TAG, "Installing in existing process " + proc);
        }
        final ProcessProviderRecord pr = proc.mProviders;
        if (!pr.hasProvider(cpi.name)) {
            checkTime(startTime, "getContentProviderImpl: scheduling install");
            pr.installProvider(cpi.name, cpr);
            try {
                thread.scheduleInstallProvider(cpi);
            } catch (RemoteException e) {
            }
        }
    } else {
        checkTime(startTime, "getContentProviderImpl: before start process");
        proc = mService.startProcessLocked(
                cpi.processName, cpr.appInfo, false, 0,
                new HostingRecord(HostingRecord.HOSTING_TYPE_CONTENT_PROVIDER,
                    new ComponentName(
                            cpi.applicationInfo.packageName, cpi.name)),
                Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false);
        checkTime(startTime, "getContentProviderImpl: after start process");
        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);
                
    
    // 内部new 一个ContentProviderConnection
     conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage,
                    callingTag, stable, true, startTime, mService.mProcessList,
                    expectedUserId);
    // newHolder 
    return cpr.newHolder(conn, false);
}
  • ContentProviderHolder是可序列化的
  • 如果没有启动进程mService.startProcessLocked 这个函数会启动,启动过程我们已经讨论过, 里面说到publishContentProviders 函数会将一些创建的ContentProviderHolder 的信息更新到AMS,这里的信息会通过cpr.newHolder(conn, false); 返回。
  • cpr.newHolder(conn, false)返回holder到A的 ActivityThread
  • conn再此流程中不是一个重要的binder。作用是用于引用计数,记录client和contentprovider的连接对象个数

到这步,其实就分析完了获取provider的核心流程了。简单总结下

总结

  • 本文讨论了如何通过ContentProvider进行简单的binder通信。
  • 深入讨论了ContentProvider 的binder通信原理,对aidl也有一定的涉猎
相关推荐
正小安31 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
ChinaDragonDreamer1 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui