This is based on the Android Framework 8.0 source code

The use of ContentProviders is also fundamentally around Binder IPC. Similar to using other system services, the use of ContentProvider by APP can be divided into three parts:

  • How does the Client APP obtain the calling interface of the ContentProvider from the system service
  • How ContentProvider publishes its calling interface to system services.
  • This section describes how the Client APP invokes the ContentProvider interface.

The core of this article is how the Client APP obtains the invocation interface of ContentProvider from the system service.

Important data objects

1. ContentProviderRecord

Of all the data objects that record important information about a ContentProvider, the ContentProviderRecord is without a doubt the most important. Either record the contentProviders that have been published in the APP in ProcessRecord or record all contentProviders that have been published in the APP in AMS. Important contents of the record in ContentProviderRecord:

2. ProviderClientRecord

Another important one is ProviderClientRecord, which caches acquired ContentProvider information (including local ContentProviders) in the Client APP. When using ContentProvider, if the corresponding ContentProvider information is found in the cache, the system does not request AMS to obtain the ContentProvider interface, but directly uses the IContentProvider interface recorded in the cache.

ContentProviderProxy

Get the process resolution of the ContentProvider

During the use of the Client APP, the Client usually does not communicate with the ContentProvider directly. Requests to use the ContentProvider are executed by the ContentResolver. ContextImpl getContentResolver(); ContextImpl getContentResolver(); ContextImpl getContentResolver(); ContextImpl getContentResolver();

//ContextImpl.java
public ContentResolver getContentResolver() {
        return mContentResolver;
}
Copy the code

Returns the mContentResolver already stored in the Context, which is initialized in ContextImpl’s related initializer (8.0 in private constructor, 4.2 in init function).

//ContextImpl.java ... mContentResolver = new ApplicationContentResolver(this, mainThread, user); .Copy the code

Instance assignment here ApplicationContentResolver type, this is a ContentResolver interface implementation class, rewrite the acquisition and release the ContentProvider logic inside.

    @Override
    protected IContentProvider acquireProvider(Context context, String auth) {
        return mMainThread.acquireProvider(context,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), true);
    }

    @Override
    protected IContentProvider acquireExistingProvider(Context context, String auth) {
        return mMainThread.acquireExistingProvider(context,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), true);
    }

    @Override
    public boolean releaseProvider(IContentProvider provider) {
        return mMainThread.releaseProvider(provider, true);
    }

    @Override
    protected IContentProvider acquireUnstableProvider(Context c, String auth) {
        return mMainThread.acquireProvider(c,
                ContentProvider.getAuthorityWithoutUserId(auth),
                resolveUserIdFromAuthority(auth), false);
    }

    @Override
    public boolean releaseUnstableProvider(IContentProvider icp) {
        return mMainThread.releaseProvider(icp, false);
    }

    @Override
    public void unstableProviderDied(IContentProvider icp) {
        mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
    }

    @Override
    public void appNotRespondingViaProvider(IContentProvider icp) {
        mMainThread.appNotRespondingViaProvider(icp.asBinder());
    }
Copy the code

What the concrete method does is simply forward a request to the ActivityThread to execute the specific logic, and the ActivityThread is responsible for interacting with AMS.

AcquireExistingProvider, acquireProvider and acquireUnstableProvider forward these three methods are ultimately perform the same method of ActivityThread. AcquireProvider (). The difference is that the last identifier, true, represents stable Provider and false represents Unstable Provider. Implementation in ActivityThread:

final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if(provider ! = null) {return provider;
        }
        IActivityManager.ContentProviderHolder holder = null;
        try {
            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;
        }

        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;

Copy the code

AcquireProvider () is divided into three parts:

  • 1. Try to obtain the cached IContentProvider.
  • 2. If no, obtain IContentProvider from AMS.
  • 3. After successful execution ActivityThread first. InstallProvider to ContentResolver returned to the provider.

Part 1: The logic for getting the IContentProvider in the cache:

        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            if(! jBinder.isBinderAlive()) { Log.i(TAG,"Acquiring provider " + auth + " for user " + userId
                        + ": existing object's process dead");
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if(prc ! = null) { incProviderRefLocked(prc, stable); }return provider;
        }
Copy the code
  • Because interfaces can be called by multiple threads, locks are added here.
  • The provider is first obtained from the mProviderMap via auth, userId.
  • If it is process of the provider’s dead then call handleUnstableProviderDiedLocked, and returns null.
  • In handleUnstableProviderDiedLocked (), if the death of preserving the provider in need to release the provider list, to release its cleaning; Notify AMS of the death of Unstable Provider.
  • If saved in the list of providers to release, the reference count is +1.

