ContentProvider
是Android四大组件之一,它的主要作用是进程间共享数据。Android中的数据存储方式主要有以下几种:网络存储、文件存储(SharedPreferences
属于文件的一种)、数据库。大多数情况下这些数据存储操作都是在同一进程中进行,但如果要数据和文件在不同进程间共享就比较复杂,而ContentProvider
正好擅长这个,所以在多进程之间共享数据的最好方式就是通过ContentProvider
来实现。
1、ContentProvider的使用
ContentProvider
是个抽象类,需要一个自定义类来实现其中的抽象方法,如下:
public class MyContentProvider extends ContentProvider {
private static final String TAG = "MyContentProvider";
//ContentProvider中的抽象方法,需要在子类实现
@Override
public boolean onCreate() {
return false;
}
//ContentProvider通过反射创建对象成功后第一个调用的方法
@Override
public void attachInfo(Context context, ProviderInfo info) {
//在父类中调用了onCreate方法
super.attachInfo(context, info);
}
//数据查询操作,如果ContentProvider在主进程中创建则该操作在主线程中执行,非线程安全
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
//返回当前 Url所代表数据的MIME类型
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
//数据插入操作,如果ContentProvider在主进程中创建则该操作在主线程中执行,非线程安全
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
//数据删除操作,如果ContentProvider在主进程中创建则该操作在主线程中执行,非线程安全
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
//数据更新操作,如果ContentProvider在主进程中创建则该操作在主线程中执行,非线程安全
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
复制代码
仅仅一个自定义类还不够,ContentProvider
与activity
、service
一样,需要在AndroidManifest.xml文件中进行配置。
<provider
android:name=".MyContentProvider"
android:authorities="com.example.content.provider"
android:multiprocess="false"
android:process=":remote"
android:exported="true"/>
复制代码
配置参数还是蛮多的,但是我们只需要关注multiprocess
、process
及exported
这三个参数即可(其他参数可以参考ContentProvider简介这篇文章)。exported
为true则表示允许其他应用访问应用中的ContentProvider
(跨应用访问),默认为false。process
表示ContentProvider
所在的进程。multiprocess
为true表示每个调用者进程都会创建一个ContentProvider
实例,默认为false。当multiprocess
、process
这两个参数结合起来就有点意思,会产生以下几种情况。
- android:process=":remote"、android:multiprocess="true" ,
ContentProvider
不会随应用的启动而加载,当调用ContentProvider
的时候才会加载,并且ContentProvider
是在调用者的进程中初始化。这时候可能定义ContentProvider
的remote
进程还没有启动。 -android:process=":remote"、android:multiprocess="false"(默认情况) ,ContentProvider
不会随应用的启动而加载,当调用到ContentProvider
的时候才会加载,并且ContentProvider
是在“remote”进程中初始化。 - android:multiprocess="true",
ContentProvider
会随着应用的启动而加载,并且ContentProvider
是在应用进程的主线程中初始化的。当被调用时会在调用者进程中实例化一个ContentProvider
对象。 - android:multiprocess="false"(默认情况),
ContentProvider
会随着应用的启动而加载,并且ContentProvider
是在应用主进程的主线程中初始化的。这种ContentProvider
只有一个实例,运行在自己App的进程中。所有调用者共享该ContentProvider
实例,调用者与ContentProvider
实例位于两个不同的进程。
ContentProvider
创建成功后,使用起来还是比较简单,首先获得一个ContentResolver
对象,再对该对象的crud操作即可。
//拿到访问的uri
Uri uri_user = Uri.parse("content://com.example.content.provider");
ContentResolver resolver = getContentResolver();
//通过URI来插入数据
resolver.insert(uri_user, ...);
//通过URI来查询数据
resolver.query(uri_user,...)
//通过URI来更新数据
resolver.update(uri_user,...)
//通过URI来删除数据
resolver.delete(uri_user,...)
复制代码
总体上来说,ContentProvider
的使用还是蛮简单的,主要在AndroidManifest.xml中对ContentProvider
进行参数配置时要注意一些。
2、ContentProvider的工作流程
前面说不设置process
时,ContentProvider
则会随着应用的启动而加载、初始化,反之则会在调用时进行加载、初始化,先来看一下ContentProvider
随着应用的启动而加载、初始化的流程。
2.1、ContentProvider随应用启动而初始化的工作流程
Android源码分析之Activity启动流程这篇文章说了Application
实例是在ActivityThread
的handleBindApplication
方法中创建。在讲解这个方法时疏漏了一点,那就是ContentProvider
会在这个方法中创建。
private void handleBindApplication(AppBindData data) {
...
try {
//通过反射创建Application实例
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
if (!data.restrictedBackupMode) {
//如果有ContentProvider,则创建
if (!ArrayUtils.isEmpty(data.providers)) {
//创建ContentProvider实例
installContentProviders(app, data.providers);
}
}
try {
//调用Instrumentation的onCreate方法
mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception e) {
...
}
try {
//调用Application的onCreate方法
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
...
}
} finally {
...
}
// 预加载字体资源
...
}
复制代码
上面简化了大量代码,但重要部分还在。可以看到installContentProviders
在Application
的onCreate
之前调用,所以可以得出结论:ContentProvider
的onCreate
在Application
的onCreate
之前调用。 下面来看installContentProviders
方法的实现。
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
//遍历所有需要随应用启动的ContentProvider
for (ProviderInfo cpi : providers) {
...
//创建ContentProvider实例
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) {
throw ex.rethrowFromSystemServer();
}
}
复制代码
installContentProviders
方法主要是创建ContentProvider
实例并在AMS中发布。在调用installProvider
方法时传入的holder为null,所以就会在installProvider
中创建ContentProvider
实例并加入HashMap中进行缓存。
//创建ContentProvider实例
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
...
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
//在应用主进程中创建ContentProvider实例
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
//在单独进程中创建ContentProvider实例,
c = mInitialApplication;
} else {
...
}
...
try {
//拿到类加载器
final java.lang.ClassLoader cl = c.getClassLoader();
//通过反射创建ContentProvider实例
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
//拿到ContentProvider对应的IContentProvider接口
provider = localProvider.getIContentProvider();
//ContentProvider实例创建失败
if (provider == null) {
...
return null;
}
// 调用ContentProvider的attachInfo方法,在该方法里会调用ContentProvider的onCreate方法
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
...
return null;
}
} else {
provider = holder.provider;
...
}
ContentProviderHolder retHolder;
synchronized (mProviderMap) {
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
if (pr != null) {
provider = pr.mProvider;
} else {
//创建ContentProviderHolder实例
holder = new ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
//添加ContentProvider信息到mProviderMap
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
retHolder = pr.mHolder;
} else {
...
}
}
return retHolder;
}
复制代码
installProviderAuthoritiesLocked
方法主要是创建一个ProviderClientRecord
对象来记录ContentProvider
信息并存入mProviderMap这个HashMap中以备下次获取,在后面会提到mProviderMap。 关于ContentProvider
随着应用的启动而加载、初始化的流程到这里就结束了。下面就来看使用ContentProvider
的工作流程。
2.2、ContentProvider在使用时初始化的工作流程
前面讲过如何使用ContentProvider
,所以这里以insert
为例,来看ContentResolver
的insert
方法。
public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@Nullable ContentValues values) {
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
...
//进行数据插入操作
Uri createdRow = provider.insert(mPackageName, url, values);
...
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
//释放引用
releaseProvider(provider);
}
}
复制代码
首先调用acquireProvider
方法获取一个IContentProvider
对象引用,而该方法是一个抽象方法,需要在子类实现,经查询,发现它的实现是在ApplicationContentResolver
类中,该类是ContextImpl
的一个静态内部类,来看这个类的实现。
private static final class ApplicationContentResolver extends ContentResolver {
...
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
...
}
复制代码
经查询发现mMainThread
就是ActivityThread
的实例,下面就来看ActivityThread
中acquireProvider
方法的实现。
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
//从缓存中获取ContentProvider实例对象
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
ContentProviderHolder holder = null;
try {
//当缓存中没有ContentProvider示例时,需要通过AMS来创建一个ContentProvider示例
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
if (holder == null) {
//通过AMS创建ContentProvider对象失败
return null;
}
//由于这里的holder不为null,所以在这里调用该方法主要是为了增加或减少计数引用
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
复制代码
首先会从acquireExistingProvider
中去查找ContentProvider
对象,如果不存在才会调用AMS来创建。
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
//ProviderKey的equals与hashCode方法被被重新实现
final ProviderKey key = new ProviderKey(auth, userId);
//从mProviderMap中获取ContentProvider信息
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
//ContentProvider所在进程被系统杀死
handleUnstableProviderDiedLocked(jBinder, true);
return null;
}
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
//当stable为true时则增加计数引用
incProviderRefLocked(prc, stable);
}
return provider;
}
}
复制代码
前面讲解installProvider
时说过ContenProvider
实例创建成功后会将ProviderClientRecord
信息保存在mProviderMap这个HashMap中,而这里就是直接从mProviderMap中获取ContenProvider
信息。 回到acquireProvider
方法。如果从acquireExistingProvider
中获取的对象为null,那么就得通过AMS中的getContentProvider
方法来创建,来看一下该方法的实现。
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
...
return getContentProviderImpl(caller, name, null, stable, userId);
}
//具体创建ContentProvider实例的方法
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
//分段锁
synchronized(this) {
ProcessRecord r = null;
...
// 首先检查该ContentProviders是否已经发布
cpr = mProviderMap.getProviderByName(name, userId);
...
//判断ContentProvider是否在运行
boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;
//ContentProvider已经在运行
if (providerRunning) {
cpi = cpr.info;
String msg;
//权限检查
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
!= null) {
//没有权限则报错
throw new SecurityException(msg);
}
if (r != null && cpr.canRunHere(r)) {
//此 ContentProvider已发布或正在发布...但它也允许在调用者的进程中运行,因此不要建立连接,只是让调用者实例化自己的实例。
//创建一个ContentProviderHolder对象
ContentProviderHolder holder = cpr.newHolder(null);
//不给调用者提供者对象,它需要自己创建,
holder.provider = null;
return holder;
}
...
//获取ContentProviderConnection对象,它继承与Binder,主要作用是连接客户端与ContentProvider
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
updateLruProcessLocked(cpr.proc, false, null);
}
}
final int verifiedAdj = cpr.proc.verifiedAdj;
//更新进程的adj值,该值非常重要,值越大越容易被系统回收,系统进程的adj值基本上都小于0
boolean success = updateOomAdjLocked(cpr.proc, true);
//检车adj值是否更新成功,可能存在更新失败的可能
if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {
success = false;
}
...
if (!success) {
//ContentProvider所在进程已被杀死,做一些清理数据的操作
appDiedLocked(cpr.proc);
if (!lastRef) {
// This wasn't the last ref our process had on
// the provider... we have now been killed, bail.
return null;
}
providerRunning = false;
conn = null;
} else {
cpr.proc.verifiedAdj = cpr.proc.setAdj;
}
...
}
//ContentProvider没有运行运行或者未创建
if (!providerRunning) {
...
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
!= null) {
//未获取权限
throw new SecurityException(msg);
}
//如果ContentProvider未在系统进程中运行,并且系统尚未准备好运行其他进程,则快速失败而不是挂起。
if (!mProcessesReady
&& !cpi.processName.equals("system")) {
throw new IllegalArgumentException(
"Attempt to launch content provider before system ready");
}
//确保开启ContentProvider的应用再运行,否则返回null
if (!mUserController.isUserRunningLocked(userId, 0)) {
return null;
}
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
//检查该ContentProviders是否已经发布
cpr = mProviderMap.getProviderByClass(comp, userId);
final boolean firstClass = cpr == null;
if (firstClass) {
...
try {
...
ai = getAppInfoForUser(ai, userId);
//创建ContentProviderRecord对象
cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
} finally {
Binder.restoreCallingIdentity(ident);
}
}
if (r != null && cpr.canRunHere(r)) {
//如果这是一个多进程ContentProvider,那么只需返回其信息并允许调用者实例化它。 只有在ContentProvider与调用者进程的用户相同时才执行此操作,或者可以以root身份运行(因此可以在任何进程中运行)。
//当android:multiprocess="true"时会走这里
return cpr.newHolder(null);
}
//从待启动的ContentProvider查找要启动的ContentProvider
final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
}
//如果ContentProvider尚未启动,则启动它。
if (i >= N) {
try {
//如果ContentProvider所在进程已存在则直接启动
//获取进程信息
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null && !proc.killed) {
if (!proc.pubProviders.containsKey(cpi.name)) {
proc.pubProviders.put(cpi.name, cpr);
try {
//通过ActivityThread启动ContentProvider
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;
}
}
cpr.launchingApp = proc;
//添加到正在启动的集合中
mLaunchingProviders.add(cpr);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
if (firstClass) {
//如果是第一次的话则需要存储信息,根据ComponentName来保存信息
mProviderMap.putProviderByClass(comp, cpr);
}
//保存ContentProvider信息,根据名称保存
mProviderMap.putProviderByName(name, cpr);
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null) {
//需要等待
conn.waiting = true;
}
}
...
}
// 等待ContentProvider的发布,如果未发布成功则会一直在这里阻塞
...
return cpr != null ? cpr.newHolder(conn) : null;
}
复制代码
上面关于AMS如何创建ContentProviderHolder
做了详细的介绍,主要分为ContentProvider
是否正在运行这两种情况,如果在运行就会提高ContentProvider
所在进程的优先级并创建一个ContentProviderConnection
对象。如果未运行则又分为ContentProvider
所在进程是否存在的两种情况。如果ContentProvider
进程已存在则调用ActivityThread
的scheduleInstallProvider
方法。
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider);
}
//handler里会调用下面的方法
public void handleInstallProvider(ProviderInfo info) {
final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
installContentProviders(mInitialApplication, Lists.newArrayList(info));
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
复制代码
可以发现在handleInstallProvider
里也调用了installContentProviders
这个方法,该方法在前面就有讲解,这里就不在讲解了。如果ContentProvider
进程不存在则创建一个新的进程。创建新进程的流程跟应用的启动流程一样,会创建Application
对象,调用installContentProviders
方法,具体流程在前面也讲解过,这里就不在过多叙述。 再次回到ActivityThread
中acquireProvider
方法,当通过AMS获得ContentProviderHolder
对象后就会调用installProvider
方法,关于该方法,前面讲了一些,这里就主要讲剩下的一些东西。
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
//传入的holder及holder.provider不会为null
if (holder == null || holder.provider == null) {
...
} else {
//拿到创建的ContentProvider对象
provider = holder.provider;
}
ContentProviderHolder retHolder;
synchronized (mProviderMap) {
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
...
} else {
//主要是增加或减少引用计数,
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManager.getService().removeContentProvider(
holder.connection, stable);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
}
}
} else {
ProviderClientRecord client = installProviderAuthoritiesLocked(
provider, localProvider, holder);
if (noReleaseNeeded) {
prc = new ProviderRefCount(holder, client, 1000, 1000);
} else {
prc = stable
? new ProviderRefCount(holder, client, 1, 0)
: new ProviderRefCount(holder, client, 0, 1);
}
mProviderRefCountMap.put(jBinder, prc);
}
retHolder = prc.holder;
}
}
return retHolder;
}
复制代码
主要是做了一个引用计数操作,当stable和unstable引用计数都为0时则移除connection信息。
2.3、inset操作的实现
前面基本上就把ContentResolver
中的acquireProvider
讲解完毕,最后该方法返回了一个IContentProvider
对象,它的实现是ContentProvider
中的Transport
类。
class Transport extends ContentProviderNative {
...
@Override
public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
...
try {
return ContentProvider.this.query(
uri, projection, queryArgs,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingPackage(original);
}
}
@Override
public String getType(Uri uri) {
...
return ContentProvider.this.getType(uri);
}
@Override
public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
...
try {
return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
} finally {
setCallingPackage(original);
}
}
...
@Override
public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
...
try {
return ContentProvider.this.delete(uri, selection, selectionArgs);
} finally {
setCallingPackage(original);
}
}
@Override
public int update(String callingPkg, Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
...
try {
return ContentProvider.this.update(uri, values, selection, selectionArgs);
} finally {
setCallingPackage(original);
}
}
...
}
复制代码
可以发现Transport
中的crud操作就是直接对ContentProvider
进行crud操作,而Transport
又能够通过Binder进行进程间通信。 到此就把ContentProvider
的工作流程梳理完毕了。
3、总结
前面两节主要讲解了ContentProvider
的使用、ContentProvider
的创建及示例insert
方法的具体实现。下面就总结以下几点。
- 当不设置
android:process=":remote"
时,ContentProvider
会随着应用的启动而初始化,此时ContentProvider
的onCreate
方法会在Application
的onCreate
之前调用。当设置时,ContentProvider
会在第一次使用时初始化。 - 当设置
android:multiprocess="true"
时,会在每个调用者进程创建一个ContentProvide
实例。其设置的android:process=":remote"
属性也就无效了 - 如果
ContentProvider
在应用主进程创建则crud也在主线程中进程,因为并没有开启子线程,在ContentProvider
创建时。
【参考资料】 《Android艺术探索》 [深入理解Android卷二 全文-第七章]深入理解ContentProvider 从源码角度看ContentProvider Android ContentProvider 多进程multiprocess 详解 ContentProvider简介 Android:关于ContentProvider的知识都在这里了! ContentProvider 引发闪退之谜