简介

本文原文在我的博客CheapTalks,欢迎大家去看看~

ContentProvider相信大家都耳熟能详了,在安卓系统越来越注重系统安全的前提下,不知道大家好不好奇provider是如何在Android层层安全措施之下使进程之间实现数据增删改查操作的。仔细想来,我们大概能猜到,安卓进程间通信惯用的"伎俩"是binder call,ContentProvider的实现很有可能就使用AMS当作provider的客户端与服务端的中间人。本篇文章,我将详细分析客户端app是如何请求到在其他应用进程运行的ContentProvider的,文章最后会附带着这期间AMS和APP两端涉及到的数据结构。

由于篇幅有限,ContentProvider涉及到的其它知识我没有细讲。例如,数据的query很有可能涉及到大量的数据,而安卓的binder同步缓冲区也才1016K这么大,所以provider的查找操作实际上是混合了ashmem与binder这两种跨进程通信技术,其中binder的主要作用是传送ashmem的fd;又如,应用开发常常使用到的ContentObserver,这块的分析可以在我以前写的ContentService的分析找到。

从CRUD之query说起

一般我们写APP时,是通过ContentResolver来进行CRUD调用的。而这个操作一般是通过context.getContentResolver().xxx这样的调用链过来的。Context这块是个装饰者模式,真实的执行者是ContextImp, 这块我不多言了,我们从ContextImpl这块的源码开始分析。

ContextImpl.java

private final ApplicationContentResolver mContentResolver;@Override
public ContentResolver getContentResolver() {return mContentResolver;
}private ContextImpl(ContextImpl container, ActivityThread mainThread,LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,Display display, Configuration overrideConfiguration, int createDisplayWithId) {...mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}复制代码

可以看到,最终ApplicationContentResolver负责了我们的APP ContentProvider CRUD这块的操作。

ContentResolver.java

public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,@Nullable String selection, @Nullable String[] selectionArgs,@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
...// 获取一个unstableProvider,这个stable与unstable的概念在于AMS中会维护unstable, stable这两个计数// 如果当stable的计数大于0,当ContentProvider服务端进程死亡时,那么使用这个服务的客户端进程也会受牵连被杀死// 这里query先尝试使用unstableProvider,表示AMS那边只会增加unstable的计数,客户端不会收到联级诛杀的牵连IContentProvider unstableProvider = acquireUnstableProvider(uri);if (unstableProvider == null) {return null;}IContentProvider stableProvider = null;Cursor qCursor = null;try {long startTime = SystemClock.uptimeMillis();
...try {// binder call到服务端,返回了一个cursor对象// 当调用cursor.close时,会调用调releaseProvider来释放ContentProvider服务端与客户端之间的引用qCursor = unstableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, 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// 第一次使用unstable尝试,服务端进程可能死亡了抛了异常// 先释放unstableProvider相关的引用unstableProviderDied(unstableProvider);// 第二次进行尝试时,将使用stableProviderstableProvider = acquireProvider(uri);if (stableProvider == null) {return null;}// 注意,失败一次后将使用stableProvider,这次如果服务端进程被杀// 并且cursor还没有调用close之前,那么客户端的进程会受到牵连也被杀死qCursor = stableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, remoteCancellationSignal);}if (qCursor == null) {return null;}...// 装饰一下CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,stableProvider != null ? stableProvider : acquireProvider(uri));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 {// 如果不为空才close// 上面的操作如果成功了,qCursor是不会是空的,所以这个关闭操作交给了APP端来做if (qCursor != null) {qCursor.close();}
...if (unstableProvider != null) {releaseUnstableProvider(unstableProvider);}if (stableProvider != null) {releaseProvider(stableProvider);}}
}复制代码

有一点需要提一下,当binder call时抛出DeadObjectException时,不一定是对端进程死亡了,有可能是对端binder缓冲区被占满之类的异常;这块之前问过Google的工程师,他们貌似有细化这个DeadObjectException的计划,不过现在来看DeadObjectException的抛出并不代表者对端进程就死亡了。

代码注释很详细,大体流程可以看看图。

查看大图

核心操作acquireProvider

客户端操作

在ContentResolver中调用acquireStableProvider和acquireUnstableProvider之后都会调用到ApplicationContextResolver这个子类中,之后再调用ActivityThread的acquireProvider的方法,区别stable与unstable的Provider在于这个stable字段,若是true则是获取stableProvider,反之是获取unstableProvider。咱们看代码:

ApplicationContentResolver.java

private static final class ApplicationContentResolver extends ContentResolver {private final ActivityThread mMainThread;private final UserHandle mUser;// stableProvider@Overrideprotected IContentProvider acquireProvider(Context context, String auth) {return mMainThread.acquireProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);}// unstableProvider@Overrideprotected IContentProvider acquireUnstableProvider(Context c, String auth) {return mMainThread.acquireProvider(c,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), false);}
...复制代码

ApplicationContentResolver其实是ActivityThread的内部类,这里为了方便看,这两块的代码还是分开分析吧

ActivityThread.java

public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) {// 现在本地寻找provider,如果没有的话才像AMS去请求final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);if (provider != null) {return provider;}...IActivityManager.ContentProviderHolder holder = null;try {// 向AMS请求ContentProvider,这块是核心方法holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {Slog.e(TAG, "Failed to find provider info for " + auth);return null;}// "安装"provider,说白了就是新建实例,增减引用这类操作// 这块的代码放到后面的scheduleInstallProvider再分析holder = installProvider(c, holder, holder.info,true /*noisy*/, holder.noReleaseNeeded, stable);return holder.provider;
}复制代码

AMS.getContentProviderImpl (1)

ActivityThread binder call 到AMS之后,紧接着就会调用getContentProviderImpl,这个方法比较大,分拆进行分析

ActivityManagerService.java