Part 2: Get the IContentProvider interface through ams.getContentProvider ()

Ended up in AMS. GetContentProviderImpl (). (Here is Blocking execution, lock is AMS. This lock is used for many things, such as registering broadcast receivers.

ActivityManagerService’s getContentProviderImpl()

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"); / / check whether the corresponding ContentProvider existing CPR. = mProviderMap getProviderByName (name, userId);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{// Not a singleton, clear continue to get CPR = null; cpi = null; }} // ContentProvider exists while the process exists & the process is not killed Boolean providerRunning = CPR! = null && cpr.proc ! = null && ! cpr.proc.killed; // The content of the subsequent implementation is segmented... .Copy the code

AMS will first attempt to retrieve cached ContentProvider information, determine whether the ContentProvider remains running, and use this as a basis for subsequent execution. The execution of the subsequent method body is divided into three parts:

1.ContentProvider keeps running:

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"); // Returns if the provider has been published or is being published.if(r ! = null && cpr.canRunHere(r)) { ContentProviderHolder holder = cpr.newHolder(null); holder.provider = null;return holder;
                }
                try {
                    if (AppGlobals.getPackageManager()
                            .resolveContentProvider(name, 0 /*flags*/, userId) == null) {
                        return null;
                    }
                } catch (RemoteException e) {
                }
                final long origId = Binder.clearCallingIdentity();
                checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); Conn = incProviderCountLocked(r, CPR, token, stable);if(conn ! = null && (conn.stableCount+conn.unstableCount) == 1) {if(cpr.proc ! Perceptible_app_adj. {// Update LRU 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; Adboolean success = updateOomAdjLocked(cpr.proc,true);
                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); // The process has been killedif(! Success) {// Make sure you don't kill slog. I (TAG,"Existing provider " + cpr.name.flattenToShortString()
                            + " is crashing; detaching "+ r); // Reduce reference count 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); }Copy the code

Note that if the reference count is reduced, the Connection between the Client and ContentProider will kill the Client process, so return null.

2. The ContentProvider is not running

            if(! providerRunning) { try { checkTime(startTime,"getContentProviderImpl: before resolveContentProvider"); / / try to obtain from the PackageManager ContentProviderInfo 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;
                }
                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")) {
                    throw new IllegalArgumentException(
                            "Attempt to launch content provider before system ready"); } // Return if the provider's provider is not running.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"); / / tries to through the package name from the cache access ContentProvider CPR. = mProviderMap getProviderByClass (comp, userId); checkTime(startTime,"getContentProviderImpl: after getProviderByClass"); final boolean firstClass = cpr == null; // No cache goes into processing, try to get a new ContentProviderRecord.if (firstClass) {
                    final long ident = Binder.clearCallingIdentity();
                    if (mPermissionReviewRequired) {
                        if(! requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {return null;
                        }
                    }
                    try {
                        checkTime(startTime, "getContentProviderImpl: before getApplicationInfo"); / / according to the package name and userId. Try to get new ApplicationInfo ApplicationInfo ai = AppGlobals. GetPackageManager (). GetApplicationInfo ( cpi.applicationInfo.packageName, STOCK_PM_FLAGS, userId); checkTime(startTime,"getContentProviderImpl: after getApplicationInfo"); // If no information is obtained in the installation package, the system returns directlyif (ai == null) {
                            Slog.w(TAG, "No package info for content provider "
                                    + cpi.name);
                            returnnull; } ai = getAppInfoForUser(ai, userId); // Encapsulate new ContentProviderRecord CPR = new ContentProviderRecord(this, CPI, ai, comp, singleton); } catch (RemoteException ex) { // pm isin same process, this will never happen.
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
                }
                checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");
                if(r ! = null && cpr.canrunHere (r)) {// If ContentProvider declares that multiple processes are running or in the same process or have the same UID, it returns.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)); // If the ContentProvider is already being started, then I < the number of contentProviders being started. final int N = mLaunchingProviders.size(); int i;for (i = 0; i < N; i++) {
                    if (mLaunchingProviders.get(i) == cpr) {
                        break; }} // Not in startup, start it.if(i >= N) { final long origId = Binder.clearCallingIdentity(); 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); } 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"); // The process corresponding to the provider has been started, but the provider has not been published // So sending a message triggers the Publish Provider operation. proc.pubProviders.put(cpi.name, cpr); try { proc.thread.scheduleInstallProvider(cpi); } catch (RemoteException e) { } } }else{// The provider process is not started or has been killed. // Start the provider process 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) {// can't start, return empty slog. w(TAG,"Unable to launch app "
                                        + cpi.applicationInfo.packageName + "/"
                                        + cpi.applicationInfo.uid + " for provider "
                                        + name + ": process is bad");
                                returnnull; }} // Set the launchingApp saved in the ContentProviderRecord to the process, and save provider // to the launching ContentProvider list CPR. launchingApp = proc; mLaunchingProviders.add(cpr); } finally { Binder.restoreCallingIdentity(origId); } } checkTime(startTime,"getContentProviderImpl: updating data structures"); // Save the new ContentProviderRecord to mProviderMap.if(firstClass) { mProviderMap.putProviderByClass(comp, cpr); } mProviderMap.putProviderByName(name, cpr); // Increase the reference count between Client and ContentProvider. conn = incProviderCountLocked(r, cpr, token, stable);if(conn ! = null) { conn.waiting =true; }}Copy the code

