赞
踩
前面两篇文章,分别对ContentProvider的基本应用和ContentProvider的启动机制结合源码进行了详细的分析,这一篇对ContentProvider的工作原理进行分析。
源码基于API 28,源码有删减,只保留本专题相关的核心代码,仅供参考。
涉及到的核心类有Candroid.app.ContextImpl、android.content.ContextWrapper、android.content.ContextWrapper、android.content.IContentProvider、android.content.ContentProviderNative
要使用ContentProvider 第一步就是通过Context#getContentResolver()方法来获取ContentResolver对象,本质上是通过android.app.ContextImpl#getContentResolver来获取的。
Context 是一个顶层抽象类,Candroid.app.ContextImpl是它的直接实现类,而ContextWrapper 知识代理android.app.ContextImpl,其中ContextImpl是个非公共类型类。
//android.content.ContextWrapper#getContentResolver,mBase 就是ContextImpl对象
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
android.app.ContextImpl#getContentResolver
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
getContentResolver最终返回的是ContextImpl$ApplicationContentResolver对象。
class ContextImpl extends Context {
...
private final ApplicationContentResolver mContentResolver;
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
@NonNull LoadedApk packageInfo, @Nullable String splitName,
@Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
@Nullable ClassLoader classLoader) {
mOuterContext = this;
mMainThread = mainThread;
mActivityToken = activityToken;
mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
if (container != null) {
mBasePackageName = container.mBasePackageName;
mOpPackageName = container.mOpPackageName;
setResources(container.mResources);
mDisplay = container.mDisplay;
} else {
mBasePackageName = packageInfo.mPackageName;
ApplicationInfo ainfo = packageInfo.getApplicationInfo();
}
mContentResolver = new ApplicationContentResolver(this, mainThread);
}
...
}
ApplicationContentResolver 继承ContentResolver并实现对应的抽象方法,最终由ContentResolver的构造函数初始化和创建。
class ContextImpl extends Context {
...
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
public ApplicationContentResolver(Context context, ActivityThread mainThread) {
super(context);
mMainThread = Preconditions.checkNotNull(mainThread);
}
...
}
android.content.ContentResolver#ContentResolver
public ContentResolver(Context context) {
mContext = context != null ? context : ActivityThread.currentApplication();
mPackageName = mContext.getOpPackageName();
mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
}
所以当调用Context#getContentResolver时,当对应的ContentProvider所在的进程未启动时,首次访问时会触发对应的ContentProvider的创建,而ContentProvider的创建是由所在进程的启动时。无论通过ContentResolver对象首次调用CRUDQ方法时都会自动去启动所在的进程和对应ContentProvider。
ContentResolver对象进行CRUDQ ,本质上就是进行Binder 通信,其他应用通过AMS访问这个ContentProvider。
无论进行CRUDQ哪种操作,首先都是先获取IContentProvider对象,IContentProvider 的具体实现是ContentProviderNative和ContentProvider.Transport类。
/**
* The ipc interface to talk to a content provider.
* @hide
*/
public interface IContentProvider extends IInterface {
public Cursor query(String callingPkg, Uri url, @Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
throws RemoteException;
public String getType(Uri url) throws RemoteException;
public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
throws RemoteException;
public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
throws RemoteException;
public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException;
public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
public ParcelFileDescriptor openFile(
String callingPkg, Uri url, String mode, ICancellationSignal signal,
IBinder callerToken)
throws RemoteException, FileNotFoundException;
public AssetFileDescriptor openAssetFile(
String callingPkg, Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
public Bundle call(
String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras)
throws RemoteException;
public ICancellationSignal createCancellationSignal() throws RemoteException;
public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args,
ICancellationSignal cancellationSignal) throws RemoteException;
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
...
}
最终都是通过ApplicationContentResolver#acquireProvider方法来获取的(因为这个方法在ContentResolver中是抽象的)
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
最终进入到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;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
ContentProviderHolder holder = null;
try {
synchronized (getGetProviderLock(auth, userId)) {
//若未启动,则通过AMS来启动目标ContentProvider进程并启动ContentProvider,
holder = ActivityManager.getService().getContentProvider(
getApplicationThread(), auth, userId, stable);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// Install provider will increment the reference count for us, and break
// any ties in the race. 再通过installProvider来修改引用计数。
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
首先通过android.app.ActivityThread#acquireExistingProvider方法查找之前缓存的目标ContentProvider,存在则直接返回。
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);
return null;
}
// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
incProviderRefLocked(prc, stable);
}
return provider;
}
}
若不存在则通过Binder IPC通信,通过AMS来启动目标ContentProvider进程并启动ContentProvider,再通过installProvider来修改引用计数,进入到com.android.server.am.ActivityManagerService#getContentProvider:
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
...
return getContentProviderImpl(caller, name, null, stable, userId);
}
本质是执行这个com.android.server.am.ActivityManagerService#getContentProviderImpl
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
...
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
synchronized(this) {
// First check if this content provider has been published...
cpr = mProviderMap.getProviderByName(name, userId);
// If that didn't work, check if it exists for user 0 and then
// verify that it's a singleton provider before using it.
if (cpr == null && userId != UserHandle.USER_SYSTEM) {
cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
if (cpr != null) {
cpi = cpr.info;
if (isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
userId = UserHandle.USER_SYSTEM;
checkCrossUser = false;
} else {
cpr = null;
cpi = null;
}
}
}
boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;
if (providerRunning) {
cpi = cpr.info;
String msg;
checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
!= null) {
throw new SecurityException(msg);
}
}
if (!providerRunning) {
// If the provider is a singleton AND
// (it's a call within the same user || the provider is a
// privileged app)
// Then allow connecting to the singleton provider
boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
if (singleton) {
userId = UserHandle.USER_SYSTEM;
}
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
// Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
if (!mUserController.isUserRunning(userId, 0)) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": user " + userId + " is stopped");
return null;
}
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
checkTime(startTime, "getContentProviderImpl: before getProviderByClass");
cpr = mProviderMap.getProviderByClass(comp, userId);
checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
final boolean firstClass = cpr == null;
if (firstClass) {
final long ident = Binder.clearCallingIdentity();
// If permissions need a review before any of the app components can run,
// we return no provider and launch a review activity if the calling app
// is in the foreground.
if (mPermissionReviewRequired) {
if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
return null;
}
}
try {
checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
ApplicationInfo ai =
AppGlobals.getPackageManager().
getApplicationInfo(
cpi.applicationInfo.packageName,
STOCK_PM_FLAGS, userId);
checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");
if (ai == null) {
Slog.w(TAG, "No package info for content provider "
+ cpi.name);
return null;
}
ai = getAppInfoForUser(ai, userId);
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)) {
// If this is a multiprocess provider, then just return its
// info and allow the caller to instantiate it. Only do
// this if the provider is the same user as the caller's
// process, or can run as root (so can be in any process).
return cpr.newHolder(null);
}
// This is single process, and our app is now connecting to it.
// See if we are already in the process of launching this
// provider.
final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
}
// If the provider is not already being launched, then get it
// started.
if (i >= N) {
try {
// Content provider is now in use, its package can't be stopped.
try {
checkTime(startTime, "getContentProviderImpl: before set stopped state");
AppGlobals.getPackageManager().setPackageStoppedState(
cpr.appInfo.packageName, false, userId);
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
}
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 {
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);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
}
return cpr != null ? cpr.newHolder(conn) : null;
}
如果ContentProvider没有启动则AMS会调用com.android.server.am.ActivityManagerService#startProcessLocked方法,启动ContentProvider所在的进程,然后再启动ContentProvider。
@GuardedBy("this")
private boolean startProcessLocked(String hostingType, String hostingNameStr, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
long startTime) {
...
if (mConstants.FLAG_PROCESS_START_ASYNC) {
try {
// final String entryPoint = "android.app.ActivityThread";
final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint,
app, app.startUid, gids, runtimeFlags, mountExternal, app.seInfo,
requiredAbi, instructionSet, invokeWith, app.startTime);
synchronized (ActivityManagerService.this) {
handleProcessStartedLocked(app, startResult, startSeq);
}
} catch (RuntimeException e) {
synchronized (ActivityManagerService.this) {
Slog.e(TAG, "Failure starting process " + app.processName, e);
mPendingStarts.remove(startSeq);
app.pendingStart = false;
forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
false, false, true, false, false,
UserHandle.getUserId(app.userId), "start failure");
}
}
});
return true;
} else {
try {
//final String entryPoint = "android.app.ActivityThread";
final ProcessStartResult startResult = startProcess(hostingType, entryPoint, app,
uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,
invokeWith, startTime);
handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
startSeq, false);
} catch (RuntimeException e) {
Slog.e(TAG, "Failure starting process " + app.processName, e);
app.pendingStart = false;
forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
false, false, true, false, false,
UserHandle.getUserId(app.userId), "start failure");
}
return app.pid > 0;
}
}
最终在com.android.server.am.ActivityManagerService#startProcess里调用 Process.start方法启动新进程,入口方法为ActivityThread.main方法,按照ContentProvider的创建和启动流程进行初始化(AMS与Zygote的通信是通过socket的)。
public final Process.ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int runtimeFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String invokeWith,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
}
}
因此无论是单进程还是单线程模式下的ContentProvider 我们都可以随时使用,因为即使我们调用另一个未启动的进程的ContentProvider,系统也会为我们去启动并创建。
android.content.ContentProviderNative继承 Binder 实现 IContentProvider并重写了相关的CRUDQ方法,而ContentProvider.Transport又继承了ContentProviderNative,所以执行CRUDQ操作时本质上是调用ContentProvider.Transport对应的CRUD方法。
首先是获取IContentProvider对象之后,剩下的就是通过AMS来访问ContentProvider(Binder类型的对象IContentProvider),,以query操作为例,android.content.ContentResolver#query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal),通过AMS 得到对应的ContentProvider实例ContentProvider.Transport。
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
//调用
qCursor = unstableProvider.query(mPackageName, uri, projection,
queryArgs, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(
mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
if (qCursor != null) {
qCursor.close();
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(null);
}
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
releaseProvider(stableProvider);
}
}
}
android.content.ContentProvider.Transport#query方法最终调用的还是ContentProvider的query方法,再通过Binder吧结果返回给调用者。
@Override
public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
// The caller has no access to the data, so return an empty cursor with
// the columns in the requested order. The caller may ask for an invalid
// column and we would not catch that but this is not a problem in practice.
// We do not call ContentProvider#query with a modified where clause since
// the implementation is not guaranteed to be backed by a SQL database, hence
// it may not handle properly the tautology where clause we would have created.
if (projection != null) {
return new MatrixCursor(projection, 0);
}
// Null projection means all columns but we have no idea which they are.
// However, the caller may be expecting to access them my index. Hence,
// we have to execute the query as if allowed to get a cursor with the
// columns. We then use the column names to return an empty cursor.
// 最终还是调用当前ContentProvider类下对应的query方法
Cursor cursor = ContentProvider.this.query(
uri, projection, queryArgs,
CancellationSignal.fromTransport(cancellationSignal));
if (cursor == null) {
return null;
}
// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.query(
uri, projection, queryArgs,
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingPackage(original);
}
}
其他的update、delete、insert方法也是类似的。
ContentProvider的创建和启动都是伴随着进程启动,如果ContentProvider所在的进程未启动,会由AMS通过socket与Zygote进行通信申请创建新进程,并启动ActivityThread.main方法,由ActivityThread#attch方法发起,最终还是在handleBindApplication里完成与AMS通信创建和启动;而使用时本质上都是虽然是通过IContentProvider Binder对象去调用对应的方法,虽然转了一圈,但最终还是会回到ContentProvider自身的CURDQ方法,结果也会经由Binder返回给调用者,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。