Android中ContentProvider的工作过程
ContentProvider是一种内容共享型组件,它通过Binder向其它组件乃至其它应用提供数据。关于ContentProvider是如何使用的,我们在之前文章《Android里内容提供者ContentProvider的使用》中已经有介绍过和实例演示。今天主要是对ContentProvider的一些工作过程作分析和学习。
ContentProvider的启动过程
一般来说,ContentProvider都应该是单实例的。因为android:multiprocess默认是false,当android:multiprocess为true时,ContentProvider为多实例,这时每个调用者的进程都存在一个ContentProvider对象。但由于实际开发中,并未发现多实例的具体使用场景(虽然官方解释说可以避免进程问通信的开销),因此我们可以简单认为ContentProvider都是单实例。下面来看看单实例的ContentProvider的启动过程。
当ContentProvider所在的进程启动时,ContentProvider会同时启动并被发布到ActivityManagerService中。而且,ContentProvider的onCreate很特殊,会先于Application的onCreate执行。我们在前面文章《Android应用程序启动详解(二)从源码了解App的启动过程》中也曾经一句话提过到这点,来回顾一下源码:
ActivityThread.java
private void handleBindApplication(AppBindData data) { …… final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); try { // 关键代码1 Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { // 关键代码2 installContentProviders(app, data.providers); mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); } } try { mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { throw new RuntimeException( "Exception thrown in onCreate() of " + data.instrumentationName + ": " + e.toString(), e); } try { // 关键代码3 mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } finally { StrictMode.setThreadPolicy(savedPolicy); }}
关键代码1中就是创建Application对象,而关键代码3就是在创建出Application对象后,调用了callApplicationOnCreate(app)方法,也就是回调Application的onCreate方法。我们重要来看看关键代码2,它就是ContentProvider的创建过程逻辑。继续跟踪installContentProviders方法看它做了啥事情:
private void installContentProviders( Context context, List providers) { final ArrayList results = new ArrayList(); // 关键代码1 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()); } // 关键代码2 IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } try { // 关键代码3 ActivityManagerNative.getDefault().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }}
方法内逻辑很清楚,关键代码1中遍历当前进程的ProviderInfo的列表并一一调用关键代码2中的installProvider方法来启动它们,最后就是关键代码3中,将已经启动的ContentProvider发布到ActivityManagerService中(关于ActivityManagerNative.getDefault()为什么是ActivityManagerService,我们在前面也提到很多次,不明白的话可以查看之前的文章《Android应用程序启动详解(一)》)。来看看关键代码2的installProvider方法:
private IActivityManager.ContentProviderHolder installProvider(Context context, IActivityManager.ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) { …… try { // 关键代码1 final java.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl. loadClass(info.name).newInstance(); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + info.name + " from sourceDir " + info.applicationInfo.sourceDir); return null; } if (DEBUG_PROVIDER) Slog.v( TAG, "Instantiating local provider " + info.name); // XXX Need to create the correct context for this provider. // 关键代码2 localProvider.attachInfo(c, info); } catch (java.lang.Exception e) { ……; } } else { …… } IActivityManager.ContentProviderHolder retHolder; synchronized (mProviderMap) { if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider + " / " + info.name); IBinder jBinder = provider.asBinder(); if (localProvider != null) { …… } else { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { …… } else { // 关键代码3 ProviderClientRecord client = installProviderAuthoritiesLocked( provider, localProvider, holder); …… } retHolder = prc.holder; } } return retHolder;}
上述关键代码1中,通过类加载器完成了ContentProvider对象的创建,还通过关键代码2中attachInfo方法来设置一些信息,来看看attachInfo的方法代码:
private void attachInfo(Context context, ProviderInfo info, boolean testing) { mNoPerms = testing; /* * Only allow it to be set once, so after the content service gives * this to us clients can't change it. */ if (mContext == null) { mContext = context; if (context != null) { mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService( Context.APP_OPS_SERVICE); } mMyUid = Process.myUid(); if (info != null) { setReadPermission(info.readPermission); setWritePermission(info.writePermission); setPathPermissions(info.pathPermissions); mExported = info.exported; mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0; setAuthorities(info.authority); } ContentProvider.this.onCreate(); }}
方法中可以看到ContentProvider的onCreate方法就是在这里被调用的了,
再看回installProvider方法的关键代码3的installProviderAuthoritiesLocked方法是做了什么事情:
final ArrayMap mProviderMap = new ArrayMap();private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) { final String auths[] = holder.info.authority.split(";"); final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid); final ProviderClientRecord pcr = new ProviderClientRecord( auths, provider, localProvider, holder); for (String auth : auths) { 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;}
原来是将刚创建的IContentProvider对象通过Map的形式来存储起来。好了,到此为止,ContentProvider就已经启动完成。
ContentProvider的操作过程
当ContentProvider启动后,外界就可以通过它所提供的insert、delete、update 和 query这四个接口来对ContentProvider中的数据源进行增删改查操作。这四个方法都是通过Binder来调用,外界无法直接访问ContentProvider,它只能通过ActivityManagerService根据Uri来获取对应用ContentProvider的Binder接口IConentProvider,然后再通过IContentProvider来访问ContentProvider中的数据源。
四个操作接口逻辑很类似,我们就从query方法入手,先来看下实例代码:
Cursor cursor = getContentResolver().query(Uri.parse("content:// com.zyx.PersonProvider/person/1"), null, null, null, null);
访问ContentProvider需要通过getContentResolver方法来获取ContentResolver的对象,ContentResolver是一个抽象类,getContentResolver方法实际上是返回了ApplicationContentResolver对象,来看代码:
ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) { Preconditions.checkNotNull(uri, "uri"); // 关键代码1 IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { return null; } IContentProvider stableProvider = null; Cursor qCursor = null; try { long startTime = SystemClock.uptimeMillis(); ICancellationSignal remoteCancellationSignal = null; if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); remoteCancellationSignal = unstableProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } try { // 关键代码2 qCursor = unstableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } catch (DeadObjectException e) { unstableProviderDied(unstableProvider); // 关键代码3 stableProvider = acquireProvider(uri); if (stableProvider == null) { return null; } // 关键代码4 qCursor = stableProvider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); } …… return wrapper; } catch (RemoteException e) { return null; } finally { …… }}
上述代码中,关键代码1 和 关键代码3都是为了获取一个IContentProvider接口对象,分别调用了acquireUnstableProvider 和 acquireProvider 方法,然后再调用其获取对象的query方法,acquireUnstableProvider方法的内部也是调用了acquireProvider方法,ApplicationContentResolver继承ContentResolver:
ContextImpl.java
private static final class ApplicationContentResolver extends ContentResolver { …… @Override protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); } @Override protected IContentProvider acquireUnstableProvider(Context c, String auth) { return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), false); }……}
再来看看acquireProvider方法的代码:
ActivityThread.java
public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { // 关键代码1 final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } IActivityManager.ContentProviderHolder holder = null; try { // 关键代码2 holder = ActivityManagerNative.getDefault().getContentProvider( getApplicationThread(), auth, userId, stable); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); return null; } // Install provider will increment the reference count for us, and break // any ties in the race. holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider;}
关键代码1中,通过acquireExistingProvider方法来获得已经存在的ContentProvider,如果存在就直接返回。否则,就会到关键代码2处发送一个进程间请求给ActivityManagerService让其启动目标ContentProvider,这样最终就会到了进程的启动入口方法ActivityThread的main方法,通过一系列过程后就又了”ContentProvider的启动过程”中所述的installContentProviders方法去。我们来看看关键代码1 的acquireExistingProvider方法的代码:
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()) { …… return null; } ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { incProviderRefLocked(prc, stable); } return provider; }}
我们看到了熟悉的mProviderMap对象,也就是在启动过程中调用了onCreate后将其保存起来的Map对象。知道了IContentProvider的获取,现在回到ContentResolver的query中的关键代码2和关键代码4中去看它们调用了。
IContentProvider是ContentProvider的Binder对象,它的具本实现是ContentProviderNative和ContentProvider.Transport,其中ContentProvider.Transport继承了ContentProviderNative。来看看ContentProvider.Transport的query方法代码:
ContentProvider.java
class Transport extends ContentProviderNative { …… @Override public Cursor query(String callingPkg, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) { validateIncomingUri(uri); uri = getUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { if (projection != null) { return new MatrixCursor(projection, 0); } Cursor cursor = ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport( cancellationSignal)); if (cursor == null) { return null; } return new MatrixCursor(cursor.getColumnNames(), 0); } final String original = setCallingPackage(callingPkg); try { // 关键代码 return ContentProvider.this.query( uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport(cancellationSignal)); } finally { setCallingPackage(original); } }……}
关键代码明显不过,它调用了ContentProvider的query方法,也就是我们自定义继承ContentProvider的类的query方法,query方法的执行结果再通过Binder返回给调用者,就这样整个调用过程就完成了。
总结
- ContentProvider一般是单实例,也可通过设置android:multiprocess为true使其变为多实例,虽官方说可以避免进程问通信的开销,但目前并未发现多实例的具体使用场景
- ContentProvider是在Application创建完成后进行,而且ContentProvider的onCreate方法是先于Application的onCreate方法
- ContentProvider创建完成后,会将IContentProvider(ContentProvider的Binder对象)会保存于一个ArrayMap对象中,当外部调用insert、delete、update 和 query方法时便先检查该对象是否存在
- IContentProvider的实现类是ContentProviderNative和ContentProvider.Transport,当外部调用insert、delete、update 和 query方法时,就是调用到IContentProvider的insert、delete、update 和 query方法,最终也是会调用到ContentProvider的insert、delete、update 和 query方法。
更多相关文章
- Android状态栏透明方法,与工具栏颜色一致
- Android手机应用开发(八) | 制作简单音乐播放器
- Android(安卓)Jetpack 之 LifeCycle
- [置顶] Android消息机制Handler、Looper、MessageQueue源码分析
- Android(安卓)状态栏下拉列表添加自定义item开关
- Android(安卓)获取判断是否有悬浮窗权限的方法
- Android简单记录和恢复ListView滚动位置的方法
- Android(安卓)使用PDF.js浏览pdf的方法示例
- Android中Bitmap用法实例分析