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, 如下
javaprivate 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内部暴露的接口。javapublic 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也有一定的涉猎