ContentProvider组件原理


一、数据访问者

ContentProvider组件是Android四大组件之一,它的使用频率低于前三者,但是也非常重要。它提供了一种组件进程向外部进程暴露数据的通用方案。在该组件进程,数据存储方式可以是数据库、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;
}

ContentResolver对象在它的ContextImpl内部,它的构造方法初始化。

private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, ...) {
    mOuterContext = this;
    mMainThread = mainThread;
    if (user == null) {
        user = Process.myUserHandle();
    }
    mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}

原来,我们获取的ContentResolver是一个ApplicationContentResolver对象,继承了ContentResolver抽象类,实现acquireProvider方法。下面是调用它的插入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);
    }
}

先查找IContentProvider,然后具体调用它的insert方法。最后会释放掉IContentProvider。
下面分析一下查找IContentProvider的过程,这是比较重要的部分哈。通过里面调用子类ApplicationContentResolver实现的acquireProvider方法实现。

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,协议://域名/目录/文件#片段标示符。
这里会判断,如果统一资源标识符协议不是content,不符合Uri,返回空。getAuthority方法,获取的解析Uri得到的authorities字符串。在ContentProvider数据提供者中,通过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得到的授权部分信息字符。

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

在ActivityThread的方法中,首先是acquireExistingProvider方法,根据auth和userId创建一个ProviderKey值,在本进程的ArrayMap中查找ProviderClientRecord,若找到返回其内部的IContentProvider。

ArrayMap mProviderMap。

ActivityThread类内部ProviderMap,负责保存本地ProviderClientRecord。
当本地IContentProvider不存在时,访问Ams服务getContentProvider方法,请求Ams服务启动ContentProvider。

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

调用Ams的getContentProviderImpl方法,返回一个ContentProviderHolder,它是在IActivityManager接口中定义的Parcelable类型,封装IContentProvider。有下面一段代码。

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

查看一下ContentProvider组件所在进程是否存在,如果ProcessRecord是空,进程未创建,startProcessLocked方法,创建一个目标ContentProvider组件进程。
进程存在,是否已经发布到Ams
如果进程已存在,且ProcessRecord内部ArrayMap没有保存ContentProviderRecord,回调App进程scheduleXxx方法,启动ContentProvider。如果在ProcessRecord内部ArrayMap查找到进程的ContentProvider已经启动并发布,不需要回调Provider组件的App进程。

final ArrayMap pubProviders = new ArrayMap<>();

回到ContentProviders组件所在App进程,调用installContentProviders方法,启动ContentProvider,遍历ProviderInfo,每一个都调用本地installProvider方法。

private void installContentProviders(
        Context context, List providers) {
    final ArrayList results =
        new ArrayList();

    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 {
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
    }
}

这里调用installProvider方法,传入ContentProviderHolder是空,当它是空时,创建ContentProvider组件对象,获取内部IContentProvider,然后,触发attachInfo方法。attachInfo方法中,初始化内部Context,调用组件onCreate方法。

try {
    final ClassLoader cl = c.getClassLoader();
    localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
    provider = localProvider.getIContentProvider();
    localProvider.attachInfo(c, info);
} catch (Exception e) {
}

方法最后,调用Ams的publishContentProviders方法,将ContentProvider发布到Ams服务,传入的是ContentProviderHolder列表。
发布到Ams服务,根据ContentProviderHolder在ProcessRecord内部的ArrayMap(即pubProviders)中查找ContentProviderRecord记录,将该记录放入Ams的ProviderMap,它存储Ams的ContentProviderRecord。
再次回到ActivityThread的acquireProvider方法。
最终,Ams服务getContentProviderImpl方法返回ContentProviderHolder对象。从Ams服务获取到ContentProviderHolder,调用ActivityThread的installProvider方法,这里调用的该方法传入ContentProviderHolder不是空,不会再执行onCreate方法启动组件,在本地进程创建ProviderClientRecord记录保存ArrayMap,下次可以直接从本地获取IContentProvider,更新Ref计数。
下面是一张数据操作时查找IContentProvider代理的流程图。

ContentProvider组件原理_第1张图片
数据操作时查找IContentProvider代理的流程图.jpg


二、数据通信原理

前面介绍过,在数据提供者所在进程,当Ams服务发起scheduleInstallProvider方法回调时,在ActivityThread的installProvider方法中,创建ContentProvider对象,执行onCreate方法,这样,数据提供者组件就启动起来了,并且会发布到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 {
}

在访问端进程触发从Ams服务返回的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);
    }
}

该方法会进入我们ContentProvider子类重写的insert方法,可以实现数据写入,我们重写该方法,自由选择数据持久化方案。其他的数据操作方法流程和数据插入一样。

三、总结

请求端组件,获取ContentResolver抽象类的实现对象,调用它的增删查改方法操作数据。

根据Url,查找IContentProvider,先本地Map,找到ProviderClientRecord,去取内部IContentProvider。
未找到,Ams查询,返回ContentProviderHolder,内部IContentProvider返回,并创建一个ProviderClientRecord封装IContentProvider存本地。

Ams查询时,若未发布,或进程问创建,先组件提供者进程去启动进程或者Provider组件,然后发布到Ams。

数据共享通信原理采用Binder机制。请求端从Ams获取的IContentProvider就是业务接口代理对象。

组件提供者进程ContentProvider组件实现增删查改方法,内部的Transport会调用他们。


任重而道远

你可能感兴趣的:(ContentProvider组件原理)