08 startup process of content provider

Time:2021-12-8

preface

As one of the four major components, content provider is not frequently used as other components, but this is no reason why we don’t study it in depth. There is no end to an article about content provider. This article first introduces its startup process.

1. Calling process from query method to AMS

An example of the use of content provider is given. In activity, I call the content provider with the following code:

public class ContentProviderActivity extends AppCompatActivity {
    private final static String TAG = "ContentProviderActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        Uri uri = Uri.parse("content://com.example.liuwangshu.mooncontentprovide.GameProvider");
        ContentValues mContentValues = new ContentValues();
        mContentValues.put("_id", 2);
        Mcontentvalues.put ("name", "great sailing era ol");
        Mcontentvalues.put ("describe", "the most fun sailing online game");
        getContentResolver().insert(uri, mContentValues);//1
        Cursor gameCursor = getContentResolver().query(uri, new String[]{"name", "describe"}, null, null, null);
     ...
    }
}

To call the content provider, first use the getcontentresolver method at comment 1, as shown below.
frameworks/base/core/Java/android/content/ContextWrapper.java

@Override
public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
}

Here, mbase refers to contextimpl. The getcontentresolver method of contextimpl is as follows.

frameworks/base/core/java/android/app/ContextImpl.java

@Override
public ContentResolver getContentResolver() {
    return mContentResolver;
}

The above code returns the mcontentresolver object of applicationcontentresolver type. Applicationcontentresolver is a static internal class in contextimpl, which inherits from contentresolver and is created in the construction method of contextimpl.
When we call the insert, query, update and other methods of contentresolver, the content provider will be started. Here, take the query method as an example.
The query method is implemented in the parent class contentresolver of applicationcontentresolver. The code is as follows.
frameworks/base/core/java/android/content/ContentResolver.java

public final @Nullable Cursor query(final @RequiresPermission.Read @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);//1
        ...
        try {
           ...
            try {
                qCursor = unstableProvider.query(mPackageName, uri, projection,
                        selection, selectionArgs, sortOrder, remoteCancellationSignal);//2
            } catch (DeadObjectException e) {
               ...
            }
    ...
   }

At note 1, return the unstableprovider object of icontentprovider type through the acquireunstableprovider method, and call the query method of unstableprovider at note 2. Let’s see what the acquireunstableprovider method does, as shown below.
frameworks/base/core/java/android/content/ContentResolver.java

public final IContentProvider acquireUnstableProvider(Uri uri) {
     if (!SCHEME_CONTENT.equals(uri.getScheme())) {//1
         return null;
     }
     String auth = uri.getAuthority();
     if (auth != null) {
         return acquireUnstableProvider(mContext, uri.getAuthority());//2
     }
     return null;
 }

Note 1 is used to check whether the scheme of URI is equal to “content”. If not, null will be returned. In Note 2, the acquireunstableprovider method is called, which is an abstract method. Its implementation is in the subclass applicationcontentresolver of contentresolver:
frameworks/base/core/java/android/app/ContextImpl.java

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

Return the acquireprovider method of the mmainthread object of activitythread type:
frameworks/base/core/java/android/app/ActivityThread.java

public final IContentProvider acquireProvider(
         Context c, String auth, int userId, boolean stable) {
     final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);//1
     if (provider != null) {
         return provider;
     }
     IActivityManager.ContentProviderHolder holder = null;
     try {
         holder = ActivityManagerNative.getDefault().getContentProvider(
                 getApplicationThread(), auth, userId, stable);//2
     } catch (RemoteException ex) {
         throw ex.rethrowFromSystemServer();
     }
     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);//3
     return holder.provider;
 }

At note 1, check whether there is a target ContentProvider in the mprovidermap of arraymap type in the activitythread. If there is, it will be returned. If not, the getcontentprovider method of AMP will be called at note 2, and finally the getcontentprovider method of AMS will be called. The installprovider method at comment 3 is used to store the data related to the ContentProvider returned at comment 2 in the mprovidermap for caching. In this way, when using the same content provider, it is not necessary to call the getcontentprovider method of AMS every time. Use the getcontentprovider method of AMS. The code is as follows.
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override
public final ContentProviderHolder getContentProvider(
        IApplicationThread caller, String name, int userId, boolean stable) {
 ...
    return getContentProviderImpl(caller, name, null, stable, userId);
}

