ContentProvider原理
ContentProvider提供了一种向外部进程暴露数据的方式。在数据提供者进程中,数据可以是数据库,也可以是sharedpreferences,文件或xml存储方式。
外部进程通过ContentProvider统一的数据访问接口实现访问,解耦,并且不需要关心具体存储方式。
数据提供者ContentProvider
数据操作的四种方法:增删查改。
ContentProvider是抽象类,子类需要实现数据操作增删查改方法。
//增
public abstract Uri insert(Uri uri, ContentValues values);
//删
public abstract int delete(Uri uri, String selection,
String[] selectionArgs);
//查
public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs,
String sortOrder);
//改
public abstract int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs);
以数据库为例,ContentProvider子类实现四个方法就是数据提供者进程sqlite的操作,通过Uri访问。
数据访问者ContentResolver
Activity提供了获取ContentResolver的getContentResolver方法。
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
//ContextImpl#getContentResolver方法。
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
在构造方法中初始化ContentResolver。
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, ...) {
mOuterContext = this;
mMainThread = mainThread;
...
if (user == null) {
user = Process.myUserHandle();
}
....
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
ApplicationContentResolver继承ContentResolver抽象类,实现方法是acquireProvider。
当在进程中访问ContentResolver的增删查改方法时,通过传入的Uri定位到ContentProvider。下面是ContentResolver#insert方法插入数据。
public final Uri insert(Uri url, ContentValues values) {
//先ContentResolver的final方法
IContentProvider provider = acquireProvider(url);
//provider为空说明Uri无法识别,抛出异常
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
releaseProvider(provider);
}
}
首先acquireProvider方法获取到IContentProvider,然后触发insert插入数据,返回成功插入的index,最后释放。
ApplicationContentResolver实现了acquireProvider方法。
public final IContentProvider acquireProvider(Uri uri) {
if (!SCHEME_CONTENT.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
if (auth != null) {
//触发子类实现的acquireProvider方法
return acquireProvider(mContext, auth);
}
return null;
}
若统一资源标识符协议不是content,不符合Uri,返回空。getAuthority获取的解析Uri得到的authorities字符串。协议://域名/目录/文件#片段标示符,在数据源ContentProvider中,通过UriMatcher的addURI添加需匹配的Uri,包括authority,存储路径或者表名,以及匹配码。在触发UriMatcher#match方法时,如果传入Uri匹配,则返回匹配码。
子类acquireProvider方法触发ActivityThread的acquireProvider方法。
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
ActivityThread#acquireProvider方法。
acquireExistingProvider根据auth和userId查找本地保存的IContentProvider,直接返回。
//存储本地IContentProvider
//userId和auth代表ProviderKey,ProviderClientRecord封装IContentProvider。
ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap。
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;
}
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
}
if (holder == null) {
//无法找到auth对应的provider
return null;
}
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
若本地不存在,委托给Ams#getContentProvider获取。
数据通信原理
IContentProvider是一个Binder代理,从Ams获得。当触发IContentProvider的数据插入方法时,数据提供者进程端触发Transport的insert方法。
Transport是ContentProvider内部类,继承ContentProviderNative,实现IContentProvider具体的数据操作业务。下面是数据插入Transport#insert方法。
@Override
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
validateIncomingUri(uri);
int userId = getUserIdFromUri(uri);
uri = getUriWithoutUserId(uri);
if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
}
final String original = setCallingPackage(callingPkg);
try {
//重写的ContentProvider的insert方法
return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
} finally {
setCallingPackage(original);
}
}
通过Transport的insert方法会进入我们重写的ContentProvider子类的insert,从而达到控制数据插入的目的。
ContentProvider实现进程数据共享的底层逻辑基于Binder通通信机制。
任重而道远