之前已经分析过在应用启动的时候安装ContentProvider的流程了,现在我们再从使用者的角度看看是怎样去拿到ContentProvider的。
在使用ContentProvider的时候,我们通常会使用Context拿到ContentResolver,然后在执行CURD的操作,比如我们要查询手机中的联系人,通常会这样做:
String[] projection = new String[] {
Profile._ID,
Profile.DISPLAY_NAME_PRIMARY,
Profile.LOOKUP_KEY,
Profile.PHOTO_THUMBNAIL_URI };
Cursor profileCursor = getContentResolver().query(
Profile.CONTENT_URI,
projection , null, null,null);
这里先重点分析一下拿到ContentProvider的过程。首先来看看这个ContentResolver是什么东西。通过源码我们可以看到它是一个抽象类,实现了ContentInterface接口,ContentInterface中则定义了CRUD的相关方法。我们可以在ContextImpl中找到getContentResolver(),通过源码我们知道,实际上我们拿到的是ApplicationContentResolver对象。
这里看完,我们可以继续看query方法,实现在ContentResolver类当中。
IContentProvider unstableProvider = acquireUnstableProvider(uri);
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
try {
qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
queryArgs, remoteCancellationSignal);
}
qCursor.getCount();
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
return null;
} finally {
if (qCursor != null) {
qCursor.close();
}
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
releaseProvider(stableProvider);
}
}
上面的代码看起来还是比较简单的,首先是是去调用acquireUnstableProvider拿到unstableProvider,通过它去取数据,如果拿不到再去调用acquireProvider拿stableProvider,最后把stableProvider和数据使用CursorWrappInner包装返回给调用者,在finally中把cursor关掉,把provider给释放掉。
我们先来看看拿stableProvider的逻辑:
public final IContentProvider acquireUnstableProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
String auth = uri.getAuthority();
if (auth != null) {
return acquireUnstableProvider(mContext, uri.getAuthority());
}
return null;
}
简单说一下,上面首先会判断我们的URL是否为content:开头,因为这是ContentProvider的scheme。之后会到url中拿到authority, autority包括这几个部分:[userinfo@]host[:port]
。最后通过authority去调用ApplicationContentResolver中的同名方法。
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
上面的方法会从我们的authority分别拿出userId和host,当然userId有可能是不传的,就会默认使用当前用户。我们继续去看ActivityThread.acquireProvider()代码:
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
ContentProviderHolder holder = null;
final ProviderKey key = getGetProviderKey(auth, userId);
try {
synchronized (key) {
holder = ActivityManager.getService().getContentProvider( getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
if (holder != null && holder.provider == null && !holder.mLocal) {
synchronized (key.mLock) {
if(key.mHolder != null) {
} else {
key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS)
}
holder = key.mHolder;
}
}
}
} finally {
synchronized (key.mLock) {
key.mHolder = null;
}
}
holder = installProvider(c, holder, holder.info, true, holder.noReleaseNeeded, stable);
return holder.provider;
}
这里我们有一个参数stable,因此我们前面获取stableProvider和unstableProvider都会走到这个方法里面来。
第3行代码,我们首先会到已存在的Provider列表中去拿,代码如下:
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
//处理Binder不存活的情况
handleUnstableProviderDiedLocked(jBinder, true);
return null;
}
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable); //增加引用计数
}
return provider;
}
}
可以看到此处为通过auth构建出来的key到mProviderMap中查找ProviderClientRecord,而这个就是我们之前分析安装Provider时候所创建并且放置到这个map中去的。后面会检查Binder是否仍然存活,并返回。
在这里我们需要注意一点,如果安装我们之前分析安装的流程,我们在自己的app里面拿自己的ContentProvider这里是肯定可以拿到的,但是如果是其他的应用提供的ContentProvider这里很显然是拿不到的。因此我们需要继续回到acquireProvider方法去看其他部分的代码。
在第11行中,我们会到AMS中去获取ContentProviderHolder,如果拿到了远端的holder,但是我们本地的ProviderKey中的holder为空,说明我们本地还没有安装这个ContentProvider,需要等待,也就是执行第16行代码进入等待状态。而这个地方的解除等待在ContentProviderHelper类的publishContentProviders方法中,可以去之前分析安装过程的文章最后一部分查看。
而拿到holder之后,最后又去执行了一次installProvider方法,这里的安装跟我们之前的启动App安装是有一些不同的,我们放到后面再来分析。
然而前面的去AMS拿ContentProviderHolder代码我们还没有看,具体代码也仍然在ContentProviderHelper中,现在去看一下它的getContentProviderImpl()方法,内容比较长,先一点一点的贴代码:
//ContentProviderHelper.java getContentProviderImpl
synchronized (mService) {
ProcessRecord r = null;
if (caller != null) {
r = mService.getRecordForAppLOSP(caller);
}
UserManagerService userManagerService = UserManagerService.getInstance();
if (!isAuthorityRedirectedForCloneProfile(name)
|| !userManagerService.isMediaSharedWithParent(userId)) { //。mediastore需要特殊判断,这里会把那些情况给过滤掉
cpr = mProviderMap.getProviderByName(name, userId);
}
...
ProcessRecord dyingProc = null;
if (cpr != null && cpr.proc != null) {
providerRunning = !cpr.proc.isKilled(); //检查ContentProvider目标进程是否被杀掉
if (cpr.proc.isKilled() && cpr.proc.isKilledByAm()) {
dyingProc = cpr.proc; //如果被杀了或者正在被杀就记录
}
}
if (providerRunning) {
cpi = cpr.info;
if (r != null && cpr.canRunHere(r)) {
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
ContentProviderHolder holder = cpr.newHolder(null, true);
holder.provider = null;
return holder;
}
//PLACEHOLDER1
}
//PLACEHOLDER2
}
以上的代码是我们会遇到的第一种情况,首先去拿到进程ProcessRecord,之后根据Provider的authority name和userId到ProviderMap中拿已有的ContentProviderRecord。拿到之后首先检查ContentProvider提供方的进程是否正在运行中,如果在运行中,并且canRunHere检查为true, 就会检查是否有权限来执行,有权限就会创建一个ContentProviderHolder传递出去。
canRunHere所做的判断代码如下:
public boolean canRunHere(ProcessRecord app) {
return (info.multiprocess || info.processName.equals(app.processName))
&& uid == app.info.uid;
}
解释下就是首先判断Provider是否支持多个进程中运行,也就是在Manifest为provider配置了multiprocess=true,另外检查Provider所在进程和当前调用是否为同一个进程,这两者条件满足一个就可以。同时还要满足当前进程的UID和Provider的进程UID相同,这个在两者为同一个应用,或者两者共享签名,或共享UID的情况下满足。这种情况下就可以直接使用ContentProvider。这种情况会创建新的ContentProviderHolder传递到App进程,其中会携带ContentProviderRecord过去。此时我们看到的传到App进程的ContentProviderConnection也是为空,至于这个对象的用处是什么我们后面会分析。同时还会把Holder的成员provider设置为空,这个有什么用呢,可以后面再看installProvider方法。
在这里还有一个检查权限和是否可以联合运行的方法checkAssociationAndPermissionLocked,代码如下:
if ((msg = checkContentProviderAssociation(callingApp, callingUid, cpi)) != null) {
throw new SecurityException("Content provider lookup " + cprName
+ " failed: association not allowed with package " + msg);
}
if ((msg = checkContentProviderPermission(
cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
callingApp != null ? callingApp.toString() : null))
!= null) {
throw new SecurityException(msg);
}
里面又分别调用了两个方法,第一个用于检查两个进程是否可以联合使用,默认是允许的,除非是系统内置应用或者预装应用会有比较严格的检查,我们这里不必关注。可以去看一下权限检查,这个比较重要:
private String checkContentProviderPermission(ProviderInfo cpi, int callingPid, int callingUid,
int userId, boolean checkUser, String appName) {
boolean checkedGrants = false;
if (checkUser) { //对于普通应用这个值传过来的为true
int tmpTargetUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
//检查是否有临时授权,这个一般是在Manifest中添加<grant-uri-permission>或者android:grantUriPermissions
if (mService.mUgmInternal.checkAuthorityGrants(
callingUid, cpi, tmpTargetUserId, checkUser)) {
return null; //检查通过直接返回成功
}
checkedGrants = true;
}
userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId,
false, ActivityManagerInternal.ALLOW_NON_FULL,
"checkContentProviderPermissionLocked " + cpi.authority, null);
if (userId != tmpTargetuserId) {
checkGrants = false;
}
}
if (ActivityManagerService.checkComponentPermission(cpi.readPermission,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) { //检查读权限,授权过返回
return null;
}
if (ActivityManagerService.checkComponentPermission(cpi.writePermission,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) { //写权限检查,授权过则返回成功
return null;
}
PathPermission[] pps = cpi.pathPermissions;
if (pps != null) {
int i = pps.length;
while (i > 0) {
i--;
PathPermission pp = pps[i];
String pprperm = pp.getReadPermission();
if (pprperm != null && ActivityManagerService.checkComponentPermission(pprperm,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
String ppwperm = pp.getWritePermission();
if (ppwperm != null && ActivityManagerService.checkComponentPermission(ppwperm,
callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
}
}
}
关于权限,前面的代码我已经加了相关的注释,我们可以对比官方文档,其中共检查了四种权限,分别是临时授权,路径授权,单独的读写授权和单一读写程序级别的授权。关于权限检查的更多内容,这里我们也先略过。此时我们可以继续回来继续分析getContentProviderImpl方法。我们继续看上面留的PLACEHOLDER 1处的代码:
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage,
callingTag, stable, true, startTime, mService.mProcessList,
expectedUserId);
其中还有一些关于OOM设置的代码这里先跳过了,上面主要的代码也是检查权限以及这个incProviderCountLocked方法:
private ContentProviderConnection incProviderCountLocked(ProcessRecord r,
final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid,
String callingPackage, String callingTag, boolean stable, boolean updateLru,
long startTime, ProcessList processList, @UserIdInt int expectedUserId) {
final ProcessProviderRecord pr = r.mProviders;
for (int i = 0, size = pr.numberOfProviderConnections(); i < size; i++) {
ContentProviderConnection conn = pr.getProviderConnectionAt(i);
if (conn.provider == cpr) {
conn.incrementCount(stable);
return conn;
}
}
ContentProviderConnection conn = new ContentProviderConnection(cpr, r, callingPackage,
expectedUserId);
conn.startAssociationIfNeeded();
conn.initializeCount(stable);
cpr.connections.add(conn);
if (cpr.proc != null) {
cpr.proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
}
pr.addProviderConnection(conn);
mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateLru && cpr.proc != null
&& r.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
processList.updateLruProcessLocked(cpr.proc, false, null);
}
return conn;
}
这里有不少关于Association相关的代码,而我们的应用一般不会走到这里。我们只需要关注其中创建Connection以及为他创建引用计数。关于它的计数,我们放到最好再看一下。
PLACEHOLDER 2处,首先处理的就是provider为运行的情况,这种情况就会回到Provider的进程去安装ContentProvider,这部分代码我们之前已经分析过了,这里略过。而我们是在使用者进程调用的此处的caller也不为空,再往后,则应该是如下的代码:
mService.grantImplicitAccess(userId, null, callingUid,
UserHandle.getAppId(cpi.applicationInfo.uid));
if (caller != null) {
synchronized (cpr) {
if (cpr.provider == null) {
if (cpr.launchingApp == null) {
return null;
}
if (conn != null) {
conn.waiting = true;
}
}
}
return cpr.newHolder(conn, false);
}
这里可以看到,就是先给调用的uid授权,设置wait 为true,创建一个ContentProviderHolder返回。这里是带着ContentProviderConnection和IContentProvider的。
代码讲解的部分只介绍了我们认为caller不为空的情况,实际上是更加复杂的,这里就把其中的完整流程流程图放在这里,如有需要可参考流程图以及之前的App启动时候的ContentProvider安装一起看。
---
title: getContentProviderImpl流程
---
flowchart TD
A(getContentProviderImpl) --> B(mProviderMap.getProviderByName)
B --> C(providerRunning = !cpr.proc.isKilled)
C --> D{Check providerRunning}
D --> |providerRunning == true|E{cpr.canRunHere}
E --> |No| I{CheckPermission}
E --> |Yes|F{ChecPermission}
F --> |Pass|G((Return local Holder))
F --> |Not Pass|H(Throw Exception)
I --> |Pass|J(incProviderCountLocked)
I --> |Not Pass|H
D --> |No|K(PMS.resolveContentProvider)
K --> L{CheckPermission}
L --> |Not Pass|H
L --> |Pass|M(Generate CPRecord)
M --> A1{cpr.canRunHere}
A1 --> |true|A2((Return local Holder))
A1 --> |False| A3{Process Live}
A3 --> |Process is live|A4(Install Provider)
A3 --> |Not Start Or Die| A5(Start Process)
A4 --> A6(incProviderCountLocked)
A5 --> A6
A6 --> B1(AMS.grantImplictAccess)
J --> B1
B1 --> B2{From customer Call}
B2 --> |Yes|B3((Return Remote Holder))
B2 --> |No|B4{cpr.provider==null}
B4 --> |Yes|B5((cpr.wait))
B5 --> B4
B4 --> |No|B6((Return Remote Holder))
看了这么多,我们就可以继续回去看App进程的代码了。在App进程就是执行我们前面说的installProvider过程。
我们可以继续分析query的过程,看代码我们知道调用的是IContentProvider的query方法,对于同UID的进程,IContentProvider为我们在instalProvider创建的本地的ContentProvider中的mTransport而其他的则是AMS调用带过来的IcontentProvider远端接口,我们这里以非本进程的情况来分析,它的获取是如下代码:
//android.content.ContentProviderNative.java
static public IContentProvider asInterface(IBinder obj)
{
if (obj == null) {
return null;
}
IContentProvider in =
(IContentProvider)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ContentProviderProxy(obj);
}
也就是说,如果是相同的UID的进程拿到的为Transport对象,如果是其他的则拿到的是ContentProviderProxy对象。
前面我们还有关于ContentProviderConnection还有很多东西没有介绍,这里继续看一下。首先是incProviderCountLocked方法中所调用的conn.incrementCount(stable)。在我看代码的过程中stable这个变量唯有这里使用了,我们继续看它的源码:
public int incrementCount(boolean stable) {
synchronized (mLock) {
if (stable) {
mStableCount++;
mNumStableIncs++;
} else {
mUnstableCount++;
mNumUnstableIncs++;
}
return mStableCount + mUnstableCount;
}
}
可以看到这个类主要记录了Stable和UnStable的调用次数,实际上AMS这一端stable和unstable似乎除了计数之外没有什么区别。但是在客户端installProvider的时候却是有区别的。我们之前分析的启动时候安装的情况stable都是为true,我们可以看看ActivityThread.installProvider如下的代码:
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);
ProviderRefCount用于记录Provider的引用计数,其中用stableCount和unstableCount来计数,当我们不需要释放Provider的时候,两个数字都设置为了1000,当我们是stable的时候只设置stable数为1,unstable数量为0,当为unstable的时候也同理。之前我们是有看到对于已经存在的provider是通过incProviderRefLocked来增加起计数的。那我们有了增加计数,那么使用完之后也应该需要减少计数。在query的finally代码块中有如下代码:
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
releaseProvider(stableProvider);
}
他们最终调用的为ActivityThread.releaseProvider方法:
public final boolean releaseProvider(IContentProvider provider, boolean stable) {
if (provider == null) {
return false;
}
IBinder jBinder = provider.asBinder();
synchronized (mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc == null) {
// The provider has no ref count, no release is needed.
return false;
}
boolean lastRef = false;
if (stable) {
if (prc.stableCount == 0) {
return false;
}
prc.stableCount -= 1;
if (prc.stableCount == 0) {
lastRef = prc.unstableCount == 0;
try {
ActivityManager.getService().refContentProvider(
prc.holder.connection, -1, lastRef ? 1 : 0);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
}
}
} else {
if (prc.unstableCount == 0) {
return false;
}
prc.unstableCount -= 1;
if (prc.unstableCount == 0) {
lastRef = prc.stableCount == 0;
if (!lastRef) {
try {
ActivityManager.getService().refContentProvider(
prc.holder.connection, 0, -1);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
}
}
}
}
return true;
}
}
代码主要分了两个分支,分别对stable和unstable的情况进行处理,他们都是先把本地对应的ProviderRefCount中的数字减一,但是调用AMS.refContentProvider却不一样,stable count减为0的时候会直接调用,而unstable为0的时候要stableCount不为0才会调用。传递的参数也有区别,代码很简单就不详解了。直接去看ContentProviderHelper的refContentProvider方法。
boolean refContentProvider(IBinder connection, int stable, int unstable) {
ContentProviderConnection conn;
try {
conn = (ContentProviderConnection) connection;
} catch (ClassCastException e) {
}
if (conn == null) {
throw new NullPointerException("connection is null");
}
try {
conn.adjustCounts(stable, unstable);
return !conn.dead;
} finally {
}
}
这里的代码其实比较简单,就是调用ContentProviderConnection的adjustCounts,这个方法的代码如下:
public void adjustCounts(int stableIncrement, int unstableIncrement) {
synchronized (mLock) {
if (stableIncrement > 0) {
mNumStableIncs += stableIncrement;
}
final int stable = mStableCount + stableIncrement;
if (stable < 0) {
throw new IllegalStateException("stableCount < 0: " + stable);
}
if (unstableIncrement > 0) {
mNumUnstableIncs += unstableIncrement;
}
final int unstable = mUnstableCount + unstableIncrement;
if (unstable < 0) {
throw new IllegalStateException("unstableCount < 0: " + unstable);
}
if ((stable + unstable) <= 0) {
throw new IllegalStateException("ref counts can't go to zero here: stable="
+ stable + " unstable=" + unstable);
}
mStableCount = stable;
mUnstableCount = unstable;
}
}
这里就是来根据传过来的参数来调整stableCount和unstableCount,也就完成了这几个count的变化。也就是完成了AMS端的减少计数。
到此位置,我们也就拿到了IContentProvider,也就可以使用它提供的CRUD方法,进行数据的增删改查了。至于具体是如何查询数据,如何做到数据的跨进程共享,如何绕过Binder传输限制1MB实现跨进程传输数据,限于篇幅下次再来分析。