private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,String name, IBinder token, boolean stable, int userId) {ContentProviderRecord cpr;ContentProviderConnection conn = null;ProviderInfo cpi = null;synchronized (this) {long startTime = SystemClock.elapsedRealtime();// 获取调用者的ProcessRecord对象ProcessRecord r = null;if (caller != null) {r = getRecordForAppLocked(caller);
...}boolean checkCrossUser = true;
...// 通过uri authority name来获取ContentProviderRecordcpr = mProviderMap.getProviderByName(name, userId);
...if (cpr == null && userId != UserHandle.USER_OWNER) {// 检查userId=0是否已存有ContentProviderRecordcpr = mProviderMap.getProviderByName(name, UserHandle.USER_OWNER);if (cpr != null) {cpi = cpr.info;if (isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {userId = UserHandle.USER_OWNER;checkCrossUser = false;} else {cpr = null;cpi = null;}}}复制代码

这一段AMS会在providerMap中寻找正在运行的provider。如果找到,那么说明这个provider已经被启动了,随后增加引用即可;如果没有找到,那么就需要调用installProvider在provider客户端中进行provider的"安装",随后AMS将等待这个客户端publishProvider。

AMS.getContentProviderImpl (2)

// 根据先前的查询,判断当前的provider是否正在运行
boolean providerRunning = cpr != null;
if (providerRunning) {cpi = cpr.info;String msg;
...// 如果provider能够在客户端进行直接运行,那么在这里就返回provider给客户端if (r != null && cpr.canRunHere(r)) {ContentProviderHolder holder = cpr.newHolder(null);holder.provider = null;return holder;}final long origId = Binder.clearCallingIdentity();
...// 增加引用conn = incProviderCountLocked(r, cpr, token, stable);// 如何stable和unstable的总引用计数为1,那么更新LruProcess列表if (conn != null && (conn.stableCount + conn.unstableCount) == 1) {if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
...updateLruProcessLocked(cpr.proc, false, null);checkTime(startTime, "getContentProviderImpl: after updateLruProcess");}}if (cpr.proc != null) {
...// 更新provider进程的adjboolean success = updateOomAdjLocked(cpr.proc);maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
...if (!success) {
...// 如果不成功,那么减少引用计数并杀死provider进程boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);checkTime(startTime, "getContentProviderImpl: before appDied");appDiedLocked(cpr.proc);checkTime(startTime, "getContentProviderImpl: after appDied");if (!lastRef) {// This wasn't the last ref our process had on// the provider...  we have now been killed, bail.return null;}providerRunning = false;conn = null;}}Binder.restoreCallingIdentity(origId);
}复制代码

AMS.getContentProviderImpl (3)

    boolean singleton;// 如果provider没有正在运行if (!providerRunning) {try {checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");// 获取ProviderInfocpi = AppGlobals.getPackageManager().resolveContentProvider(name,STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");} catch (RemoteException ex) {}// 如果为空,则说明没有找到这个provider,直接返回空给客户端if (cpi == null) {return null;}// Provider是否为单例singleton = isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);if (singleton) {userId = UserHandle.USER_OWNER;}cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
...// 通过ComponentName获取providerMap中的cprComponentName comp = new ComponentName(cpi.packageName, cpi.name);cpr = mProviderMap.getProviderByClass(comp, userId);
...// 该provider是否是第一次被创建final boolean firstClass = cpr == null;if (firstClass) {final long ident = Binder.clearCallingIdentity();try {ApplicationInfo ai =AppGlobals.getPackageManager().getApplicationInfo(cpi.applicationInfo.packageName,STOCK_PM_FLAGS, userId);
...ai = getAppInfoForUser(ai, userId);// 创建一个cprcpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);} catch (RemoteException ex) {// pm is in same process, this will never happen.} finally {Binder.restoreCallingIdentity(ident);}}
...if (r != null && cpr.canRunHere(r)) {return cpr.newHolder(null);}
...final int N = mLaunchingProviders.size();int i;for (i = 0; i < N; i++) {if (mLaunchingProviders.get(i) == cpr) {break;}}// provider还没有被运行if (i >= N) {final long origId = Binder.clearCallingIdentity();try {// Content provider is now in use, its package can't be stopped.try {checkTime(startTime, "getContentProviderImpl: before set stopped state");AppGlobals.getPackageManager().setPackageStoppedState(cpr.appInfo.packageName, false, userId);checkTime(startTime, "getContentProviderImpl: after set stopped state");} catch (RemoteException e) {} catch (IllegalArgumentException e) {Slog.w(TAG, "Failed trying to unstop package "+ cpr.appInfo.packageName + ": " + e);}// 获取到运行provider的进程ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false);// 如果这个进程已经启动,那么binder call给这个进程,创建providerif (proc != null && proc.thread != null) {if (!proc.pubProviders.containsKey(cpi.name)) {checkTime(startTime, "getContentProviderImpl: scheduling install");proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} else {// 如果进程没有启动,那么就启动这个进程// 需要说的一点是,进程启动完毕后,创建provider的操作将会在ActivityThread初始化时进行proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name), false, false, false);// 进程没有创建成功,直接返回空给客户端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);} finally {Binder.restoreCallingIdentity(origId);}}...// 如果第一次创建这个provider的实例,在providerMap中进行缓存if (firstClass) {mProviderMap.putProviderByClass(comp, cpr);}mProviderMap.putProviderByName(name, cpr);// 增加引用conn = incProviderCountLocked(r, cpr, token, stable);if (conn != null) {conn.waiting = true;}}
}复制代码

AMS.getContentProviderImpl (4)

此时getContentProviderImpl的分析已经接近尾声。我们看到,如果provider此时尚未在其进程中被创建,那么AMS将会对这个provider进行实例化,也就是"publish"发布。AMS会在这里等待APP进程的完成,随后才会返回。

synchronized (cpr) {while (cpr.provider == null) {if (cpr.launchingApp == null) {
...return null;}try {
...if (conn != null) {conn.waiting = true;}// 等待provider"发布"完成cpr.wait();} catch (InterruptedException ex) {} finally {if (conn != null) {conn.waiting = false;}}}
}
return cpr != null ? cpr.newHolder(conn) : null;复制代码

查看大图

scheduleInstallProvider

应用provider的实例化有两个入口,一个是当进程已经存在时,AMS binder call到APP实例化某个provider;一个是当进程与Application互相绑定时,批量对provider进行安装。

ActivityThread.java

private void installContentProviders(Context context, List<ProviderInfo> providers) {final ArrayList<IActivityManager.ContentProviderHolder> results =new ArrayList<IActivityManager.ContentProviderHolder>();for (ProviderInfo cpi : providers) {
...// 对provider进行实例化IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);if (cph != null) {cph.noReleaseNeeded = true;results.add(cph);}}try {// 完成实例化,通知AMS,进行provider"发布"ActivityManagerNative.getDefault().publishContentProviders(getApplicationThread(), results);} catch (RemoteException ex) {}
}复制代码

installProvider (1)

如果provider尚未实例化,则需要在这个宿主进程中进行"安装"

private IActivityManager.ContentProviderHolder installProvider(Context context,IActivityManager.ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {ContentProvider localProvider = null;IContentProvider provider;// 如果向AMS.getContentProviderImpl返回NULL或者需要app"安装"provider,就会对provider在本地尝试实例化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 {c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);} catch (PackageManager.NameNotFoundException e) {// Ignore}}
...try {// 利用反射进行provider实例化final java.lang.ClassLoader cl = c.getClassLoader();localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();provider = localProvider.getIContentProvider();
...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);}复制代码

installProvider (2)

进行引用方面的操纵;在宿主进程进行provider的缓存

IActivityManager.ContentProviderHolder retHolder;synchronized (mProviderMap) {// 获取provider代理IBinder jBinder = provider.asBinder();// 如果这个provider在上一个操作刚被创建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 IActivityManager.ContentProviderHolder(info);holder.provider = provider;holder.noReleaseNeeded = true;// 根据Uri authority name进行分类缓存pr = installProviderAuthoritiesLocked(provider, localProvider, holder);mLocalProviders.put(jBinder, pr);mLocalProvidersByName.put(cname, pr);}retHolder = pr.mHolder;} else {ProviderRefCount prc = mProviderRefCountMap.get(jBinder);if (prc != null) {if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, updating ref count");}
...if (!noReleaseNeeded) {incProviderRefLocked(prc, stable);try {ActivityManagerNative.getDefault().removeContentProvider(holder.connection, stable);} catch (RemoteException e) {//do nothing content provider object is dead any way}}} else {// 初始化引用ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);if (noReleaseNeeded) {prc = new ProviderRefCount(holder, client, 1000, 1000);} else {prc = stable? new ProviderRefCount(holder, client, 1, 0): new ProviderRefCount(holder, client, 0, 1);}mProviderRefCountMap.put(jBinder, prc);}retHolder = prc.holder;}
}return retHolder;复制代码

AMS.publishContentProviders

在provider宿主进程进行实例化成功之后,就需要通知AMS,告诉它不需要再等待了。此后,访问provider的应用进程的getContnetProviderImpl才真正的结束

public final void publishContentProviders(IApplicationThread caller,List<ContentProviderHolder> providers) {
...enforceNotIsolatedCaller("publishContentProviders");synchronized (this) {// 获取provider宿主进程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);if (src == null || src.info == null || src.provider == null) {continue;}ContentProviderRecord dst = r.pubProviders.get(src.info.name);if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);if (dst != null) {ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);// 根据类进行缓存mProviderMap.putProviderByClass(comp, dst);String names[] = dst.info.authority.split(";");// 根据uri authority name进行缓存for (int j = 0; j < names.length; j++) {mProviderMap.putProviderByName(names[j], dst);}int launchingCount = mLaunchingProviders.size();int j;boolean wasInLaunchingProviders = false;for (j = 0; j < launchingCount; j++) {if (mLaunchingProviders.get(j) == dst) {mLaunchingProviders.remove(j);wasInLaunchingProviders = true;j--;launchingCount--;}}// 这里移除provider ANR的"定时炸弹"if (wasInLaunchingProviders) {mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);}synchronized (dst) {dst.provider = src.provider;dst.proc = r;// 通知AMS provider已经"发布"成功dst.notifyAll();}updateOomAdjLocked(r);maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,src.info.authority);}}Binder.restoreCallingIdentity(origId);}
}复制代码

查看大图

ContentProvider隐藏陷阱之联级诛杀

因为ContentProvider的设计,当provider的宿主进程死亡时,访问它的进程如果正在做CRUD,那么这个进程也会受到牵连。

进程被杀后

以下从ProcessRecord.kill为入口点进行分析

ProcessRecord.java

void kill(String reason, boolean noisy) {if (!killedByAm) {
...// 杀死进程Process.killProcessQuiet(pid);Process.killProcessGroup(uid, pid);if (!persistent) {killed = true;killedByAm = true;}
...}
}复制代码

当进程死亡后,将会调用当初在attachApplication时注册的死亡回调

ActivityManagerService.java

private final class AppDeathRecipient implements IBinder.DeathRecipient {final ProcessRecord mApp;final int mPid;final IApplicationThread mAppThread;AppDeathRecipient(ProcessRecord app, int pid,IApplicationThread thread) {if (DEBUG_ALL) Slog.v(TAG, "New death recipient " + this+ " for thread " + thread.asBinder());mApp = app;mPid = pid;mAppThread = thread;}@Overridepublic void binderDied() {// 进程死亡后,将会调用到里面synchronized (ActivityManagerService.this) {appDiedLocked(mApp, mPid, mAppThread, true);}}
}final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,boolean fromBinderDied) {
...handleAppDiedLocked(app, false, true);...
}private final void handleAppDiedLocked(ProcessRecord app,boolean restarting, boolean allowRestart) {int pid = app.pid;boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
...
}private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,boolean restarting, boolean allowRestart, int index) {
...// 删除和该进程有关的已"发布"的providerfor (int i = app.pubProviders.size() - 1; i >= 0; i--) {ContentProviderRecord cpr = app.pubProviders.valueAt(i);final boolean always = app.bad || !allowRestart;boolean inLaunching = removeDyingProviderLocked(app, cpr, always);if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
...restart = true;}cpr.provider = null;cpr.proc = null;}app.pubProviders.clear();复制代码

进行provider级联诛杀

private final boolean removeDyingProviderLocked(ProcessRecord proc,ContentProviderRecord cpr, boolean always) {final boolean inLaunching = mLaunchingProviders.contains(cpr);// 如果这个provider尚正在运行if (!inLaunching || always) {synchronized (cpr) {cpr.launchingApp = null;cpr.notifyAll();}mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid));String names[] = cpr.info.authority.split(";");for (int j = 0; j < names.length; j++) {mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid));}}// 遍历这个进程所有的Connectionfor (int i = cpr.connections.size() - 1; i >= 0; i--) {ContentProviderConnection conn = cpr.connections.get(i);if (conn.waiting) {// If this connection is waiting for the provider, then we don't// need to mess with its process unless we are always removing// or for some reason the provider is not currently launching.if (inLaunching && !always) {continue;}}// 获取到这个provider的客户端ProcessRecord capp = conn.client;conn.dead = true;// 这个connection的stable计数大于0才会杀死其客户端if (conn.stableCount > 0) {// 如果这个app不是常驻进程且正在运行中,那么将会进行联级诛杀if (!capp.persistent && 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 : "??"), true);}} else if (capp.thread != null && conn.provider.provider != null) {try {capp.thread.unstableProviderDied(conn.provider.provider.asBinder());} catch (RemoteException e) {}// In the protocol here, we don't expect the client to correctly// clean up this connection, we'll just remove it.cpr.connections.remove(i);if (conn.client.conProviders.remove(conn)) {stopAssociationLocked(capp.uid, capp.processName, cpr.uid, cpr.name);}}}if (inLaunching && always) {mLaunchingProviders.remove(cpr);}return inLaunching;
}复制代码

provider中,query操作首次是使用unstableProvider,失败一次后会使用stableProvider;其余insert, update, delete操作直接使用的是stableProvider

联级存在的意义在于保护provider客户端与服务端的数据一致性;因为插入,删除这些操作会涉及到数据更新,所以如果provider出现了异常,为了保证客户端维护的数据是正确了,只能强迫客户端进程直接死亡再重新启动恢复数据

查看大图

releaseProvider

前面我们已经分析,acquireProvider会增加stable的引用计数,而provider服务端死亡时,如果stable计数大于0,那么provider客户端也会收到波及被杀死。那什么时候会stable的计数会减少呢,答案在releaseProvider这个方法中

触发时机

ContentResolver.java

public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,@Nullable String selection, @Nullable String[] selectionArgs,@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {Preconditions.checkNotNull(uri, "uri");IContentProvider unstableProvider = acquireUnstableProvider(uri);
...try {
...try {qCursor = unstableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, remoteCancellationSignal);} catch (DeadObjectException e) {
...unstableProviderDied(unstableProvider);stableProvider = acquireProvider(uri);if (stableProvider == null) {return null;}qCursor = stableProvider.query(mPackageName, uri, projection,selection, selectionArgs, sortOrder, remoteCancellationSignal);}if (qCursor == null) {return null;}
...// 返回cursor,待客户端调用close后才会调用到releaseProviderreturn wrapper;} catch (RemoteException e) {return null;} finally {
...}
}public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {Preconditions.checkNotNull(url, "url");IContentProvider provider = acquireProvider(url);if (provider == null) {throw new IllegalArgumentException("Unknown URL " + url);}try {long startTime = SystemClock.uptimeMillis();Uri createdRow = provider.insert(mPackageName, url, values);long durationMillis = SystemClock.uptimeMillis() - startTime;maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);return createdRow;} catch (RemoteException e) {return null;} finally {// 直接在最后调用releaseProviderreleaseProvider(provider);}
}public final int update(@NonNull Uri uri, @Nullable ContentValues values,@Nullable String where, @Nullable String[] selectionArgs) {Preconditions.checkNotNull(uri, "uri");IContentProvider provider = acquireProvider(uri);if (provider == null) {throw new IllegalArgumentException("Unknown URI " + uri);}try {long startTime = SystemClock.uptimeMillis();int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);long durationMillis = SystemClock.uptimeMillis() - startTime;maybeLogUpdateToEventLog(durationMillis, uri, "update", where);return rowsUpdated;} catch (RemoteException e) {return -1;} finally {releaseProvider(provider);}
}public final int delete(@NonNull Uri url, @Nullable String where,@Nullable String[] selectionArgs) {Preconditions.checkNotNull(url, "url");IContentProvider provider = acquireProvider(url);if (provider == null) {throw new IllegalArgumentException("Unknown URL " + url);}try {long startTime = SystemClock.uptimeMillis();int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);long durationMillis = SystemClock.uptimeMillis() - startTime;maybeLogUpdateToEventLog(durationMillis, url, "delete", where);return rowsDeleted;} catch (RemoteException e) {return -1;} finally {releaseProvider(provider);}
}public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method,@Nullable String arg, @Nullable Bundle extras) {Preconditions.checkNotNull(uri, "uri");Preconditions.checkNotNull(method, "method");IContentProvider provider = acquireProvider(uri);if (provider == null) {throw new IllegalArgumentException("Unknown URI " + uri);}try {return provider.call(mPackageName, method, arg, extras);} catch (RemoteException e) {// Arbitrary and not worth documenting, as Activity// Manager will kill this process shortly anyway.return null;} finally {releaseProvider(provider);}
}复制代码

以上,除了query外,其它操作都会在最后调用releaseProvider释放provider的计数;query比较特殊,会在调用cursor的close后才调用;

这里有个特殊的方法:call,它的不同之处在于数据传输的方式。其它的query, insert, update, delete这些操作是使用binder+ashmem结合的方式进行数据传输,而call纯粹使用的是binder进行

查看大图

App端数据结构

查看大图

  • ProviderKey: 包含URL authority字符串
  • ProviderClientRecord: 与AMS端的ContentProviderRecord对应,主要引用了provider proxy句柄
  • ProviderRefCount: 封装了stable, unstable两种引用
  • ContentProviderHolder: 主要引用了ContentProviderConnection proxy句柄,provider proxy句柄
  • ProviderInfo: provider的抽象存储类
  • ComponentName: 组件类的抽象类

system_server端数据结构

查看大图

  • ProviderMap: AMS端用来缓存ContentProviderRecord的对象,提供四种不同的查询方式
  • ContentProviderRecord: provider在AMS中的封装类,主要引用了provider binder proxy、进程信息
  • Association: 两个进程相互关联的抽象
  • ProcessRecord: 进程的抽象,包含了进程优先级、四大组件等等信息
  • ContentProviderConnection: 主要引用了provider客户端的进程抽象

总结

本篇博客详细分析了provider代理对象是如果被获取的,其中涉及了provider客户端,system_server的AMS,provider服务端三个进程的通信。同时也分析了contentprovider的联级诛杀原理,这块的知识点客户端开发需要格外注意,否则自己的app无故死亡了都不知道。

关于provider客户端如何同服务端使用ashmem与binder结合的方式来传输数据,因为本篇实在已经太长了,我没有细讲,以后再作分析。

关于ContentProvider的监听,这块感兴趣的可以看我的博客从源码层解析ContentService如何实现数据变化监听

从源码角度看ContentProvider相关推荐

  1. 从JDK源码角度看Long

    概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其 ...

  2. 从源码角度看Android系统Launcher在开机时的启动过程

    Launcher是Android所有应用的入口,用来显示系统中已经安装的应用程序图标. Launcher本身也是一个App,一个提供桌面显示的App,但它与普通App有如下不同: Launcher是所 ...

  3. 从源码角度看Android系统SystemServer进程启动过程

    SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动 ...

  4. 从源码角度看Android系统Zygote进程启动过程

    在Android系统中,DVM.ART.应用程序进程和SystemServer进程都是由Zygote进程创建的,因此Zygote又称为"孵化器".它是通过fork的形式来创建应用程 ...

  5. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  6. 从源码角度看CPU相关日志

    简介 (本文原地址在我的博客CheapTalks, 欢迎大家来看看~) 安卓系统中,普通开发者常常遇到的是ANR(Application Not Responding)问题,即应用主线程没有相应.根本 ...

  7. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(github.com/answershuto-)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些产出也会对同样想要学习Vue.j ...

  8. 从源码角度看Spark on yarn client cluster模式的本质区别

    首先区分下AppMaster和Driver,任何一个yarn上运行的任务都必须有一个AppMaster,而任何一个Spark任务都会有一个Driver,Driver就是运行SparkContext(它 ...

  9. hotspot源码角度看OOP之类属性的底层实现(一)

    hello,大家好,我是江湖人送外号[道格牙]的子牙老师. 最近看hotspo源码有点入迷.hotspot就像一座宝库,等你探索的东西太多了.每次达到一个新的Level回头细看,都有不同的感触.入迷归 ...

最新文章

  1. java s字符_java 字符集s
  2. [RDMA] 高性能异步的可靠消息传递和RPC :Accelio
  3. 升级 Vue3 大幅提升开发运行效率
  4. 三元表达式,列表解析和生成器表达式
  5. 如何查看Exchange2010中邮箱数据库的用户邮箱占用情况
  6. Autofac的切面编程实现
  7. linux在A目录下创建B文件,Linux课程---5、常用文件命令和目录命令(创建文件命令)...
  8. (53)FPGA基础编码D触发器(二)
  9. [第二届构建之法论坛] 预培训文档(C++版)
  10. [server]阿里云服务器远程文件传输的解决方案
  11. 学计算机仓库管理一定打字吗,仓库管理员要会电脑吗?需要哪些电脑操作呢?...
  12. Linux如何切换字符或者图形界面
  13. android 阻尼函数,数学的 H5 应用:拖动阻尼
  14. 数据防泄密软件可以解决哪些安全问题?
  15. Nginx源码完全注释(4)ngx_queue.h / ngx_queue.c
  16. 什么是架构?架构的本质和作用!
  17. 3d transform的(x、y、z)坐标空间及位置
  18. 看这里,教你如何快速将pdf文件翻译成中文
  19. win32 009 masm32
  20. python多级雷达图绘制解析_Python实例15:霍兰德人格分析雷达图

热门文章

  1. 不狂热不忧虑:观看波士顿动力机器人视频的正确姿势
  2. 这个高仿真框架AI2-THOR,想让让强化学习快速走进现实世界
  3. php实现从尾到头打印列表
  4. 游戏人生Silverlight(6) - 贪吃蛇[Silverlight 3.0(c#)]
  5. curl之采集QQ空间留言
  6. SpringBoot(1.5.6.RELEASE)源码解析(三)
  7. eclipse-4.4.2安装Groovy插件(其他版本eclipse可参考)
  8. (转)jquery图片左右滚动
  9. Centos下安装MySQL全过程(linux下安装MySQL)
  10. JavaScript数组操作 [Z]