How to find ContentProvider from Uri in source Teahouse

Keywords: Android Java Attribute xml

Introduction

We all know the use of ContentProvider, one of the four components, which provides you with a unified data access format. The caller doesn't need to care where the data comes from (such as DB, XML file, network, etc.), just get the corresponding ContentResolver to add, delete, query and modify.
When you implement a Provider, you will also declare the following in the configuration file:

<provider
    android:name=".provider.TestProvider"
    android:authorities="com.xxx.yyy.provider"
    android:exported="true"
    android:readPermission="com.xxx.yyy.permission.READ_PROVIDER" />

The authors are the unique identifier of the Provider, so they are generally written as a combination of package name and other strings. If data is required to be provided to other applications, the exported value should be set to true, and at the same time, read-write permission should be added to the standard method.
Then, let's start with the common query operations:

ContentResolver r = getContentResolver();
Uri uri = Uri.parse("content://com.xxx.yyy.provider/test_path/1");
Cursor c = r.query(uri, null, null, null, null);
// ...

Just like accessing a web address, we need a URI to access ContentProvider, whose data format is:

  • The scheme prefix is fixed: content://
  • Authorized host: in this case, com.xxx.yyy.provider
  • Path and parameters: in this case, test path / 1

So, how does the system lock the corresponding ContentProvider through such a URI?

Seek

Mainly related to source code (based on Android 10):

frameworks/base/core/java/android/content/ContentResolver.java
frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/app/ActivityThread.java

The general idea is to trace the parameter uri in the above query method and see its flow direction. According to the set of source code design, at first several layers of calls are invisible, so we don't need to read it carefully. Let's see the query method of ContentResolver:

    @Override
    public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable Bundle queryArgs,
            @Nullable CancellationSignal cancellationSignal) {
        // ...
        // Get unstable Provider
        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        Cursor qCursor = null;
        try {
            // ...
            try {
                // Try query operation
                qCursor = unstableProvider.query(mPackageName, uri, projection,
                        queryArgs, remoteCancellationSignal);
            } catch (DeadObjectException e) {
                // The remote process has died...  but we only hold an unstable
                // reference though, so we might recover!!!  Let's try!!!!
                // This is exciting!!1!!1!!!!1
                // I didn't delete this note specially. It's very leather. Remote process is dead, but we still have a reference to unstableProvider. Try to recycle its resources! This is a real rowing boat! (although I don't know where it's happening.)
                unstableProviderDied(unstableProvider);
                // Unstable Provider operation failed, get stable Provider
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                // Try the query operation again
                qCursor = stableProvider.query(
                        mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
                }
            if (qCursor == null) {
                return null;
            }
            // ...
        } catch (RemoteException e) {
            // ...
            return null;
        } finally {
            // Release resources
        }
    }

From the above source code, we can see that there are two places to obtain ContentProvider according to uri, namely acquirenstableprovider and acquireProvider methods of ContentResolver. First look at the former:

    public final IContentProvider acquireUnstableProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        String auth = uri.getAuthority(); // As an example, you can get "com.xxx.yyy.provider" here
        if (auth != null) {
            // This is an abstract method in ContentResolver, which is implemented by each sub Resolver
            return acquireUnstableProvider(mContext, uri.getAuthority());
        }
        return null;
    }

So we trace to the static inner class ApplicationContentResolver of ContextImpl:

    private static final class ApplicationContentResolver extends ContentResolver {
        @UnsupportedAppUsage
        private final ActivityThread mMainThread;
        // ...
        @Override
        protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }
    }

Actually called to ActivityThread. Note that the key parameter passed at this time is auth instead of uri:

    @UnsupportedAppUsage
    public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        // Get existing Provider    
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
        	return provider;
        }
        // ...
        // I didn't get it. I'll try to install it again. Here's a socket. It'll be useful later
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

Generally speaking, it's better to look at the source code first deep and then wide, and make clear the hot code first. Next let's look at the acquireExistingProvider method:

    public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            // Pay attention to the map that stores the Provider records. In fact, this is the focus of this article
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider; // Get Provider instance finally
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                // Provider's process is dead. null is returned directly
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }
			// ...
            return provider;
        }
    }

