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