Getcontentprovider method returns the getcontentproviderimpl method:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
...
       ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);//1
                        if (proc != null && proc.thread != null && !proc.killed) {
                            ...
                            if (!proc.pubProviders.containsKey(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                proc.pubProviders.put(cpi.name, cpr);
                                try {
                                    proc.thread.scheduleInstallProvider(cpi);//2
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            checkTime(startTime, "getContentProviderImpl: before start process");
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);//3
                            checkTime(startTime, "getContentProviderImpl: after start process");
                          ...
                        }
             ...           
                        
}

There is a lot of code for the getcontentproviderimpl method, and the key part is intercepted here. In note 1, the application process information of the target ContentProvider is obtained through the getprocessrecordlocked method, which is represented by the Proc of processrecord type. If the application process has been started, the code in Note 2 will be called; otherwise, the startprocesslocked method in note 3 will be called to start the process. Here, we assume that the application process of ContentProvider has not been started. For the application process startup process, IAndroid application process startup process (previous)As already mentioned, the main method of activitythread will eventually be called. The code is as follows.
frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
     ...
       Looper.prepareMainLooper();//1
       ActivityThread thread = new ActivityThread();//2
       thread.attach(false);
       if (sMainThreadHandler == null) {
           sMainThreadHandler = thread.getHandler();
       }
       if (false) {
           Looper.myLooper().setMessageLogging(new
                   LogPrinter(Log.DEBUG, "ActivityThread"));
       }
       // End of event ActivityThreadMain.
       Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
       Looper.loop();//3
       throw new RuntimeException("Main thread loop unexpectedly exited");
   }

In note 1, obtain the looper in ThreadLocal through the preparemainlooper method, and open the message loop in note 3. At comment 2, the activitythread is created and its attach method is called:
frameworks/base/core/java/android/app/ActivityThread.java

  private void attach(boolean system) {
  ...
    final IActivityManager mgr = ActivityManagerNative.getDefault();//1
            try {
                mgr.attachApplication(mAppThread);//2
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
  ...          
}

AMS will finally be obtained at note 1. Call the attachapplication method of AMS at note 2 and pass in the mappthread object of applicationthread type.
The calling process from query method to AMS is shown in the following sequence diagram (omitting the application process startup process).

08 startup process of content provider

image.png

2. AMS starts the process of content provider

Next, let’s look at the attachapplication method of AMS, as shown below.
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override
public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

Attachapplicationlocked method is called in attachapplication method:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

     private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
   ...
   thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());
...
}

The attachApplicationLocked method calls the bindApplication method of thread. Thread is IApplicationThread type. From the type name, it can be seen that it is used for inter process communication. The bindApplication method here is ApplicationThreadProxy class, which implements IApplicationThread interface.
frameworks/base/core/java/android/app/ApplicationThreadNative.java

class ApplicationThreadProxy implements IApplicationThread {
...
    @Override
    public final void bindApplication(String packageName, ApplicationInfo info,
            List<ProviderInfo> providers, ComponentName testName, ProfilerInfo profilerInfo,
            Bundle testArgs, IInstrumentationWatcher testWatcher,
            IUiAutomationConnection uiAutomationConnection, int debugMode,
            boolean enableBinderTracking, boolean trackAllocation, boolean restrictedBackupMode,
            boolean persistent, Configuration config, CompatibilityInfo compatInfo,
            Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
      ...
        mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
                IBinder.FLAG_ONEWAY);
        data.recycle();
    }
...
}

So far, the above calling process is still executed in AMS process. Therefore, it is necessary to send bind to the newly created application process (the process where the target content provider is located) through the mremote object of ibinder type_ APPLICATION_ A communication request of type transaction. This communication request is handled by the bindapplication method of applicationthread executed in the newly created application process, as shown below.
frameworks/base/core/java/android/app/ActivityThread.java