After analysis, there are naturally several questions. What is ProviderKey and how to construct it? When was the mProviderMap filled?
With questions, first look at the former:

    private static final class ProviderKey {
        final String authority;
        final int userId;

        public ProviderKey(String authority, int userId) {
            this.authority = authority;
            this.userId = userId;
        }

        @Override
        public boolean equals(Object o) {
            // ...
        }

        @Override
        public int hashCode() {
            // ...
        }
    }

It can be seen that ProviderKey is an internal POJO in ActivityThread, which is very common and does not do any special processing for the input parameter. Then ContentProvider is uniquely determined by authority and userId, which corresponds to the introduction at the beginning of the article.
In addition, because Android is currently a multi-user operating system (the concept is diluted in the domestic ROM, but the functions of application double opening and system separation are realized with it), the user id is necessary here.

Next, let's look at the latter question, where does the mProviderMap come from? When are Provider records added? Very simply, in ActivityThread, instantiate as follows:

    @UnsupportedAppUsage
    final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
        = new ArrayMap<ProviderKey, ProviderClientRecord>();

And only one place is put:

    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
            ContentProvider localProvider, ContentProviderHolder holder) {
        final String auths[] = holder.info.authority.split(";");
        final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);

        if (provider != null) {
            // ...
        }

        final ProviderClientRecord pcr = new ProviderClientRecord(
                auths, provider, localProvider, holder);
        for (String auth : auths) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord existing = mProviderMap.get(key);
            if (existing != null) {
                // ...
            } else {
                mProviderMap.put(key, pcr); // Added here
            }
        }
        return pcr;
    }

It can be seen that the construction of the ProviderClientRecord instance is completed in this installProviderAuthoritiesLocked private method and added to the map.
Here's an episode to note: the first line of code of the method splits the authority string (the separator is;), and the final number of ProviderClientRecord depends on the array split. So when declaring the android:authorities attribute in the Manifest configuration file, you can fill in multiple authorized host s (for example, multiple domain names can point to a website at the same time), which are separated by semicolons. No wonder the attribute names are plural.

Next, let's look at the call of installProviderAuthoritiesLocked method:

    @UnsupportedAppUsage
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            // ...
        } else {
            provider = holder.provider;
            // ...
        }

        ContentProviderHolder retHolder;

        synchronized (mProviderMap) {
            // ...
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    // ...
                } else {
                    // ...
                    // First call
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    // ...
                }
                retHolder = pr.mHolder;
            } else {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    // ...
                } else {
                    // The second call
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    // ...
                }
                retHolder = prc.holder;
            }
        }
        return retHolder;
    }

From above, the call of installProviderAuthoritiesLocked method is in installProvider method. Do you still remember the "poke eyes" above? I answered.

summary

  • When we use ContentResolver to query, the query method calls the acquireExistingProvider method of ActivityThread layer by layer, and obtains the corresponding Provider instance according to the authorization host (i.e. authority) in the URI string and the current userId.

  • When acquireExistingProvider cannot be obtained, install Provider through installProvider method and add its carrier ProviderClientRecord to mProviderMap.

  • When a Provider is declared in the Android manifest, the android:authorities property can be filled with multiple strings, separated by semicolons:

    <provider
        android:name=".provider.TestProvider"
        android:authorities="com.xxx.yyy.provider;cn.xxx.yyy.provider;net.xxx.yyy.provider"
        ... />
    

    In this way, it can be written as URI s of three different host s, but the same ContentProvider is mapped. The specific benefits I can think of are as follows:

    • As with the IP multi domain sites, the domain name is diversified, and some host s are preempted in advance to avoid three-way counterfeiting.
    • Different URI s are provided for internal and external developers to distinguish and count data.
88 original articles published, 185 praised, 500000 visitors+
Private letter follow

Posted by sridhar golyandla on Wed, 29 Jan 2020 03:21:11 -0800