Introduction to the
Read this article on my CheapTalks blog
ContentProvider is believed to be familiar to everyone. Under the premise of Android system paying more and more attention to system security, I don’t know that everyone is curious about provider how to realize data addition, deletion, change and check operation between processes under the security measures of Android. If you think about it, you can probably guess that Android’s common “trick” for interprocess communication is binder Call, and the implementation of ContentProvider probably uses AMS as a middleman between the provider’s client and server. In this article, I will analyze in detail how the client app requests contentProviders that run in other application processes. At the end of the article, the data structures involved between AMS and app will be attached.
Due to space constraints, I won’t go into detail about other aspects of ContentProvider. For example, the query of data can involve a large amount of data, and Android’s Binder synchronization buffer is only 1016K, so provider look-up is a hybrid of ashmeM and Binder’s cross-process communication technology. The main function of binder is to transmit ASHmem FD. For example, the analysis of ContentObserver, which is often used by application development, can be found in the analysis of ContentService I wrote earlier.
Start with CRUD query
When we write an APP, we usually make CRUD calls through the ContentResolver. And this operation is usually by the context. GetContentResolver (), XXX this invocation chain. Context is the decorator pattern, and the actual executor is ContextImp, so I’m not going to say much about that, but let’s start with the ContextImpl source.
ContextImpl.java
private final ApplicationContentResolver mContentResolver;
@Override
public ContentResolver getContentResolver(a) {
return mContentResolver;
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {... mContentResolver =new ApplicationContentResolver(this, mainThread, user);
}Copy the code
As you can see, the final ApplicationContentResolver is in charge of our APP ContentProvider CRUD on this operation.
ContentResolver.java
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {...// Obtain an unstableProvider that is unstable because AMS maintains unstable and unstable
// If the count of stable is greater than zero, when the ContentProvider server process dies, then the client process using the service will also be killed
// Here we try to use unstableProvider to increase the value of unstable AMS
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
longstartTime = SystemClock.uptimeMillis(); .try {
// Binder calls to the server and returns a cursor object
// When cursor.close is called, the call releaseProvider is called to release the reference between the ContentProvider server and the client
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, 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
// The first time I tried to use Unstable, the server process died and an exception was thrown
// Release the references associated with unstableProvider
unstableProviderDied(unstableProvider);
// On the second attempt, stableProvider will be used
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
// Note that stableProvider is used after a failure, this time if the server process is killed
// If the cursor has not called close, the client process will be killed
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null; }...// Decorate it
CursorWrapperInner wrapper = newCursorWrapperInner(qCursor, stableProvider ! =null ? stableProvider : acquireProvider(uri));
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 {
// Close if not empty
// If the above operation succeeds, the qCursor will not be empty, so the closing operation is assigned to the APP side
if(qCursor ! =null) { qCursor.close(); }...if(unstableProvider ! =null) {
releaseUnstableProvider(unstableProvider);
}
if(stableProvider ! =null) { releaseProvider(stableProvider); }}}Copy the code
It is important to note that when a Binder call throws a DeadObjectException, it is not necessarily because the peer process is dead, but because the peer binder buffer is full. This section spoke to the Google engineers earlier and they seem to have plans to refine the DeadObjectException, but it turns out that throwing a DeadObjectException doesn’t mean the peer process is dead.
The code comments are very detailed, and the general flow can be seen in the diagram.
To view a larger version
The core operation acquireProvider
Client operation
In the ContentResolver calls acquireStableProvider and acquireUnstableProvider will be called after the subclasses ApplicationContextResolver, If ActivityThread acquireReProvider is true, obtain a stableProvider that is unstable and unstable. Instead, get the unstableProvider. Let’s look at the code:
ApplicationContentResolver.java
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
private final UserHandle mUser;
// stableProvider
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
// unstableProvider
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false); }...Copy the code
ApplicationContentResolver is ActivityThread inner classes, look here for convenience, or separate the two pieces of code analysis
ActivityThread.java
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
// Now find the provider locally, if there is no such thing as AMS to request
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if(provider ! =null) {
returnprovider; }... IActivityManager.ContentProviderHolder holder =null;
try {
// Request the ContentProvider from AMS, which is the core method
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// "install" the provider, which basically means creating new instances, adding or removing references
// scheduleInstallProvider () ¶
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}Copy the code
AMS.getContentProviderImpl (1)
After ActivityThread Binder calls AMS, it immediately calls getContentProviderImpl, which is a large method and is broken down for analysis
ActivityManagerService.java
private final 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.elapsedRealtime();
// Gets the caller's ProcessRecord object
ProcessRecord r = null;
if(caller ! =null) { r = getRecordForAppLocked(caller); . }boolean checkCrossUser = true; .// Get ContentProviderRecord by uri Authority namecpr = mProviderMap.getProviderByName(name, userId); .if (cpr == null&& userId ! = UserHandle.USER_OWNER) {// Check if userId=0 already has a ContentProviderRecord
cpr = mProviderMap.getProviderByName(name, UserHandle.USER_OWNER);
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_OWNER;
checkCrossUser = false;
} else {
cpr = null;
cpi = null; }}}Copy the code
In this section AMS looks for the running provider in the providerMap. If the provider is found, it indicates that the provider has been started. If not, the provider “installs” in the Provider client by calling installProvider, and AMS waits for the client to publish the Provider.
AMS.getContentProviderImpl (2)
// Determine whether the current provider is running based on previous queries
booleanproviderRunning = cpr ! =null;
if(providerRunning) { cpi = cpr.info; String msg; .// If the provider can be run directly on the client, return the provider to the client here
if(r ! =null && cpr.canRunHere(r)) {
ContentProviderHolder holder = cpr.newHolder(null);
holder.provider = null;
return holder;
}
final longorigId = Binder.clearCallingIdentity(); .// Add a reference
conn = incProviderCountLocked(r, cpr, token, stable);
// How to update LruProcess when stable and unstable have a total reference count of 1
if(conn ! =null && (conn.stableCount + conn.unstableCount) == 1) {
if(cpr.proc ! =null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
...
updateLruProcessLocked(cpr.proc, false.null);
checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); }}if(cpr.proc ! =null) {...Update the adj. of the provider process
booleansuccess = updateOomAdjLocked(cpr.proc); maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name); .if(! success) { ...// If not, reduce the reference count and kill the provider process
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;
}
}
Binder.restoreCallingIdentity(origId);
}Copy the code
AMS.getContentProviderImpl (3)
boolean singleton;
// If the provider is not running
if(! providerRunning) {try {
checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
/ / get ProviderInfo
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
} catch (RemoteException ex) {
}
// If it is empty, the provider was not found and is returned to the client
if (cpi == null) {
return null;
}
// Provider specifies whether the Provider is a singleton
singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
if(singleton) { userId = UserHandle.USER_OWNER; } cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId); .// Get CPR from providerMap via ComponentName
ComponentName comp = newComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); .// Whether the provider is created for the first time
final boolean firstClass = cpr == null;
if (firstClass) {
final long ident = Binder.clearCallingIdentity();
try{ ApplicationInfo ai = AppGlobals.getPackageManager(). getApplicationInfo( cpi.applicationInfo.packageName, STOCK_PM_FLAGS, userId); . ai = getAppInfoForUser(ai, userId);Create a CPR
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)) {
return cpr.newHolder(null); }...final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break; }}// Provider is not running yet
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);
}
// Obtain the process to run the provider
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
// If the process is already started, binder calls the process to create the provider
if(proc ! =null&& proc.thread ! =null) {
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 {
If the process is not started, start the process
// It should be noted that after the process is started, the creation of the provider will take place when the ActivityThread is initialized
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false.0."content provider".new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false.false.false);
// The process was not created successfully
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); }}...// If you are creating an instance of this provider for the first time, cache it in providerMap
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
mProviderMap.putProviderByName(name, cpr);
// Add a reference
conn = incProviderCountLocked(r, cpr, token, stable);
if(conn ! =null) {
conn.waiting = true; }}}Copy the code
AMS.getContentProviderImpl (4)
At this point, the analysis of getContentProviderImpl is nearing completion. We see that if the provider has not been created in its process at this point, AMS will instantiate the provider, which is “publish”. AMS waits here for the APP process to complete before returning.
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {...return null;
}
try{...if(conn ! =null) {
conn.waiting = true;
}
// Wait for the provider" publish "to complete
cpr.wait();
} catch (InterruptedException ex) {
} finally {
if(conn ! =null) {
conn.waiting = false; }}}}returncpr ! =null ? cpr.newHolder(conn) : null;Copy the code
To view a larger version
scheduleInstallProvider
Application Provider instantiation has two entrances. One is when the process already exists, AMS Binder calls the APP to instantiate a provider. One is to install providers in batches when the process is bound to the Application.
ActivityThread.java
private void installContentProviders( Context context, List
providers)
{
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>();
for (ProviderInfo cpi : providers) {
...
// Instantiate the provider
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/.true /*noReleaseNeeded*/.true /*stable*/);
if(cph ! =null) {
cph.noReleaseNeeded = true; results.add(cph); }}try {
// Complete the instantiation, notify AMS, and publish the provider.
ActivityManagerNative.getDefault().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
}
}Copy the code
installProvider (1)
If the provider is not already instantiated, you need to “install” it in the host process
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
/ / if the AMS getContentProviderImpl returns NULL or need to app "installation" provider, against the provider in the local attempt to instantiate
if (holder == null || holder.provider == null) {... 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}}...try {
// Instantiate the provider using reflection
finaljava.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl. loadClass(info.name).newInstance(); provider = localProvider.getIContentProvider(); . 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);
}Copy the code
installProvider (2)
Manipulation of references; Caching the provider in the host process
IActivityManager.ContentProviderHolder retHolder;
synchronized (mProviderMap) {
// Obtain the Provider proxy
IBinder jBinder = provider.asBinder();
// If the provider was created just before the previous operation
if(localProvider ! =null) {å
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
// Local cache
if(pr ! =null) {... provider = pr.mProvider; }else {
holder = new IActivityManager.ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
// Cache classes based on Uri authority name
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"); }...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 {
// Initialize the reference
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;Copy the code
AMS.publishContentProviders
After the provider host process has successfully instantiated, AMS needs to be notified that it no longer needs to wait. After that, the getContnetProviderImpl of the application process accessing the Provider is truly finished
public final void publishContentProviders(IApplicationThread caller, List
providers)
{... enforceNotIsolatedCaller("publishContentProviders");
synchronized (this) {
// Get the provider host process
finalProcessRecord r = getRecordForAppLocked(caller); .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);
// Cache by class
mProviderMap.putProviderByClass(comp, dst);
String names[] = dst.info.authority.split(";");
// Cache according to URI authority name
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--; }}// Remove provider ANR "time bomb"
if (wasInLaunchingProviders) {
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
}
synchronized (dst) {
dst.provider = src.provider;
dst.proc = r;
// Notify THE AMS Provider that it has "published" successfullydst.notifyAll(); } updateOomAdjLocked(r); maybeUpdateProviderUsageStatsLocked(r, src.info.packageName, src.info.authority); } } Binder.restoreCallingIdentity(origId); }}Copy the code
To view a larger version
ContentProvider is a series of hidden traps
Because of the design of the ContentProvider, when the provider’s host process dies, the process accessing it is also affected if it is doing CRUD.
After the process is killed
The following is analyzed from processRecord.kill as the entry point
ProcessRecord.java
void kill(String reason, boolean noisy) {
if(! killedByAm) { ...// Kill the process
Process.killProcessQuiet(pid);
Process.killProcessGroup(uid, pid);
if(! persistent) { killed =true;
killedByAm = true; }... }}Copy the code
When the process dies, the death callback registered with attachApplication is invoked
ActivityManagerService.java
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
final IApplicationThread mAppThread;
AppDeathRecipient(ProcessRecord app, int pid,
IApplicationThread thread) {
if (DEBUG_ALL) Slog.v(
TAG, "New death recipient " + this
+ " for thread " + thread.asBinder());
mApp = app;
mPid = pid;
mAppThread = thread;
}
@Override
public void binderDied(a) {
// When a process dies, it will be called inside
synchronized (ActivityManagerService.this) {
appDiedLocked(mApp, mPid, mAppThread, true); }}}final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
boolean fromBinderDied) {... handleAppDiedLocked(app,false.true); . }private final void handleAppDiedLocked(ProcessRecord app,
boolean restarting, boolean allowRestart) {
int pid = app.pid;
boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1); . }private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index) {...// Delete the "published" provider associated with the process
for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
final booleanalways = app.bad || ! allowRestart;boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
...
restart = true;
}
cpr.provider = null;
cpr.proc = null;
}
app.pubProviders.clear();Copy the code
Cascading the provider
private final boolean removeDyingProviderLocked(ProcessRecord proc,
ContentProviderRecord cpr, boolean always) {
final boolean inLaunching = mLaunchingProviders.contains(cpr);
// If the provider is still running
if(! inLaunching || always) {synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
}
mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid));
String names[] = cpr.info.authority.split(";");
for (int j = 0; j < names.length; j++) { mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid)); }}// Loop through all connections for this process
for (int i = cpr.connections.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = cpr.connections.get(i);
if (conn.waiting) {
// If this connection is waiting for the provider, then we don't
// need to mess with its process unless we are always removing
// or for some reason the provider is not currently launching.
if(inLaunching && ! always) {continue; }}// Get the provider's client
ProcessRecord capp = conn.client;
conn.dead = true;
// This connection must have a stable count greater than 0 to kill its client
if (conn.stableCount > 0) {
// If the app is not a resident process and is running, a cascade will be executed
if(! capp.persistent && capp.thread ! =null&& capp.pid ! =0&& capp.pid ! = MY_PID) { capp.kill("depends on provider "
+ cpr.name.flattenToShortString()
+ " in dying proc "+ (proc ! =null ? proc.processName : "??"), true); }}else if(capp.thread ! =null&& conn.provider.provider ! =null) {
try {
capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
} catch (RemoteException e) {
}
// In the protocol here, we don't expect the client to correctly
// clean up this connection, we'll just remove it.
cpr.connections.remove(i);
if(conn.client.conProviders.remove(conn)) { stopAssociationLocked(capp.uid, capp.processName, cpr.uid, cpr.name); }}}if (inLaunching && always) {
mLaunchingProviders.remove(cpr);
}
return inLaunching;
}Copy the code
In provider, unstableProvider is used for query operations for the first time, and stableProvider is used after a failure. StableProvider is used directly for other insert, update, and delete operations
Cascading is to protect data consistency between the provider client and the server. Insert and delete operations involve data update. Therefore, if the provider fails, the client process can only be forced to die and restart to recover data to ensure that the client maintains correct data
To view a larger version
releaseProvider
AcquireProvider acquireProvider increases the reference count of the stable, and when the Provider server dies, if the stable count is greater than zero, the Provider client will be killed. When does the count of stables decrease? The answer is in releaseProvider
trigger
ContentResolver.java
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri"); IContentProvider unstableProvider = acquireUnstableProvider(uri); .try{...try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
...
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null; }...// Return cursor, and releaseProvider will not be called until the client calls close
return wrapper;
} catch (RemoteException e) {
return null;
} finally{... }}public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {
Preconditions.checkNotNull(url, "url");
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert".null /* where */);
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
// Call releaseProvider directly at the endreleaseProvider(provider); }}public final int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String where, @Nullable String[] selectionArgs) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
return rowsUpdated;
} catch (RemoteException e) {
return -1;
} finally{ releaseProvider(provider); }}public final int delete(@NonNull Uri url, @Nullable String where, @Nullable String[] selectionArgs) {
Preconditions.checkNotNull(url, "url");
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
return rowsDeleted;
} catch (RemoteException e) {
return -1;
} finally{ releaseProvider(provider); }}public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
Preconditions.checkNotNull(uri, "uri");
Preconditions.checkNotNull(method, "method");
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
return provider.call(mPackageName, method, arg, extras);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally{ releaseProvider(provider); }}Copy the code
Above, all operations except Query end with a call to releaseProvider to release the provider count; Query is special, and is called only after cursor’s close is called.
There is a special method called Call, which is different in the way the data is transferred. Other query, INSERT, update and DELETE operations are combined with Binder and AshMEm for data transmission, while Call is purely performed with Binder
To view a larger version
App-side data structure
To view a larger version
- ProviderKey: contains the URL Authority string
- ProviderClientRecord: Corresponds to the ContentProviderRecord of AMS, which mainly refers to the Provider proxy handle
- ProviderRefCount: encapsulates two references to stable and unstable
- ContentProviderHolder: mainly refers to the ContentProviderConnection proxy handles, the provider proxy handles
- ProviderInfo: Provider’s abstract storage class
- ComponentName: An abstract class for a component class
System_server data structure
To view a larger version
- ProviderMap: AMS side is used to cache objects in ContentProviderRecord, providing four different query methods
- ContentProviderRecord: Encapsulates provider in AMS, mainly referring to Provider Binder proxy and process information
- Association: An abstraction in which two processes are related
- ProcessRecord: An abstraction of the process, containing information about the priority of the process, its four components, and so on
- ContentProviderConnection: mainly refers to the abstract provider client process
conclusion
This blog analyzes in detail how the Provider proxy object is acquired, which involves the communication between the provider client, the AMS of system_server, and the provider server. At the same time, it also analyzes the cascading principle of contentProvider, which requires special attention to client development, otherwise their app died without cause.
As for how the provider client and the server use ashmem and Binder to transfer data, I will not go into details because this article is too long, and I will analyze it later.
How does a ContentProvider implement data change monitoring