ContentProvider 总结
时序图:

白色:client,发起provider的进程
红色:systemserver进程
蓝色:server,提供provider的进程
1.流程1-4在client进程(进程A)中,一般来说APP在进程启动之后的初始化时,会installProvider(流程7-10),如果APP请求的provider在当前进程,那么到4就能获取到
2.如果请求的provider在另外一个进程(进程B)中,则会走到流程5-6
3.查询到provider信息之后,如果需要跨进程调用,则通过ContentProviderProxy发起binder call到对方进程执行query。在这中,AMS充当中间管理员,
每个进程在启动之后需要把自己应该install的provider install之后publish给AMS。后面其他进程请求这个provider的话,AMS可以直接告诉你所请求的对方的信息
进程存在但provider未publish,provider进程存在但provide的记录对象cpr ==null,需要向AMS publish:

- Client进程在获取provider的过程,发现cpr为空,则调用scheduleInstallProvider来向provider所在进程发出一个oneway的binder请求,并进入wait()状态.
- provider进程安装完provider信息,则notifyAll()处于等待状态的进程/线程;
4.如果进程B存在且已经publish ,则AMS.getContentProviderImpl,进入installProvider过程,直接返回给进程A 对方的provider信息,不进入到wait()流程
5.进程不存在,需要先创建provider的进程,再publish他的provider:

源码
ActivityThread. installContentProviders
在APP的进程启动的时候,handleBindApplication中会触发installContentProviders:
markdown
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
// 此处的provider信息是在AMS启动进程时
// 从manifest收集到的需要install的provider信息
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());
}
// 执行installProvider,注意此处的stable参数默认为true
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
try {
// install完成之后,要告诉AMS
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
ActivityThread.installProvider的具体实现
markdown
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;//Transport
// holder为null表示还没有install过
if (holder == null || holder.provider == null) {
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
// 创建context
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
...
try {
final java.lang.ClassLoader cl = c.getClassLoader();
// 通过反射创建provider对象
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
// 获取IContentProvider对象,用于跨进程binder call
//返回Transport对象,是ContentProviderNative子类,用来binder通信
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
// provider的attach,其中最重要的是会执行provider的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;
}
// 到这里,provider的对象创建好了,那么接下来需要做的就是数据结构的封装
// 把provider相关信息保存起来
ContentProviderHolder retHolder;
// mProviderMap的key时providerKey,value是ProviderClientReocrd
// 这两个类主要是封装了一些provider的基本信息
synchronized (mProviderMap) {
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
ComponentName cname = new ComponentName(info.packageName, info.name);
// mLocalProvidersByName的key是component信息,value是对应的ProviderClientReocrd
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
if (pr != null) {
// 不为空代表install过
provider = pr.mProvider;
} else {
// 对于新创建的provider,创建其对应的ContentProviderHolder对象
holder = new ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
// install Authorities
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
// mLocalProviders的key是IContentProvider的binder对象,value是ProviderClientRecord
// 将封装好的provider放入map中
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
retHolder = pr.mHolder;
} else {
// mProviderRefCountMap的key是binder对象,value是ProviderRefCount
// ProviderRefCount中记录了这个provider的stable和unstable的数量
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManager.getService().removeContentProvider(
holder.connection, stable);
} catch (RemoteException e) {
}
}
} else {
ProviderClientRecord client = installProviderAuthoritiesLocked(
provider, localProvider, holder);
if (noReleaseNeeded) {
// 创建ProviderRefCount
prc = new ProviderRefCount(holder, client, 1000, 1000);
} else {
// 根据参数中的stable和unstable创建对象
prc = stable
? new ProviderRefCount(holder, client, 1, 0)
: new ProviderRefCount(holder, client, 0, 1);
}
// 放入map
mProviderRefCountMap.put(jBinder, prc);
}
retHolder = prc.holder;
}
}
return retHolder;
}
scala
public abstract class ContentProvider implements ComponentCallbacks2 {
//mTransport在ContentProvider中,表示ContentProvider对象创建时它就创建了
private Transport mTransport = new Transport();
class Transport extends ContentProviderNative {
ContentProvider getContentProvider() {
return ContentProvider.this;
}
}
//获取Transport
public IContentProvider getIContentProvider() {
return mTransport;
}
}
ActivityThread.installProviderAuthoritiesLocked
markdown
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
ContentProvider localProvider, ContentProviderHolder holder) {
final String auths[] = holder.info.authority.split(";");
final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
if (provider != null) {
for (String auth : auths) {
// 对于一些特殊的auth,允许跨进程binder call
// Binder.allowBlocking代表允许执行同步的binder call
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());
}
}
}
// 创建ProviderClientRecord
final ProviderClientRecord pcr = new ProviderClientRecord(
auths, provider, localProvider, holder);
for (String auth : auths) {
// 根据auth和userId创建ProviderKey,放入mProviderMap
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;
}
小结:
- 创建了provider对象,其中也创建了IContentProvider对象(Transport)
- 创建ContentProviderHolder
- 创建ProviderKey(根据auth和userId创建,放入mProviderMap)
- 创建ProviderClientRecord,这是一个provider在client进程中对应的对象,分别放入mProviderMap/mLocalProviders/mLocalProvidersByName
- 创建ProviderRefCount,放入mProviderRefCountMap( mProviderRefCountMap的key是binder对象,value是ProviderRefCount, ProviderRefCount中记录了这个provider的stable和unstable的数量)
stable和unstable:
代表的是client和server的链接,主要取决于获取provider的传参,默认情况下,insert/update/delete建立的链接都是stable,而query则是unstable,不过在query的时候如果失败,还会重新创建stable
stable和unstable最重大的差别在于unstable的情况下,即使对端挂掉了,client也没关系,但是stable的话,如果对端进程挂掉了,client也会被跟着级联kill掉。
一个问题
XX进程 作为securitycenter的client端 ,因为低内存导致securitycenter died ,而被杀掉了。
07-16 08:15:55.683 2523 6648 I XXX.securitycenter/1000(26179): invisible->died(168916ms) R(process died ) adj=900.
07-16 08:15:55.683 2523 6648 I ActivityManager: Process XXX.securitycenter (pid 26179) has died: cch CRE
07-16 08:15:55.688 2523 6648 I ActivityManager: Killing 16348:com.android.camera/u0a84 (adj 900): depends on providerXXX
解决办法:
自己实现ContentProvider的调用,用完后释放。这里采用UnstableContentProviderClient替换ContentResolver,对象判空,我们再决定是否继续调用call()方法。用完将ContentProviderClient close。
markdown
private final boolean removeDyingProviderLocked(ProcessRecord proc,
ContentProviderRecord cpr, boolean always) {
...
for (int i = cpr.connections.size() - 1; i >= 0; i--) {
...
ProcessRecord capp = conn.client;
conn.dead = true;
// 关键就在于conn.stableCount > 0 这个条件
if (conn.stableCount > 0) {
if (!capp.isPersistent() && capp.thread != null
&& capp.pid != 0
&& capp.pid != MY_PID) {
capp.kill("depends on provider "
+ cpr.name.flattenToShortString()
+ " in dying proc " + (proc != null ? proc.processName : "??")
+ " (adj " + (proc != null ? proc.setAdj : "??") + ")",
ApplicationExitInfo.REASON_DEPENDENCY_DIED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
true);
}
...
}
...
}
从方法名来看,removeDyingProviderLocked应该是AMS用来移除将死进程的Provider信息的。并且在移除这些Provider信息的时候会根据一些条件来判断是否要杀死调用方。接下去我们可以分两个方向来分析,一个是removeDyingProviderLocked (ProcessRecord proc, ContentProviderRecord cpr, boolean always)这个方法何时会被调用,另一个则是conn.stableCount在满足怎样的条件时会大于0。
方法 | stableCount | unstableCount | 条件 |
---|---|---|---|
acquireProvider | 1 | 0 | removePending=false |
acquireProvider | 1 | -1 | removePending=true |
acquireUnstableProvider | 0 | 1 | removePending=false |
acquireUnstableProvider | 0 | 0 | removePending=true |
导致XXX闪退的关键就在system_server到provider process的交互过程
AMS首先会调用getContentProviderImp()方法尝试获取target provider。如果ContentProvider还未被注册(即所在进程还未启动),则会调用startProcessLocked()方法来启动server process
server进程在启动时会调用attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq)方法,关键代码如下:
java
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
private boolean attachApplicationLocked(@NonNull 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,
ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);
}
// ...
}
server进程会判断当前AndroidManifest.xml文件中是否存在需要注册的ContentProvider,如果存在就给Handler发送一个延时消息。这个消息的处理逻辑如下:
java
case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
ProcessRecord app = (ProcessRecord)msg.obj;
synchronized (ActivityManagerService.this) {
processContentProviderPublishTimedOutLocked(app);
}
} break;
//
private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
cleanupAppInLaunchingProvidersLocked(app, true);
mProcessList.removeProcessLocked(app, false, true,
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
ApplicationExitInfo.SUBREASON_UNKNOWN,
"timeout publishing content providers");
}
//
final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
...
// Remove published content providers.
for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
if (cpr.proc != app) {
// If the hosting process record isn't really us, bail out
continue;
}
final boolean alwaysRemove = app.bad || !allowRestart;
final boolean inLaunching = removeDyingProviderLocked(app, cpr, alwaysRemove);
...
}
...
}
在全局范围内搜索CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG的时候会发现,还有removeMessage的方法。总共有两个调用地方
- 重启server进程
arduino
final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
...
if (restart && allowRestart && !app.isolated) {
// We have components that still need to be running in the
// process, so re-launch it.
if (index < 0) {
ProcessList.remove(app.pid);
}
// Remove provider publish timeout because we will start a new timeout when the
// restarted process is attaching (if the process contains launching providers).
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);
mProcessList.addProcessNameLocked(app);
app.pendingStart = false;
mProcessList.startProcessLocked(app,
new HostingRecord("restart", app.processName),
ZYGOTE_POLICY_FLAG_EMPTY);
return true;
}
...
}
- ContentProvider注册成功
markdown
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
...
if (wasInLaunchingProviders) {
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
}
...
}
system_server在启动进程时如果目标进程有需要注册的ContentProvider,就会发送一个10s的超时信息;如果目标进程的ContentProvider在十秒内加载完成,system_server就会移除这个超时信息;如果没有注册完成,system_server就会处理这个信息,最终就会调用到removeDyingProviderLocked()方法。
根据ContentResolver中各个方法的实现逻辑,大致列出以下几个有可能导致调用方进程闪退的方法。包括:acquireProvider()、getStreamTypes()、canonicalize()、uncanonicalize()、refresh()、insert()、bulkInsert()、delete()、update()、call()、acquireContentProviderClient()
ContentProviderClient VS ContentResolver
在Android官方文档中提到:如需访问内容提供程序中的数据,您可以客户端的形式使用应用的 Context
中的 ContentResolver
对象与提供程序进行通信。
实际上还可以通过getContentResolver().acquireContentProviderClient(authority)获取
ContentProviderClient对象进行通信。
那两者有何区别呢?
Android通过content://uri区分数据库内容。
ContentResolver通过URI找到对应的ContentProvider,从而实现数据库存取。
1.调用ContentResolver.query() ContentResolver.update()等方法时,uri作为其一部分参数被解析。ContentResolver通过该内容标识找到对应的ContentProvider。每一次的查询对应ContentProvider的操作都是非常耗费资源的操作,因为每次的URI都可能是不同的,对应的内容标识也可能是不同的。另外,连接和断开连接对应的ContentProvider的操作也是非常耗费资源的。
2.调用acquireContentProviderClient(authority)时,查询对应的ContentProvider动作已经完成了,同时获得了一个ContentProviderClient对象。这个对象与其对应的ContentProvider建立了连接。因此,当使用ContentProviderClient时,会直接与对应的ContentProvider通信,大大减少了查询对应ContentProvider带来的消耗。
(warning: acquireContentProviderClient方法的ContentProvider对象必须在使用结束后调用ContentProviderClient.release()释放)
小结:
不同ContentProvider的多次调用:使用ContentResolver;
相同ContentProvider的多次调用:使用ContentProviderClient,记得释放。