Uso de Android ContentProvider y análisis de código fuente (Android Q)

Uso del proveedor de contenido de Android


ContentProvider es uno de los cuatro componentes principales de Android. Es un proveedor de contenido. Utiliza una interfaz unificada para proporcionar contenido de datos para las aplicaciones.

ContentProvider puede ayudarnos a lograr el acceso a datos entre procesos de manera muy simple, sin preocuparse por el proceso en el que se encuentran los datos y cómo se almacena el proceso.

Registro de ContentProvider

ContentProvider es un proveedor de datos y nuestro propósito es utilizarlo para proporcionar datos al mundo exterior.

  1. En primer lugar, tenemos que definir una clase que herede de ContentProvider (por ejemplo, com.budaye.MyProvider) y copiar varios métodos públicos de la clase para procesar la consulta del proceso de datos, agregar, eliminar, actualizar y otras operaciones. Nuestros datos pueden almacenarse en archivos, bases de datos o de otras formas.

  2. Registre la clase ContentProvider personalizada en AndroidManifest.

        <provider
            android:name="com.budaye.MyProvider"
            android:authorities="com.budaye.application.storage"
            android:exported="false" />

Uso de ContentProvider

Los datos de ContentProvider se pueden llamar en cualquier programa o proceso, y todas son interfaces unificadas (que incluyen: consultar, agregar, eliminar, actualizar, etc.).

Pasos de uso:

  1. Cuando esté en uso, llame a getContentResolver () del objeto de contexto para obtener el objeto ContentResolver.
  2. Utilice el objeto ContentResolver para realizar la manipulación de datos.
    public void readData(String selection) {
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver()
                    .query(getTableUri(), null, selection, null, null);
            ……
        } catch (Exception e) {
            LogX.e(TAG, SUB_TAG, getName() + "; " + e.toString());
        } 
    }

Análisis de código fuente de Android ContentProvider


Arriba, hemos presentado ContentProvider y cómo usarlo. ¿Crees que es muy simple?

Entonces debemos pensar en las siguientes preguntas:

  1. ¿Cómo se realiza una forma de uso tan sencilla?
  2. ¿Cómo simplifica la comunicación entre procesos? (Llamar al ContentProvider de otros procesos es tan simple como en el proceso actual)
  3. ¿Por qué es necesario registrarse en AndroidManifest?
  4. ¿Cuándo se instaló ContentProvider? ¿Cuándo se llama nuestra subclase ContentProvider?

Con las preguntas anteriores, veamos el código fuente.

Proceso de instalación de ContentProvider

En el artículo anterior "Creación de aplicaciones y análisis de origen del proceso de inicio del proceso de la aplicación" , podemos encontrar que ContentProvider se instala e inicializa durante el proceso de inicio de la aplicación.

Cuando se inicia una aplicación, el sistema llama primero al método main () de ActivityThread, que es el método de entrada de una aplicación, y ActivityThread representa el hilo principal o el hilo de la interfaz de usuario de la aplicación. Luego, llame al método adjunto de ActivityThread para realizar una gran cantidad de inicialización y preparación. El método adjunto de ActivityThread llama a la aplicación attach del servicio ActivityManagerService de forma remota para iniciar una aplicación. El método attachApplicationLocked en ActivityManagerService en el proceso de servicio AMS llama de forma remota a la bindApplication del ApplicationThread del proceso actual para crear e iniciar la Aplicación correspondiente a la Aplicación. El mensaje BIND_APPLICATION se procesa a través del handleMessage de la clase H, y se llama al método handleBindApplication para el proceso de inicio oficial de la Aplicación. Una vez creado el objeto Aplicación, se llevará a cabo el proceso de instalación de ContentProvider, y luego el onCreate de la Aplicación Se llamará al objeto para completar el proceso de inicio de la aplicación.

Veamos la lógica de procesamiento de handleBindApplication to ContentProvider de ActivityThread.

ActivityThread 的 handleBindApplication

        Application app;
        ……
        try {
            //创建 Application 对象
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ……
            // 如果应用正在安装或恢复这类特殊状态,restrictedBackupMode 值为 true。
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    //此处安装 ContentProvider
                    installContentProviders(app, data.providers);
                }
            }

