ContentProvider 原理


一、数据访问者

ContentProvider 提供了一种向外部进程暴露数据的通用方案,在进程中,数据任意存储方式,数据库、Sharedpreferences、xml、文件、网络。
外部进程通过统一的接口实现对进程的数据访问,数据提供者和访问者解耦,不用关心数据的存储方式。
我们在使用它时,比如向某个进程ContentProvider组件插入数据,在Activity组件中定义如下代码。

ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://xxx/xxx");
ContentValues values = new ContentValues();
values.put("name", "Demo");
Uri returnuir = contentResolver.insert(uri, values);

通过 Activity 组件调用 getContentResolver() 方法,获取一个 ContentResolver 访问者,ContentResolver 是抽象类。
统一资源标识符 Uri,匹配 ContentProvider,调用 ContentResolver 访问者的增删改查方法,访问者的 #insert() 方法实现插入。

@Override
public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
}

Activity 组件 Wrapper 类中,调用 ContextImpl 类的 getContentResolver() 方法。

@Override
public ContentResolver getContentResolver() {
    return mContentResolver;
}

ContextImpl 构造方法,创建 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() 方法。

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() 方法。

二、IContentProvider 代理获取

数据访问者获取 IContentProvider 代理流程

查找 IContentProvider,子类 ApplicationContentResolver 实现方法。

public final IContentProvider acquireProvider(Uri uri) {
    //SCHEME_CONTENT是"content"
    if (!SCHEME_CONTENT.equals(uri.getScheme())) {
        return null;
    }
    final String auth = uri.getAuthority();
    if (auth != null) {
        //触发子类实现的acquireProvider方法
        return acquireProvider(mContext, auth);
    }
    return null;
}

统一资源标识符,协议 content://,授权信息域名,是 ContentProvider 唯一的标识符,目录即是表名,文件,具体的某项记录ID

协议://域名/目录/文件#片段标示符。

ContentProvider 的 协议是 content,getAuthority() 方法,Uri 的 authorities 字符串,通过 UriMatcher 的 addURI 添加需匹配的 Uri,包括 authority,存储路径或者表名,以及匹配码。在 UriMatcher #match() 方法,若传入 Uri 匹配,返回匹配码。

protected IContentProvider acquireProvider(Context context, String auth) {
    return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
}

调用 ActivityThread 类 acquireProvider() 方法,入参是解析 Uri 得到的 authority 字符。

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;
}

acquireExistingProvider() 方法,根据 auth 和 userId 创建一个 ProviderKey,从本进程 ArrayMap 中查找 ProviderClientRecord 类,内部 IContentProvider。

// ActivityThread 类内部 ProviderMap,
// 保存本地 ProviderClientRecord。
ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap。

当本地 IContentProvider 不存在时,访问 Ams 服务 getContentProvider() 方法,请求 Ams 启动 ContentProvider,返回IActivityManager.ContentProviderHolder,IActivityManager 接口中定义的 Parcelable 类型,封装 IContentProvider。

@Override
public final ContentProviderHolder getContentProvider(
        IApplicationThread caller, String name, int userId, boolean stable) {
    //IApplicationThread是空抛出异常
    return getContentProviderImpl(caller, name, null, stable, userId);
}

检查 ContentProvider 组件的进程,如果 ProcessRecord 是空,进程不存在,startProcessLocked() 方法,创建进程。

final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();

ProcessRecord proc = getProcessRecordLocked(
        cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null) {
     //ContentProvider所在进程存在,但是还发布到 Ams。
    if (!proc.pubProviders.containsKey(cpi.name)) {
        //加入ContentProviderRecord记录,同时让组件进程启动
        proc.pubProviders.put(cpi.name, cpr);
        try {
            proc.thread.scheduleInstallProvider(cpi);
        } catch (RemoteException e) {
        }
    }
} else {
    //ContentProvider所在进程还不存在,创建
    proc = startProcessLocked(cpi.processName,
            cpr.appInfo, false, 0, "content provider",
            new ComponentName(cpi.applicationInfo.packageName,
                    cpi.name), false, false, false);
    if (proc == null) {
        return null;
    }
}

