💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 9.5 ContentProvider的工作过程 ContentProvider的使用方法在第2章已经做了介绍,这里再简单说明一下。ContentProvider是一种内容共享型组件,它通过Binder向其他组件乃至其他应用提供数据。当ContentProvider所在的进程启动时,ContentProvider会同时启动并被发布到AMS中。需要注意的是,这个时候ContentProvider的onCreate要先于Application的onCreate而执行,这在四大组件中是一个少有的现象。 当一个应用启动时,入口方法为ActivityThread的main方法,main方法是一个静态方法,在main方法中会创建ActivityThread的实例并创建主线程的消息队列,然后在ActivityThread的attach方法中会远程调用AMS的attachApplication方法并将ApplicationThread对象提供给AMS。ApplicationThread是一个Binder对象,它的Binder接口是IApplicationThread,它主要用于ActivityThread和AMS之间的通信,这一点在前面多次提到。在AMS的attachApplication方法中,会调用ApplicationThread的bindApplication方法,注意这个过程同样是跨进程完成的,bindApplication的逻辑会经过ActivityThread中的mH Handler切换到ActivityThread中去执行,具体的方法是handleBindApplication。在handleBindApplication方法中,ActivityThread会创建Application对象并加载ContentProvider。需要注意的是,ActivityThread会先加载ContentProvider,然后再调用Application的onCreate方法,整个流程可以参看图9-2。 :-: ![](https://img.kancloud.cn/6c/c4/6cc495d284fda3440665db220df8050c_990x602.png) 图9-2 ContentProvider的启动过程 这就是ContentProvider的启动过程,ContentProvider启动后,外界就可以通过它所提供的增删改查这四个接口来操作ContentProvider中的数据源,即insert、delete、update和query四个方法。这四个方法都是通过Binder来调用的,外界无法直接访问ContentProvider,它只能通过AMS根据Uri来获取对应的ContentProvider的Binder接口IConentProvider,然后再通过IConentProvider来访问ContentProvider中的数据源。 一般来说,ContentProvider都应该是单实例的。ContentProvider到底是不是单实例,这是由它的android:multiprocess属性来决定的,当android:multiprocess为false时,ContentProvider是单实例,这也是默认值;当android:multiprocess为true时,ContentProvider为多实例,这个时候在每个调用者的进程中都存在一个ContentProvider对象。由于在实际的开发中,并未发现多实例的ContentProvider的具体使用场景,官方文档中的解释是这样可以避免进程间通信的开销,但是这在实际开发中仍然缺少使用价值。因此,我们可以简单认为ContentProvider都是单实例的。下面分析单实例的ContentProvider的启动过程。 访问ContentProvider需要通过ContentResolver, ContentResolver是一个抽象类,通过Context的getContentResolver方法获取的实际上是ApplicationContentResolver对象,ApplicationContentResolver类继承了ContentResolver并实现了ContentResolver中的抽象方法。当ContentProvider所在的进程未启动时,第一次访问它时就会触发ContentProvider的创建,当然这也伴随着ContentProvider所在进程的启动。通过ContentProvider的四个方法的任何一个都可以触发ContentProvider的启动过程,这里选择query方法。 ContentProvider的query方法中,首先会获取IContentProvider对象,不管是通过acquireUnstableProvider方法还是直接通过acquireProvider方法,它们的本质都是一样的,最终都是通过acquireProvider方法来获取ContentProvider。下面是ApplicationContent-Resolver的acquireProvider方法的具体实现: protected IContentProvider acquireProvider(Context context, String auth) { return mMainThread.acquireProvider(context, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), true); } ApplicationContentResolver的acquireProvider方法并没有处理任何逻辑,它直接调用了ActivityThread的acquireProvider方法,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; } // There is a possible race here. Another thread may try to acquire // the same provider at the same time. When this happens, we want to ensure // that the first one wins. // Note that we cannot hold the lock while acquiring and installing the // provider since it might take a long time to run and it could also potentially // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; try { 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; } // 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; } 上面的代码首先会从ActivityThread中查找是否已经存在目标ContentProvider了,如果存在就直接返回。ActivityThread中通过mProviderMap来存储已经启动的ContentProvider对象,mProviderMap的声明如下所示。 final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<ProviderKey, ProviderClientRecord>(); 如果目前ContentProvider没有启动,那么就发送一个进程间请求给AMS让其启动目标ContentProvider,最后再通过installProvider方法来修改引用计数。那么AMS是如何启动ContentProvider的呢?我们知道,ContentProvider被启动时会伴随着进程的启动,在AMS中,首先会启动ContentProvider所在的进程,然后再启动ContentProvider。启动进程是由AMS的startProcessLocked方法来完成的,其内部主要是通过Process的start方法来完成一个新进程的启动,新进程启动后其入口方法为ActivityThread的main方法,如下所示。 public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(User- Handle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 可以看到,ActivityThread的main方法是一个静态方法,在它内部首先会创建Activity-Thread的实例并调用attach方法来进行一系列初始化,接着就开始进行消息循环了。ActivityThread的attach方法会将ApplicationThread对象通过AMS的attachApplication方法跨进程传递给AMS,最终AMS会完成ContentProvider的创建过程,源码如下所示。 try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { // Ignore } AMS的attachApplication方法调用了attachApplicationLocked方法,attachApplication-Locked中又调用了ApplicationThread的bindApplication,注意这个过程也是进程间调用,如下所示。 thread.bindApplication(processName, appInfo, providers, app.instrumen- tationClass, profilerInfo, app.instrumentationArguments, app.instrumentation- Watcher, app.instrumentationUiAutomationConnection, testMode, enableOpen- GlTrace, isRestrictedBackupMode || ! normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServices- Locked(), mCoreSettingsObserver.getCoreSettingsLocked()); ActivityThread的bindApplication会发送一个BIND_APPLICATION类型的消息给mH, mH是一个Handler,它收到消息后会调用ActivityThread的handleBindApplication方法,bindApplication发送消息的过程如下所示。 AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providers; data.instrumentationName = instrumentationName; data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.instrumentationUiAutomationConnection = instrumentationUiConnection; data.debugMode = debugMode; data.enableOpenGlTrace = enableOpenGlTrace; data.restrictedBackupMode = isRestrictedBackupMode; data.persistent = persistent; data.config = config; data.compatInfo = compatInfo; data.initProfilerInfo = profilerInfo; sendMessage(H.BIND_APPLICATION, data); ActivityThread的handleBindApplication则完成了Application的创建以及Content-Provider的创建,可以分为如下四个步骤。 1.创建ContextImpI和Instrumentation ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { java.lang.ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate instrumentation " + data.instrumentationName + ": " + e.toString(), e); } mInstrumentation.init(this, instrContext, appContext, new ComponentName(ii.packageName, ii.name), data.instrumentation- Watcher, data.instrumentationUiAutomationConnection); 2.创建AppIication对象 Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; 3.启动当前进程的ContentProvider并调用其onCreate方法 List<ProviderInfo> providers = data.providers; if (providers ! = null) { installContentProviders(app, providers); // For process that contains content providers, we want to // ensure that the JIT is enabled "at some point". mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); } installContentProviders完成了ContentProvider的启动工作,它的实现如下所示。首先会遍历当前进程的ProviderInfo的列表并一一调用调用installProvider方法来启动它们,接着将已经启动的ContentProvider发布到AMS中,AMS会把它们存储在ProviderMap中,这样一来外部调用者就可以直接从AMS中获取ContentProvider了。 private void installContentProviders( Context context, List<ProviderInfo> providers) { final ArrayList<IActivityManager.ContentProviderHolder> results = new ArrayList<IActivityManager.ContentProviderHolder>(); 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()); } IActivityManager.ContentProviderHolder cph = installProvider (context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); if (cph ! = null) { cph.noReleaseNeeded = true; results.add(cph); } } try { ActivityManagerNative.getDefault().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { } } 下面看一下ContentProvider对象的创建过程,在installProvider方法中有下面一段代码,其通过类加载器完成了ContentProvider对象的创建: 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. localProvider.attachInfo(c, info); 在上述代码中,除了完成ContentProvider对象的创建,还会通过ContentProvider的attachInfo方法来调用它的onCreate方法,如下所示。 private void attachInfo(Context context, ProviderInfo info, boolean testing) { ... if (mContext == null) { mContext = context; if (context ! = null) { mTransport.mAppOpsManager = (AppOpsManager) context.getSystem- Service( Context.APP_OPS_SERVICE); } mMyUid = Process.myUid(); ... ContentProvider.this.onCreate(); } } 到此为止,ContentProvider已经被创建并且其onCreate方法也已经被调用,这意味着ContentProvider已经启动完成了。 4.调用AppIication的onCreate方法 try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (! mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } 经过上面的四个步骤,ContentProvider已经成功启动,并且其所在进程的Application也已经启动,这意味着ContentProvider所在的进程已经完成了整个的启动过程,然后其他应用就可以通过AMS来访问这个ContentProvider了。拿到了ContentProvider以后,就可以通过它所提供的接口方法来访问它了。需要注意的是,这里的ContentProvider并不是原始的ContentProvider,而是ContentProvider的Binder类型的对象IContentProvider, IContentProvider的具体实现是ContentProviderNative和ContentProvider.Transport,其中ContentProvider.Transport继承了ContentProviderNative。这里仍然选择query方法,首先其他应用会通过AMS获取到ContentProvider的Binder对象即IContentProvider,而IContentProvider的实现者实际上是ContentProvider.Transport。因此其他应用调用IContentProvider的query方法时最终会以进程间通信的方式调用到ContentProvider. Transport的query方法,它的实现如下所示。 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) ! = AppOpsManager.MODE_ ALLOWED) { return rejectQuery(uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport(cancellationSignal)); } final String original = setCallingPackage(callingPkg); try { return ContentProvider.this.query( uri, projection, selection, selectionArgs, sortOrder, CancellationSignal.fromTransport(cancellationSignal)); } finally { setCallingPackage(original); } } 很显然,ContentProvider.Transport的query方法调用了ContentProvider的query方法,query方法的执行结果再通过Binder返回给调用者,这样一来整个调用过程就完成了。除了query方法,insert、delete和update方法也是类似的,这里就不再分析了。