Vemos que cuando se crea el objeto Aplicación, el proceso de instalación de ContentProvider continuará.

  1. El proceso de instalación de ContentProvider se realiza después de que se crea el objeto Aplicación. Cuando se crea el objeto Aplicación, se llamará a su método adjunto y el método adjunto llamará al método attachBaseContext. Esto significa que el proceso de instalación de ContentProvider se ejecutará después de attachBaseContext de la Aplicación y antes del método onCreate de la Aplicación. Generalmente, al procesar soluciones como el refuerzo, el ContentProvider de la aplicación de refuerzo debe procesarse primero en attachBaseContext; de lo contrario, se informará un error (no se puede encontrar la clase durante la instalación), que también es la razón.
  2. Aquí se llama al método installContentProviders para crear, instalar y publicar ContentProvider.
  3. Aquí, data.providers es propiedad de los proveedores de AppBindData. ¿Dónde está asignado ?, aquí lo ignoraremos por ahora y lo analizaremos más adelante.

método installContentProviders

El método installContentProviders es responsable de crear, instalar y publicar ContentProviders.

Código fuente:

    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        // ContentProviderHolder 是对 ProviderInfo 和 远程接口 IContentProvider 的封装,方便进行跨进程通信
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            //创建 ContentProvider 对象,并获取 ContentProviderHolder 对象
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }
        try {
            //调用 AMS 的 publishContentProviders 发布 ContentProvider
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

Aquí se hacen principalmente dos cosas:

  1. Cree un objeto ContentProvider, obtenga el objeto ContentProviderHolder y agregue el objeto ContentProviderHolder generado a un ArrayList (resultados).
  2. Llame a publishContentProviders de AMS, pase los resultados y publique ContentProvider.

Primero miramos el proceso installProvider.

método installProvider

installProvider realiza principalmente la creación e inicialización del objeto ContentProvider y crea el par de Binder correspondiente para llamadas remotas y finalmente guarda la información relevante como un caché en el Mapa.

    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) {
            ……
            //首先获取有效的 Context 对象。
            Context c = null;
            ……

            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
                //创建 ContentProvider 对象,这里是本地对象
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                //获取 ContentProvider 对应的 Binder 对象,以支持远程调用
                provider = localProvider.getIContentProvider();
                ……
                //调用 ContentProvider 对象的 attachInfo 方法,该方法最后会调用 ContentProvider 对象的 onCreate() 方法。
                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 {
            // holder 对象存在,则直接获取
            provider = 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 != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {//存在缓存
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } else {//缓存列表中没找到,则进行创建
                    holder = new ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    //添加到缓存 Map 中
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                retHolder = pr.mHolder;
            } else {
                ……
            }
        }
        return retHolder;
    }

  1. Primero obtenga un objeto Context válido.
  2. Utilice el objeto ClassLoader para cargar la clase ContentProvider y crear el objeto ContentProvider.
  3. Obtenga el objeto Binder correspondiente al ContentProvider para la invocación remota. El método getIContentProvider () devuelve un objeto Transport. Transport hereda de ContentProviderNative y ContentProviderNative hereda de Binder, que implementa el soporte de llamadas a objetos remotos.
  4. Llame al método attachInfo del objeto ContentProvider, que finalmente llamará al método onCreate () del objeto ContentProvider. En este punto, se ha creado el objeto ContentProvider.
  5. El siguiente paso es crear un objeto ContentProviderHolder. provider es un objeto de instancia de Transport, jBinder el objeto Binder correspondiente al ContentProvider; pr es un objeto ProviderClientRecord. Aquí primero cree un objeto ContentProviderHolder y agregue jBinder y pr a la caché del mapa.
  6. Finalmente, se devuelve el objeto ContentProviderHolder.

AMS publishContentProviders publica ContentProvider

El ActivityThread de la APP actual pasa la cola ArrayList generada en el paso anterior al método publishContentProviders del proceso de servicio AMS para publicar todos los ContentProviders registrados en todas las APP.

