赞
踩
ContentProvider
是Android四大组件之一,它的主要作用是进程间共享数据。Android中的数据存储方式主要有以下几种:网络存储、文件存储(SharedPreferences
属于文件的一种)、数据库。大多数情况下这些数据存储操作都是在同一进程中进行,但如果要数据和文件在不同进程间共享就比较复杂,而ContentProvider
正好擅长这个,所以在多进程之间共享数据的最好方式就是通过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
这两个参数结合起来就有点意思,会产生以下几种情况。
ContentProvider
不会随应用的启动而加载,当调用ContentProvider
的时候才会加载,并且ContentProvider
是在调用者的进程中初始化。这时候可能定义ContentProvider
的remote
进程还没有启动。ContentProvider
不会随应用的启动而加载,当调用到ContentProvider
的时候才会加载,并且ContentProvider
是在“remote”进程中初始化。ContentProvider
会随着应用的启动而加载,并且ContentProvider
是在应用进程的主线程中初始化的。当被调用时会在调用者进程中实例化一个ContentProvider
对象。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
进行参数配置时要注意一些。
前面说不设置process
时,ContentProvider
则会随着应用的启动而加载、初始化,反之则会在调用时进行加载、初始化,先来看一下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
的工作流程。
前面讲过如何使用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信息。
前面基本上就把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
的工作流程梳理完毕了。
前面两节主要讲解了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 引发闪退之谜
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。