本章分监听机制、通知机制和数据库访问三个部分进行分析。
一、监听机制
在android中,我们可以通过ContentResolver监听数据库的变化,这一节我们来看监听是如何实现的。
我们首先会获取ContentResolver,在ContextImpl中调用getContentResolver()即可,它会返回一个内部类对象,如下:
public ContentResolver getContentResolver() {
return mContentResolver;
}
public final void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
@NonNull ContentObserver observer) {
Preconditions.checkNotNull(uri, "uri");
Preconditions.checkNotNull(observer, "observer");
registerContentObserver(
ContentProvider.getUriWithoutUserId(uri),
notifyForDescendants,
observer,
ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
}
通过调用registerContentObserver进行注册动作,第一个参数为监听的uri,也就是我们需要监听的数据库字段;第二个参数为监听是否需要监听派生的uri还是只严格匹配该uri,在后面通知机制一节中我们再看该变量是如何控制的;第三个为重写的ContentObserver。注意到ContentResolver实际就是通过ContentService这个系统服务来操作的,这里其实容易理解,注册的信息应该交给系统服务来统一管理。接下来看ContentService的registerContentObserver方法:
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle) {
if (observer == null || uri == null) {
throw new IllegalArgumentException("You must pass a valid uri and observer");
}
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int callingUserHandle = UserHandle.getCallingUserId();
// Registering an observer for any user other than the calling user requires uri grant or
// cross user permission
if (callingUserHandle != userHandle) {
if (checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION, userHandle)
!= PackageManager.PERMISSION_GRANTED) {
enforceCrossUserPermission(userHandle,
"no permission to observe other users' provider view");
}
}
if (userHandle < 0) {
if (userHandle == UserHandle.USER_CURRENT) {
userHandle = ActivityManager.getCurrentUser();
} else if (userHandle != UserHandle.USER_ALL) {
throw new InvalidParameterException("Bad user handle for registerContentObserver: "
+ userHandle);
}
}
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
uid, pid, userHandle);
if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
" with notifyForDescendants " + notifyForDescendants);
}
}
mRootNode是一个根节点,它是一个ObserverNode对象,从它的初始化看到它的name是空字符串。来到它的addObserverLocked方法:
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
boolean notifyForDescendants, Object observersLock,
int uid, int pid, int userHandle) {
// If this is the leaf node add the observer
if (index == countUriSegments(uri)) {
mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
uid, pid, userHandle));
return;
}
// Look to see if the proper child already exists
String segment = getUriSegment(uri, index);
if (segment == null) {
throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
}
int N = mChildren.size();
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (node.mName.equals(segment)) {
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
return;
}
}
// No child found, create one
ObserverNode node = new ObserverNode(segment);
mChildren.add(node);
node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
observersLock, uid, pid, userHandle);
}
第一次传入的index为0,第一个判断是如果传入的uri的segment数量刚好和index一样,才会创建一个新的ObserverEntry放到该ObserverNode对象的容器mObservers中,我们传入的uri segment个数肯定是大于0的。比如:content://com.rhythmjay.providers/item/0的个数就是3。接下来取index为0的segemt,也就是com.rhythmjay.providers,遍历mRootNode的所有子节点,查找子节点中是否有name为com.rhythmjay.providers,有则调用该子节点的addObserverLocked方法且index+1,否则创建一个name为com.rhythmjay.providers的子节点调用其addObserverLocked方法且index+1。
第二次传入的index为1,第一个判断仍然不满足,获取index为1的segment为item。寻找或者创建name为item的子节点,调用其addObserverLocked并且index+1。
第三次传入的index为2,第一个判断仍然不满足,获取index为2的segment为0. 寻找或者创建name为0的子节点,调用其addObserverLocked并且index+1。
第四次传入的index为3,第一个判断满足,直接通过observer新建ObserverNode对象并放入到该子节点的容器mObservers中。
至此,我们可以看到ContentService中实际保存了一颗类似树状的信息,每个叶子节点都根据segment有命名,从根节点到叶子节点保存了一条uri的信息,且每个叶子节点都有保存这条路径uri的监听者observer。
二、通知机制
我们在自定义ContentProvider时,会将一些客户敏感的字段变化通知出去,而这也是通过ContentResolver完成的。首先我们确认好通知的条件,满足条件后我们就可以调用如下方法通知给感兴趣的客户端:
getContext().getContentResolver().notifyChange(CONTENT_URI, null);
这里也会通过ContentService完成通知:
public void notifyChange(Uri uri, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
int userHandle) {
......
try {
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
flags, userHandle, calls);
}
final int numCalls = calls.size();
for (int i=0; i<numCalls; i++) {
ObserverCall oc = calls.get(i);
try {
oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
+ uri);
} catch (RemoteException ex) {
synchronized (mRootNode) {
Log.w(TAG, "Found dead observer, removing");
IBinder binder = oc.mObserver.asBinder();
final ArrayList<ObserverNode.ObserverEntry> list
= oc.mNode.mObservers;
int numList = list.size();
for (int j=0; j<numList; j++) {
ObserverNode.ObserverEntry oe = list.get(j);
if (oe.observer.asBinder() == binder) {
list.remove(j);
j--;
numList--;
}
}
}
}
}
if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
uri.getAuthority());
}
}
......
}
这里有个flags,通过上述方式通知的话默认为NOTIFY_SYNC_TO_NETWORK,意思就是会通知网络服务器。这里有创建一个ObserverCall的列表calls,它是一个输出参数,接下来就是从mRootNode来搜集所有的observer了。
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
int targetUserHandle, ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
flags, targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
+ segment);
// Notify any observers at this level who are interested in descendants
collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
flags, targetUserHandle, calls);
}
int N = mChildren.size();
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (segment == null || node.mName.equals(segment)) {
// We found the child,
node.collectObserversLocked(uri, index + 1, observer,
observerWantsSelfNotifications, flags, targetUserHandle, calls);
if (segment != null) {
break;
}
}
}
}
继续拿通知的uri为content://com.rhythmjay.providers/item/0作例子。
第一次传入的index为0,index小于segmentCount,segment为com.rhythmjay.providers,调用collectMyObserversLocked,注意这里的leaf为false。进入collectMyObserversLocked:
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags,
int targetUserHandle, ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
ObserverEntry entry = mObservers.get(i);
// Don't notify the observer if it sent the notification and isn't interested
// in self notifications
boolean selfChange = (entry.observer.asBinder() == observerBinder);
if (selfChange && !observerWantsSelfNotifications) {
continue;
}
// Does this observer match the target user?
if (targetUserHandle == UserHandle.USER_ALL
|| entry.userHandle == UserHandle.USER_ALL
|| targetUserHandle == entry.userHandle) {
// Make sure the observer is interested in the notification
if (leaf) {
// If we are at the leaf: we always report, unless the sender has asked
// to skip observers that are notifying for descendants (since they will
// be sending another more specific URI for them).
if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0
&& entry.notifyForDescendants) {
if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+ ": skip notify for descendants");
continue;
}
} else {
// If we are not at the leaf: we report if the observer says it wants
// to be notified for all descendants.
if (!entry.notifyForDescendants) {
if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
+ ": not monitor descendants");
continue;
}
}
if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
+ " flags=" + Integer.toHexString(flags)
+ " desc=" + entry.notifyForDescendants);
calls.add(new ObserverCall(this, entry.observer, selfChange,
UserHandle.getUserId(entry.uid)));
}
}
}
由于mRootNode的mObservers.size()为0,所以这方法中我们什么都没做,接下来就是查找mRootNode的子节点中name为com.rhythmjay.providers的节点。然后调用它的collectObserversLocked方法且index+1。
第二次传入的index为1,index小于segmentCount,segment为item,调用collectMyObserversLocked,注意这里的leaf为false。然后查找是否有监听content://com.rhythmjay.providers的observer,一般我们都不会把authorities作为监听对象。
所以这里也是什么都没做。接下来就是查找mRootNode的子节点中name为item的节点。然后调用它的collectObserversLocked方法且index+1。
第三次传入的index为2,index小于segmentCount,segment为0,调用collectMyObserversLocked,注意这里的leaf为false。然后查找是否有监听content://com.rhythmjay.providers/item的observer。我们假设有这样的observer,则进入到循环中。这里注意到如果满足!entry.notifyForDescendants的条件,会过滤掉该observer,还记得我们在注册的时候registerContentObserver中的第二个参数notifyForDescendants吗,如果为false,则不会搜集该observer,如果为true,则搜集该observer,即如果为true的话,监听content://com.rhythmjay.providers/item的observer在content://com.rhythmjay.providers/item/0字段变化时,也会被通知到。所以创建一个ObserverCall对象增加到列表中。接下来就是查找mRootNode的子节点中name为0的节点。然后调用它的collectObserversLocked方法且index+1。
第四次传入的index为3,index等于segmentCount,然后调用collectMyObserversLocked,leaf为true,将监听content://com.rhythmjay.providers/item/0的observer增加到列表中。到这里,已经完成所有监听者的搜集。
接下来就是遍历列表并调用成员的回调onChange,通过binder的方式回调到各个进程中。然后有SyncManager的scheduleLocalSync方法,实现网络同步。
三、访问数据库
首先来到ContentResolver的query方法,通过acquireUnstableProvider方法和参数uri获取ContentProvider的Binder客户端,将uri转换为auth。ContextImpl中的ApplicationContentResolver实现了该方法,而最终实现的地方在ActivityThread中:
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;
}
// 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.
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().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.
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}
首先调用acquireExistingProvider,查找本地应用是否已经保存了Provider的客户端记录ProviderClientRecord对象,如果没有返回null,如果有则不管是stable还是unstable均需要将其引用计数加1并通知AMS将ContentProviderConnection(用于维护一个客户端进程和ConntentProvider)的引用计数加1。
这里我们假设是第一次获取该ContentProvider客户端,所以继续往下走,接下来我们要去AMS去获取ContentProviderHolder对象,它是一个Parcelable对象,内部有IContentProvider和ContentProviderConnection的Binder客户端,接下来我们看下AMS的getContentProviderImpl的方法:
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
synchronized(this) {
long startTime = SystemClock.uptimeMillis();
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller);
if (r == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when getting content provider " + name);
}
}
boolean checkCrossUser = true;
checkTime(startTime, "getContentProviderImpl: getProviderByName");
// 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);
}
checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
if (r != null && cpr.canRunHere(r)) {
// This provider has been published or is in the process
// of being published... but it is also allowed to run
// in the caller's process, so don't make a connection
// and just let the caller instantiate its own instance.
ContentProviderHolder holder = cpr.newHolder(null);
// don't give caller the provider object, it needs
// to make its own.
holder.provider = null;
return holder;
}
final long origId = Binder.clearCallingIdentity();
checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
// In this case the provider instance already exists, so we can
// return it right away.
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
// make sure to count it as being accessed and thus
// back up on the LRU list. This is good because
// content providers are often expensive to start.
checkTime(startTime, "getContentProviderImpl: before updateLruProcess");
updateLruProcessLocked(cpr.proc, false, null);
checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
}
}
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
final int verifiedAdj = cpr.proc.verifiedAdj;
boolean success = updateOomAdjLocked(cpr.proc);
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
// it, we will check whether the process still exists. Note that this doesn't
// completely get rid of races with LMK killing the process, but should make
// them much smaller.
if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {
success = false;
}
maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
checkTime(startTime, "getContentProviderImpl: after updateOomAdj");
if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success);
// NOTE: there is still a race here where a signal could be
// pending on the process even though we managed to update its
// adj level. Not sure what to do about this, but at least
// the race is now smaller.
if (!success) {
// Uh oh... it looks like the provider's process
// has been killed on us. We need to wait for a new
// process to be started, and make sure its death
// doesn't kill our process.
Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString()
+ " is crashing; detaching " + r);
boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
checkTime(startTime, "getContentProviderImpl: before appDied");
appDiedLocked(cpr.proc);
checkTime(startTime, "getContentProviderImpl: after appDied");
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;
}
Binder.restoreCallingIdentity(origId);
}
if (!providerRunning) {
try {
checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
} catch (RemoteException ex) {
}
if (cpi == null) {
return null;
}
// 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);
checkTime(startTime, "getContentProviderImpl: got app info for user");
String msg;
checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
!= null) {
throw new SecurityException(msg);
}
checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
if (!mProcessesReady
&& !cpi.processName.equals("system")) {
// If this content provider does not run in the system
// process, and the system is not yet ready to run other
// processes, then fail fast instead of hanging.
throw new IllegalArgumentException(
"Attempt to launch content provider before system ready");
}
// Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
if (!mUserController.isUserRunningLocked(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 (Build.PERMISSIONS_REVIEW_REQUIRED) {
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);
}
}
checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");
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);
}
if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "
+ (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "
+ cpr.info.name + " callers=" + Debug.getCallers(6));
// 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) {
final long origId = Binder.clearCallingIdentity();
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);
checkTime(startTime, "getContentProviderImpl: after set stopped state");
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ cpr.appInfo.packageName + ": " + e);
}
// Use existing process if already started
checkTime(startTime, "getContentProviderImpl: looking for process record");
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null && !proc.killed) {
if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,
"Installing in existing process " + proc);
if (!proc.pubProviders.containsKey(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
checkTime(startTime, "getContentProviderImpl: before start process");
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": process is bad");
return null;
}
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
checkTime(startTime, "getContentProviderImpl: updating data structures");
// Make sure the provider is published (the same provider class
// may be published under multiple names).
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
mProviderMap.putProviderByName(name, cpr);
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null) {
conn.waiting = true;
}
}
checkTime(startTime, "getContentProviderImpl: done!");
}
// Wait for the provider to be published...
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": launching app became null");
EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
UserHandle.getUserId(cpi.applicationInfo.uid),
cpi.applicationInfo.packageName,
cpi.applicationInfo.uid, name);
return null;
}
try {
if (DEBUG_MU) Slog.v(TAG_MU,
"Waiting to start provider " + cpr
+ " launchingApp=" + cpr.launchingApp);
if (conn != null) {
conn.waiting = true;
}
cpr.wait();
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
return cpr != null ? cpr.newHolder(conn) : null;
}
首先用getProviderByName判断获取ContentProvider的记录ContentProviderRecord是否存在,这里有单例或者针对每个userid有ContentProvider,也就说同一auth的ContentProvider可能只有一个或者也有多个。之后我们来看providerRunning的值如何获取,如果ContentProviderRecord对象存在且其进程存在,且进程未被kill掉,它的值为true,说明ContentProvider已经正常运转起来了。我们先看为true的情况,这里有cpr.canRunHere的判断,如下:
public boolean canRunHere(ProcessRecord app) {
return (info.multiprocess || info.processName.equals(app.processName))
&& uid == app.info.uid;
}
满足设置multiprocess为true或者获取ContentProvider的进程与ContentProvider同一进程,然后shareduid一致则返回true,意思就是我们可以为每个进程都提供一个ContentProvider实例,但必须shareduid一致。在这里直接创建ContentProviderHolder实例,且其IContentProvider为null并返回。
然后再来看下providerRunning为false的情况,首先查询ContentProvider的信息ProviderInfo,通过它保存的包名和类名创建ComponentName对象,最后通过getProviderByClass查询ContentProviderRecord对象,这里与之前getProviderByName类似。如果cpr为null,这种情况只有在Singleton时满足,首先会创建一个新的ContentProviderRecord对象,如果是multiprocess则直接创建ContentProviderHolder返回。接下来判断该ContentProvider是否正在启动,如果未在启动,i >= N为true,进入判断语句内部。接下来根据ContentProvider所在进程是否已经存在,如未存在,则首先启动该进程,我们在之前的Activity启动中已经分析过,这里就直接看进程已经起来的场景,调用ActivityThread的scheduleInstallProvider方法。直接进入installContentProviders方法,如下:
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>();
for (ProviderInfo cpi : providers) {
if (DEBUG_PROVIDER) {
StringBuilder buf = new StringBuilder(128);
buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
}
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) {
throw ex.rethrowFromSystemServer();
}
}
这里有两个关键调用,installProvider及AMS的publishContentProviders,我们先看installProvider方法:
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
if (DEBUG_PROVIDER || noisy) {
Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (c == null) {
Slog.w(TAG, "Unable to get context for package " +
ai.packageName +
" while loading content provider " +
info.name);
return null;
}
try {
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
if (DEBUG_PROVIDER) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
if (!mInstrumentation.onException(null, e)) {
throw new RuntimeException(
"Unable to get provider " + info.name
+ ": " + e.toString(), e);
}
return null;
}
} else {
provider = holder.provider;
if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ info.name);
}
IActivityManager.ContentProviderHolder retHolder;
synchronized (mProviderMap) {
if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
+ " / " + info.name);
IBinder jBinder = provider.asBinder();
if (localProvider != null) {
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
if (pr != null) {
if (DEBUG_PROVIDER) {
Slog.v(TAG, "installProvider: lost the race, "
+ "using existing local provider");
}
provider = pr.mProvider;
} else {
holder = new IActivityManager.ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
retHolder = pr.mHolder;
} else {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
if (DEBUG_PROVIDER) {
Slog.v(TAG, "installProvider: lost the race, updating ref count");
}
// We need to transfer our new reference to the existing
// ref count, releasing the old one... but only if
// release is needed (that is, it is not running in the
// system process).
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManagerNative.getDefault().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;
}
由于我们传入的ContentProviderHolder为null,所以在这里通过反射创建了对应的ContentProvider实例localProvider,并通过attachInfo设置其权限和回调onCreate方法,同时通过getIContentProvider取出IContentProvider。如果查询到的ProviderClientRecord为null,则会创建ContentProviderHolder对象,并将IContentProvider的引用放到ContentProviderHolder对象中。同时也会创建ProviderClientRecord对象,保存到容器中。最终该函数返回ContentProviderHolder对象。
接下来看publishContentProviders,如下
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
if (providers == null) {
return;
}
enforceNotIsolatedCaller("publishContentProviders");
synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
if (r == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when publishing content providers");
}
final long origId = Binder.clearCallingIdentity();
final int N = providers.size();
for (int i = 0; i < N; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
continue;
}
ContentProviderRecord dst = r.pubProviders.get(src.info.name);
if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
if (dst != null) {
ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
mProviderMap.putProviderByClass(comp, dst);
String names[] = dst.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
mProviderMap.putProviderByName(names[j], dst);
}
int launchingCount = mLaunchingProviders.size();
int j;
boolean wasInLaunchingProviders = false;
for (j = 0; j < launchingCount; j++) {
if (mLaunchingProviders.get(j) == dst) {
mLaunchingProviders.remove(j);
wasInLaunchingProviders = true;
j--;
launchingCount--;
}
}
if (wasInLaunchingProviders) {
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
}
synchronized (dst) {
dst.provider = src.provider;
dst.proc = r;
dst.notifyAll();
}
updateOomAdjLocked(r);
maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
src.info.authority);
}
}
Binder.restoreCallingIdentity(origId);
}
}
这里会根据之前返回的ContentProviderHolder对象中的信息查询ContentProviderRecord,由于我们之前创建了一个ContentProviderRecord对象,所以它不会为null。将该ContentProviderRecord对象保存到mProviderMap中,实际也是宣告了ContentProviderRecord已经是启动状态了。接着将其中真正启动的ContentProvider记录中移除。接下来比较关键的一点,在这里调用了ContentProviderRecord对象的notify方法,为什么要notify呢,等下我们就知道了。
再回到之前的AMS getContentProviderImpl流程,我们看到这里有wait动作,原来是唤醒的这里,确保getContentProviderImpl不会因为内部的异步处理,导致获取不到正确结果,该接口最终返回ContentProviderHolder对象。
回到客户端进程,再一次调用installProvider,注意与前一次的installProvider的区别,两个进程不一样,一个为ContentProvider所在进程,一个为客户端所在进程。
在这里也是区别multiprocess和普通获取ContentProvider的地方,如果multiprocess为true,则holder.provider == null满足条件,就会重新创建ContentProvider实例,这个实例也就属于客户端的进程中。如果是普通获取ContentProvider,则只是累计计数加1。
我们拿到IContentProvider以后,就可以通过它来访问ContentProvider的内部数据了,处理完后会调用releaseProvider将引用计数减1。
到这里,ContentProvider的工作机制已经分析完成了。