El ContentProvider (especialmente el objeto Binder remoto) guardado por el método publishContentProviders () El objeto ContentProviderHolder está asociado con el objeto ContentProviderRecord (el objeto ContentProviderRecord se crea cuando la aplicación resuelve el AndroidManifest, y es la información de ContentProvider en la APLICACIÓN almacenada en AMS ). Durante el proceso, el objeto ContentProviderRecord se almacenará en la cola mProviderMap. Cuando otras aplicaciones llamen al ContentProvider de la aplicación actual en el futuro, AMS recuperará directamente el objeto ContentProviderRecord a través de la cola de caché mProviderMap y usará el objeto Binder contenido en ContentProviderRecord objeto (que implementa la interfaz IContentProvider) para transferencia remota.

    public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers) {
        if (providers == null) {
            return;
        }

        enforceNotIsolatedCaller("publishContentProviders");
        synchronized (this) {
            final ProcessRecord r = getRecordForAppLocked(caller);
            if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
            if (r == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                      + " (pid=" + Binder.getCallingPid()
                      + ") when publishing content providers");
            }

            final long origId = Binder.clearCallingIdentity();

            final int N = providers.size();
            for (int i = 0; i < N; i++) {//遍历 providers List,并处理
                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);
                    mProviderMap.putProviderByClass(comp, dst);
                    String names[] = dst.info.authority.split(";");
                    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--;
                        }
                    }
                    if (wasInLaunchingProviders) {
                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                    }
                    // Make sure the package is associated with the process.
                    // XXX We shouldn't need to do this, since we have added the package
                    // when we generated the providers in generateApplicationProvidersLocked().
                    // But for some reason in some cases we get here with the package no longer
                    // added...  for now just patch it in to make things happy.
                    r.addPackage(dst.info.applicationInfo.packageName,
                            dst.info.applicationInfo.longVersionCode, mProcessStats);
                    synchronized (dst) {
                        dst.provider = src.provider;
                        dst.setProcess(r);
                        dst.notifyAll();
                    }
                    updateOomAdjLocked(r, true, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
                    maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
                            src.info.authority);
                }
            }

            Binder.restoreCallingIdentity(origId);
        }
    }

La operación principal del proceso de publicación es un proceso de actualización y almacenamiento de la información relevante del objeto ContentProvider en el AMS.

En este punto, ContentProvider en nuestra APLICACIÓN está listo y otras aplicaciones pueden acceder a ellos en cualquier momento.

Proceso de preparación y análisis de ContentProvider

El proceso de instalación y lanzamiento de ContentProvider se explicó anteriormente. Volvamos a handleBindApplication de ActivityThread. Cuando se llama a installContentProviders (app, data.providers), ¿dónde se asigna el parámetro data.providers?

A continuación, analicemos el proceso de análisis y preparación de ContentProvider.

Proceso de asignación de proveedores de AppBindData

El valor del parámetro data.providers del método installContentProviders es la propiedad de proveedores (List) del objeto AppBindData.

¿Dónde se asigna la propiedad de proveedor del objeto AppBindData? Esto tenemos que comenzar con attachApplicationLocked de ActivityManagerService.

El método attachApplicationLocked de ActivityManagerService.

En el primer párrafo, obtenga la información de registro de ContentProvider del archivo AndroidManifest del paquete de instalación:

        boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
        List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

Aquí, use el método generateApplicationProvidersLocked del servicio PMS para obtener:

    private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
        List<ProviderInfo> providers = null;
        try {
            //远程调用 PMS 获取应用中注册的 ContentProvider 信息
            providers = AppGlobals.getPackageManager()
                    .queryContentProviders(app.processName, app.uid,
                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
                                    | MATCH_DEBUG_TRIAGED_MISSING, /*metadastaKey=*/ null)
                    .getList();
        } catch (RemoteException ex) {
        }
    }

En este momento, obtuvimos la información de ContentProvider del AndroidManifest del paquete de instalación.

El segundo párrafo del método attachApplicationLocked:

final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
            if (app.isolatedEntryPoint != null) {
                // This is an isolated process which should just call an entry point instead of
                // being bound to an application.
                thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
            } else if (instr2 != null) {
                thread.(processName, appInfo, providers,
                        instr2.mClass,
                        profilerInfo, instr2.mArguments,
                        instr2.mWatcher,
                        instr2.mUiAutomationConnection, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.compat, getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions);
            } else {
                thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.compat, getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions);
            }