public final void bindApplication(String processName, ApplicationInfo appInfo,
               List<ProviderInfo> providers, ComponentName instrumentationName,
               ProfilerInfo profilerInfo, Bundle instrumentationArgs,
               IInstrumentationWatcher instrumentationWatcher,
               IUiAutomationConnection instrumentationUiConnection, int debugMode,
               boolean enableBinderTracking, boolean trackAllocation,
               boolean isRestrictedBackupMode, boolean persistent, Configuration config,
               CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {
               ...
               sendMessage(H.BIND_APPLICATION, data);
       }

Call the SendMessage method to send bind like H_ For application type messages, the handlemessage method of H is as follows.
frameworks/base/core/java/android/app/ActivityThread.java

   public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
            ...
            case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
  ...
  }
  ... 
}

Let’s next look at the handlebindapplication method:
frameworks/base/core/java/android/app/ActivityThread.java

private void handleBindApplication(AppBindData data) {
 ...
      final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);//1
       try {
              final ClassLoader cl = instrContext.getClassLoader();
              mInstrumentation = (Instrumentation)
                  cl.loadClass(data.instrumentationName.getClassName()).newInstance();//2
          } catch (Exception e) {
           ...
          }
          final ComponentName component = new ComponentName(ii.packageName, ii.name);
          mInstrumentation.init(this, instrContext, appContext, component,
                  data.instrumentationWatcher, data.instrumentationUiAutomationConnection);//3
         ...
          Application app = data.info.makeApplication(data.restrictedBackupMode, null);//4
          mInitialApplication = app;
          if (!data.restrictedBackupMode) {
              if (!ArrayUtils.isEmpty(data.providers)) {
                  installContentProviders(app, data.providers);//5
                  mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
              }
          }
        ...
         mInstrumentation.callApplicationOnCreate(app);//6
        ... 
}

The code of handlebindapplication method is very long, and the main part is intercepted here. Contextimpl is created at comment 1. Create instrumentation through reflection at note 2 and initialize instrumentation at note 3. Create the application in note 4 and call the oncreate method of the application in Note 6, which means that the application process where the content provider is located has been started. Before that, call the installcontentproviders method in note 5 to start the content provider. The code is as follows.
frameworks/base/core/java/android/app/ActivityThread.java

private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
        new ArrayList<IActivityManager.ContentProviderHolder>();

    for (ProviderInfo cpi : providers) {//1
        ...
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);//2
      ...
    }

    try {
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);//3
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

In note 1, traverse the providerinfo list of the current application process to get the providerinfo of each content provider (storing the information of the content provider), and call the installprovider method in Note 2 to start these content providers. In note 3, these content providers are stored in the mprovidermap of AMS through the publishcontentproviders method of AMS. As mentioned earlier, this mprovidermap acts as a cache to prevent the getcontentprovider method of AMs from being called every time the same content provider is used. To see how to start the content provider when using the installprovider method. The installprovider method is shown below.
frameworks/base/core/java/android/app/ActivityThread.java

private IActivityManager.ContentProviderHolder installProvider(Context context,
           IActivityManager.ContentProviderHolder holder, ProviderInfo info,
           boolean noisy, boolean noReleaseNeeded, boolean stable) {
       ContentProvider localProvider = null;
  ...
               final java.lang.ClassLoader cl = c.getClassLoader();
               localProvider = (ContentProvider)cl.
                   loadClass(info.name).newInstance();//1
               provider = localProvider.getIContentProvider();
               if (provider == null) {
                 ...
                   return null;
               }
               if (DEBUG_PROVIDER) Slog.v(
                   TAG, "Instantiating local provider " + info.name);
               localProvider.attachInfo(c, info);//2
           } catch (java.lang.Exception e) {
              ...
               }
               return null;
           }
       }
          ...
       return retHolder;
   }

Create a localprovider object of ContentProvider type through reflection at annotation 1, and call its attachinfo method at annotation 2:
frameworks/base/core/java/android/content/ContentProvider.java

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
     ...
          ContentProvider.this.onCreate();
      }
  }

The onCreate method is invoked in the attachInfo method, which is an abstract method. In this way, the content provider is started.
Finally, the timing diagram of AMS starting content provider is given.

08 startup process of content provider

image.png