The key steps are annotated in the paper, and the main process is as follows:

  • Permission check
  • Obtain the provider information from the PM
  • Check whether the provider can run properly
  • Start the corresponding process, and publish provider
  • Update package status, cache, reference count.

3. Polling ensures that the provider is published

        synchronized (cpr) {
            while(CPR. provider == null) {// The provider process does not existif (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); // Only when there is a reference between the Client and the ContentProviderif(conn ! = null) { conn.waiting =true;
                    }
                    cpr.wait();
                } catch (InterruptedException ex) {
                } finally {
                    if(conn ! = null) { conn.waiting =false; }}}}returncpr ! = null ? cpr.newHolder(conn) : null;Copy the code

GetContentProviderImpl always tries to fetch the published provider from the cache and returns it. If the cache does not exist, try to create a new provider and start the corresponding process.

The third part: perform ActivityThread. InstallProvider ()

   private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if(holder = = null | | holder. The provider = = null) {/ / local providerif (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ":"+ info.name); } Context c = null; ApplicationInfo ai = info.applicationInfo; / / providercallerIn the same bagif (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;
            }
            if(info.splitName ! = null) { try { c = c.createContextForSplit(info.splitName); } catch (NameNotFoundException e) { throw new RuntimeException(e); }} // Create a local provider try {final java.lang.classLoader cl = c.gettclassLoader ();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// Create the local ContentProvider ContextlocalProvider.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);
                }
                returnnull; }}else{// ContentProvider = holder.provider;if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ":"
                    + info.name);
        }
        ContentProviderHolder retHolder;
        synchronized (mProviderMap) {
            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                    + "/" + info.name);
            IBinder jBinder = provider.asBinder();
            if (localProvider ! Cname = new ComponentName(info.packagename, info.name); ProviderClientRecord pr = mLocalProvidersByName.get(cname);if(pr ! = null) {// A local provider instance already exists in the cache.if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } elseHolder = new ContentProviderHolder(info); // Create a new Client Provider record and install it. holder.provider = provider; holder.noReleaseNeeded =true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); mLocalProvidersByName.put(cname, pr); } // Content Provider Holder retHolder = pr.mholder; }else {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if(prc ! = null) {// If this parameter is not null, the reference count of the provider is stored in the local provider reference cache.if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, updating ref count"); } // The external provider increases the reference count.if(! noReleaseNeeded) { incProviderRefLocked(prc, stable); try { ActivityManager.getService().removeContentProvider( holder.connection, stable); } catch (RemoteException e) { //do nothing content provider object is dead any way
                        }
                    }
                } else {
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    if(Noreleasenneeded) {// Local provider or provider from the system PRC = new ProviderRefCount(holder, client, 1000, 1000); }else{// Provider PRC of other apps = stable? new ProviderRefCount(holder, client, 1, 0) : new ProviderRefCount(holder, client, 0, 1); } // Store the provider reference count in the App cache. mProviderRefCountMap.put(jBinder, prc); } retHolder = prc.holder; }}return retHolder;
    }
Copy the code

For local ContentProvider, installProvider creates a local provider and stores the content (IContentProvider interface, local ContentProvider instance, etc.) in the App cache. For external ContentProviders, what installProvider does is store the provider’s reference count in the App’s cache.