Esto es principalmente para llamar al método bindApplication del proceso donde se encuentra la aplicación (el método bindApplication del objeto ApplicationThread, aquí está la llamada IPC) para iniciar la aplicación.

El objeto hilo aquí es una instancia de ApplicationThread, y la clase ApplicationThread hereda de IApplicationThread.Stub, que es un objeto Binder que se usa para la comunicación entre procesos (por ejemplo, aquí para comunicarse con el proceso de servicio AMS).

Veamos el método bindApplication de ApplicationThread:

        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 services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions) {
            ……
            ……
            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            sendMessage(H.BIND_APPLICATION, data);
        }

El objeto AppBindData se crea aquí y los proveedores del objeto AppBindData también se asignan aquí. En este punto, también hemos encontrado la fuente de la asignación del proveedor, y sabemos que lo que se almacena en el proveedor es en realidad la información de registro del ContentProvider obtenida del AndroidManifest del paquete de instalación.

Al final de este método, el mensaje H.BIND_APPLICATION se envía a H (un controlador) para el procesamiento del mensaje. Veamos:

                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;

El método handleBindApplication de ActivityThread se llama aquí, que está conectado con el proceso de instalación de ContentProvider, y luego el proceso de creación, instalación y publicación de ContentProvider es el proceso.

En este punto, se ha completado el proceso de análisis del código fuente de ContentProvider.

para resumir


  1. ContentProvider es uno de los cuatro componentes principales de Android. Es un proveedor de contenido. Utiliza una interfaz unificada para proporcionar contenido de datos para las aplicaciones.

  2. ContentProvider utiliza esencialmente el mecanismo IPC del sistema Android (modelo Binder). Cuando se inicia la aplicación, analiza la información de registro del ContentProvider del archivo AndroidManifest y, después de procesarla, la pasa al servicio AMS y crea el objeto ContentProviderRecord correspondiente. y lo guarda en el AMS en servicio. Cuando la aplicación cliente accede a los datos a través de ContentProvider, consultará y obtendrá el objeto ContentProviderRecord correspondiente a través del servicio AMS, obtendrá el objeto ContentProvider Binder real y luego se podrá realizar el acceso a los datos.

  3. El método attachApplicationLocked de AMS utiliza el servicio PMS para obtener la información de registro de ContentProvider del archivo AndroidManifest del paquete de instalación.

  4. AMS llama al método bindApplication del proceso donde se encuentra la aplicación (el método bindApplication del objeto ApplicationThread, aquí está la llamada IPC) para iniciar la aplicación, y pasa la información de ContentProvider como parámetro a la aplicación.

  5. El método bindApplication del ApplicationThread del proceso App recibe la información ContentProvider pasada, crea un objeto AppBindData y asigna la información ContentProvider a los proveedores del objeto AppBindData.

  6. Luego, a través de la ocurrencia del mensaje H.BIND_APPLICATION, se entrega a H (un Handler) para el procesamiento del mensaje, y finalmente se llama al método handleBindApplication de ActivityThread para realizar el proceso de creación, instalación y publicación de ContentProvider.

  7. En el método handleBindApplication, llame al método installContentProviders para crear, instalar y publicar ContentProvider.

  8. El método installContentProviders crea un objeto ContentProvider, obtiene el objeto ContentProviderHolder y agrega el objeto ContentProviderHolder generado a un ArrayList (resultados).

  9. Llame a publishContentProviders de AMS, pase los resultados y publique ContentProvider.

  10. La operación principal del proceso de publicación es un proceso de actualización y almacenamiento de la información relevante del objeto ContentProvider en el AMS.


PD: Para obtener más artículos, consulte la serie de artículos -> columna "Análisis de los principios subyacentes de Android" .
PD: Para obtener más artículos, consulte la serie de artículos -> columna "Análisis de los principios subyacentes de Android" .
PD: Para obtener más artículos, consulte la serie de artículos -> columna "Análisis de los principios subyacentes de Android" .

Supongo que te gusta

Origin blog.csdn.net/u011578734/article/details/111812636
Recomendado
Clasificación