ContentProvider启动流程及原理
ContentProvider作为Android的四大组件之一是非常重要的,它的用法参考ContentProvider的用法。本篇主要顺着源码理解ContentProvider的启动和底层原理。
1、ContentProvider启动
当一个APP启动的时候从ActivityThread的main方法入口加载的过程中就会初始化本应用的ContentProvider。主要流程如下:
应用启动的时候会从ActivityThread的入口:
public static void main(String[] args) {
... ...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
}接着里面通过IActivityManager AIDL接口和系统服务对接。在ActivityManagerService中经过一系列调用链:attachApplication->attachApplicationLocked->handleAppDiedLocked。
到了IApplicationThread AIDL接口,而IApplicationThread 的实现正是ActivityThread,于是再回到APP层:bindApplication->mH->handleBindApplication->installContentProviders->installProvider
通过LoadedApk去加载实例化ContentProvider。
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
...
try {
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
// System startup case.
packageInfo = getSystemContext().mPackageInfo;
}
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
if (!mInstrumentation.onException(null, e)) {
throw new RuntimeException(
"Unable to get provider " + info.name
+ ": " + e.toString(), e);
}
return null;
}
...
}方法最后调用ContentProvide的dattachInfo(Context context, ProviderInfo info)方法:
localProvider.attachInfo(c, info);最后调用ContentProvider的onCreate()方法,在这里初始化应用的ContentProvider,执行必要的初始化操作,比如LeakCanary库通过ContentProvider巧妙的实现了监听接口的注册。
private void attachInfo(Context context, ProviderInfo info, boolean 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) {
...
ContentProvider.this.onCreate();
}
}2、ContentProvider增删改查
ContentProvider的增删改查主要借助ContentResolver完成。增删改查流程基本差不多,这里只捋顺使用ContentProvider插入方法insert()的流程。
2.1、获取ContentResolver
调用getContentResolver()方法来获取ContentResolver执行增删改查。
getContentResolver().insert(Uri url,ContentValues values)public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}mBase已经比较熟悉了,它的实现类是ContextImpl
public ContentResolver getContentResolver() {
return mContentResolver;
}ContextImpl在创建的时候,构造函数中会初始化自己的成员变量mContentResolver
private ContextImpl(...) {
...
mContentResolver = new ApplicationContentResolver(this, mainThread);
}ApplicationContentResolver类是定义在ContextImpl中的静态内部类,继承自ContentResolver。
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
public ApplicationContentResolver(Context context, ActivityThread mainThread) {
super(context);
mMainThread = Preconditions.checkNotNull(mainThread);
}
...
}2.2、insert()
好了,到现在已经初始化完成需要的ContentResolver对象。继续看看它的insert()方法如何实现的。
public final Uri insert(Uri url,ContentValues values) {
Preconditions.checkNotNull(url, "url");
try {
if (mWrapped != null) return mWrapped.insert(url, values);
} catch (RemoteException e) {
return null;
}
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
releaseProvider(provider);
}
}2.3、获取ContentProvider
里面通过acquireProvider(Uri uri)方法获取到对应的ContentProvider。
public final IContentProvider acquireProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
if (auth != null) {
return acquireProvider(mContext, auth);
}
return null;
}接着又调用了acquireProvider(Context c, String name)方法,这个方法在ContentProvider中是抽象方法:
protected abstract IContentProvider acquireProvider(Context c, String name);该方法的实现就是前面已经提到的静态内部类ApplicationContentResolver。
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}接着到了当前APP运行的ActivityThread,经过一系列的调用链最终初始化本应用的ContentProvider。
IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable)
...
}2.4、ContentProvier insert()
再回到ContentResolve的insert()方法中,获取到IContentProvider对象实例之后,调用执行插入操作:
Uri createdRow = provider.insert(mPackageName, url, values);IContentProvider接口的实现类是ContentProviderNative ,但是ContentProviderNative仍然是抽象类,它的最终实现在IContentProvider中定义的IContentProvider.Transport类。
内部通过IBinder实现进程间通信,Binder继承了IBinder的接口。也就是ContentProvider可以提供跨进程操作的原因。
@UnsupportedAppUsage
private IBinder mRemote;所有的增删改查操作都通过这个mRemote IBinder对象执行。
public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
{
...
mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
...
}其它应用调用增删改查,通过IContentProvider->ContentProviderNative:IBinder->ContentProvier.Transport最终在本应用进程执行。ContentProvier.Transport中的insert()方法:
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
...
try {
return maybeAddUserId(mInterface.insert(uri, initialValues), userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
setCallingPackage(original);
Trace.traceEnd(TRACE_TAG_DATABASE);
}
}mInterface是ContentInterface接口类型,就是当前ContentProvider。
class Transport extends ContentProviderNative {
...
volatile ContentInterface mInterface = ContentProvider.this;
}另外发现,处理单个的增删改查方法外,还有批量的方法。比如bulkInsert
@Override
public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
...
}推荐博客:


