本文目标:以MediaProvider为例,想搞清楚调用ContentResolver访问各个ContentProvider的调用过程。
Java code:
getContentResolver().query(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,null,null)
具体调用过程是
1.通过ContentResolver先查找对应给定Uri的ContentProvider,返回对应的BinderProxy
如果该Provider尚未被调用进程使用过:
a.通过ServiceManager查找activity service得到ActivityManagerService对应BinderProxy
b.调用BinderProxy的transcat方法发送GET_CONTENT_PROVIDER_TRANSACTION命令,得到对应ContentProvider的BinderProxy。
如果该Provider已被调用进程使用过,则调用进程会保留使用过provider的HashMap。此时直接从此表查询即得。
2.调用BinderProxy的query()
通过下图,可以清楚的看到,getContentResolver().query调用时首先得到Actiivity服务,再次查询Activity服务中记录的对应ContentProviderRecord.如果发现此ContentProvider尚未publish则引发publish该ContentProvider,详见分析二一文。查询到ContentProviderRecord后返回对应MediaProvider的IBinder并返回给调用者。
整个调用过程中需要经过两次Binder调用以实现跨进程访问,即:
Calling Process -> ActivityManagerService Process -> MediaProvider process
Detailed call sequence(If calling process doesn't ever used the Provider):
源代码调用路径:
第1,2步:
frameworks/base/core/java/android/app/ContextImpl.java
private static final class ApplicationContentResolver extends ContentResolver {
public ApplicationContentResolver(Context context, ActivityThread mainThread) {
super(context);
mMainThread = mainThread;
}
@Override
protected IContentProvider acquireProvider(Context context, String name) {
return mMainThread.acquireProvider(context, name);
}
public final IContentProvider acquireProvider(Context c, String name) {
IContentProvider provider = getProvider(c, name);
if(provider == null)
return null;
IBinder jBinder = provider.asBinder();
synchronized(mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc == null) {
mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); //创建对此Provider的引用计数
} else {
prc.count++; //计数+1
} //end else
} //end synchronized
return provider;
}
得到名字为name的Provider
private final IContentProvider getProvider(Context context, String name) {
IContentProvider existing = getExistingProvider(context, name);
if (existing != null) {
return existing; //Provider已经publish,直接返回
}
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), name);
} catch (RemoteException ex) {
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + name);
return null;
}
IContentProvider prov = installProvider(context, holder.provider,
holder.info, true);
if (holder.noReleaseNeeded || holder.provider == null) {
// We are not going to release the provider if it is an external
// provider that doesn't care about being released, or if it is
// a local provider running in this process.
//Slog.i(TAG, "*** NO RELEASE NEEDED");
synchronized(mProviderMap) {
mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000)); //为何holder.provider == null??
}
}
return prov;
}
得到ActivityManagerService。
static public IActivityManager getDefault()
{
if (gDefault != null) {
return gDefault;
}
IBinder b = ServiceManager.getService("activity");
gDefault = asInterface(b);
return gDefault;
}
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name) {
if (caller == null) {
String msg = "null IApplicationThread when getting content provider "
+ name;
throw new SecurityException(msg);
}
return getContentProviderImpl(caller, name);
}
private final ContentProviderHolder getContentProviderImpl(
IApplicationThread caller, String name) {
ContentProviderRecord cpr;
ProviderInfo cpi = null;
synchronized(this) {
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller); //caller app must be registered
if (r == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when getting content provider " + name);
}
}
// First check if this content provider has been published...
cpr = mProvidersByName.get(name);
if (cpr != null) {
cpi = cpr.info;
String msg;
if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { //检查app是否有访问权限
throw new SecurityException(msg);
}
if (r != null && cpr.canRunHere(r)) {
// This provider has been published or is in the process
// of being published... but it is also allowed to run
// in the caller's process, so don't make a connection
// and just let the caller instantiate its own instance.
if (cpr.provider != null) {
// don't give caller the provider object, it needs
// to make its own.
cpr = new ContentProviderRecord(cpr);
}
return cpr;
}
final long origId = Binder.clearCallingIdentity();
// In this case the provider instance already exists, so we can
// return it right away.
if (r != null) {
if (DEBUG_PROVIDER) Slog.v(TAG,
"Adding provider requested by "
+ r.processName + " from process "
+ cpr.info.processName);
Integer cnt = r.conProviders.get(cpr);
if (cnt == null) {
r.conProviders.put(cpr, new Integer(1));
} else {
r.conProviders.put(cpr, new Integer(cnt.intValue()+1));
}
cpr.clients.add(r);
if (cpr.app != null && r.setAdj <= PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
// make sure to count it as being accessed and thus
// back up on the LRU list. This is good because
// content providers are often expensive to start.
updateLruProcessLocked(cpr.app, false, true);
}
} else {
cpr.externals++;
}
if (cpr.app != null) {
updateOomAdjLocked(cpr.app);
}
Binder.restoreCallingIdentity(origId);
}
...
}
// Wait for the provider to be published...
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": launching app became null");
EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
cpi.applicationInfo.packageName,
cpi.applicationInfo.uid, name);
return null;
}
try {
cpr.wait(); //publishContentProvider结束后会notify
} catch (InterruptedException ex) {
}
}
}
return cpr;
}
frameworks/base/core/java/android/content/ContentProviderNative.java
final class ContentProviderProxy implements IContentProvider
{
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
//TODO make a pool of windows so we can reuse memory dealers
CursorWindow window = new CursorWindow(false /* window will be used remotely */);
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
IBulkCursor bulkCursor = bulkQueryInternal(
url, projection, selection, selectionArgs, sortOrder,
adaptor.getObserver(), window,
adaptor);
return adaptor;
}
private IBulkCursor bulkQueryInternal(
Uri url, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
IContentObserver observer, CursorWindow window,
BulkCursorToCursorAdaptor adaptor) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IContentProvider.descriptor);
url.writeToParcel(data, 0);
int length = 0;
if (projection != null) {
length = projection.length;
}
data.writeInt(length);
for (int i = 0; i < length; i++) {
data.writeString(projection[i]);
}
data.writeString(selection);
if (selectionArgs != null) {
length = selectionArgs.length;
} else {
length = 0;
}
data.writeInt(length);
for (int i = 0; i < length; i++) {
data.writeString(selectionArgs[i]);
}
data.writeString(sortOrder);
data.writeStrongBinder(observer.asBinder());
window.writeToParcel(data, 0);
// Flag for whether or not we want the number of rows in the
// cursor and the position of the "_id" column index (or -1 if
// non-existent). Only to be returned if binder != null.
final boolean wantsCursorMetadata = (adaptor != null);
data.writeInt(wantsCursorMetadata ? 1 : 0);
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
IBulkCursor bulkCursor = null;
IBinder bulkCursorBinder = reply.readStrongBinder();
if (bulkCursorBinder != null) {
bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
if (wantsCursorMetadata) {
int rowCount = reply.readInt();
int idColumnPosition = reply.readInt();
if (bulkCursor != null) {
adaptor.set(bulkCursor, rowCount, idColumnPosition);
}
}
}
data.recycle();
reply.recycle();
return bulkCursor;
}
class Transport extends ContentProviderNative {
...
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
enforceReadPermission(uri);
return ContentProvider.this.query(uri, projection, selection,
selectionArgs, sortOrder);
}