本文目标:以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); }
public Cursor query(Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String sort) { //调用到真正做事情的地方