进程存在,但 ProcessRecord 内部 Map 没有保存 ContentProviderRecord,回调 App scheduleInstallProvider() 方法,在 ContentProviders 组件进程,启动 ContentProvider。

public void handleInstallProvider(ProviderInfo info) {
    try {
        installContentProviders(mInitialApplication, Lists.newArrayList(info));
    } finally {
    }
}

遍历 ProviderInfo, installProvider() 方法,生成 ContentProviderHolder。

private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
        new ArrayList<IActivityManager.ContentProviderHolder>();

    for (ProviderInfo cpi : providers) {
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }
    try {
        ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
    } catch (RemoteException ex) {
    }
}

将 ContentProviderHolder list,通知 Ams,publishContentProviders() 方法,将 远程 ContentProviderRecord 记录更新 ContentProviderHolder 中的 IContentProvider。

本地 installProvider() 方法,初始化 ContentProvider,将数据访问到业务接口 IContentProvider 注册到 Ams。

ContentProvider localProvider = null;
IContentProvider provider;

try {
    final java.lang.ClassLoader cl = c.getClassLoader();
    LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
    if (packageInfo == null) {
        // System startup case.
        packageInfo = getSystemContext().mPackageInfo;
    }
    //通过 info 的 class name, newInstance() 创建,
    localProvider = packageInfo.getAppFactory()
            .instantiateProvider(cl, info.name);
    provider = localProvider.getIContentProvider();
    localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) { 
}

创建 ContentProvider 对象 localProvider,内部类 Transport ,即 IContentProvider。
attachInfo() 方法,初始化内部 Context,触发组件 onCreate() 生命周期方法。
将 IContentProvider 赋值给 ContentProviderHolder。
ProviderClientRecord 记录,封装 ContentProviderHolder,IContentProvider 和 ContentProvider。

最终,Ams 服务 getContentProviderImpl() 方法,返回 ContentProviderHolder 对象。
通过 ActivityThread 的 installProvider() 方法,(这一次是访问者进程调用,不是 ContentProvider 组件进程),不会执行 onCreate()方法。
本地进程创建 ProviderClientRecord 记录保存 ArrayMap,下次可以直接从本地获取 IContentProvider,即 acquireExistingProvider() 方法 ,更新 Ref 计数。

三、通信原理

数据提供者进程,当 Ams 服务发起 scheduleInstallProvider() 方法时,在 ActivityThread 类 installProvider() 方法,创建 ContentProvider 对象,执行 onCreate() 方法,ContentProvider 组件即完成启动,且 将 IContentProvider 发布到 Ams 服务。

访问者进程,通过 IContentProvider 的数据操作方法实现跨进程组件请求,底层 binder 通信,IContentProvider 是业务接口。
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);

从 Ams 获取的 IContentProvider 是一个 binder 代理,ContentProvider 组件内部引用一个 Transport 对象。

private Transport mTransport = new Transport();

ContentProvider 内部类,继承 ContentProviderNative,是通信的服务端。IContentProvider 具体数据操作业务。

class Transport extends ContentProviderNative {
}

abstract public class ContentProviderNative extends Binder implements IContentProvider {
}

访问者进程触发从 Ams 服务返回的 IContentProvider 的 insert() 方法时,在数据提供者进程,触发 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);
    }
}

进入我们重写 ContentProvider 子类的 insert() 方法,实现数据写入,自由选择数据持久化方案,其他的数据操作流程和 insert 相同。

四、总结

访问者,获取 ContentResolver 抽象类的实现对象,调用它的数据操作方法。

根据 Url,查找 IContentProvider,先本地 Map,找到 ProviderClientRecord,取内部 IContentProvider。

未找到,Ams 服务查询,返回 ContentProviderHolder,内部 IContentProvider,且创建一个 ProviderClientRecord 封装 IContentProvider 本地。

Ams 查询时,若未发布或进程未创建,提供者进程启动,组件创建,生命周期 执行,发布到 Ams。

数据通信原理是利用 binder 机制,访问者从 Ams 获取的 IContentProvider 即是业务接口代理对象。

提供者进程 ContentProvider 组件实现增删查改方法,内部 Transport 是业务接口服务端,接收到请求时, ContentProvider 组件对应实现方法。


任重而道远

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容