我爱编程

ContentProvider原理

2018-04-11  本文已影响55人  gczxbb

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通通信机制。


任重而道远

上一篇下一篇

猜你喜欢

热点阅读