Android四大组件系列10 ContentProvider原理

一 概述

ContentProvider 用于提供数据的统一访问格式,封装底层的具体实现。对于数据使用者来说,无需知晓数据的来源是数据库、文件,或者网络,只需简单地使用 ContentProvider 提供的数据操作接口:增(insert)、删(delete)、改(update)、查(query) 即可,非常方便。

在 Android 系统中通讯录或者短信的数据,都是以 ContentProvider 的形式提供的,ContentProvider 本质上就是把数据存储在 SQLite 数据库中,另外 ContentProvider 读取数据时使用了匿名共享内存(ASM),ASM 实质上也是通过 Binder 通信的。

  • ContentProvider 的 onCreate 先于 Application 的 onCreate 启动,在应用启动的时候,会通过 ActivityThread 的 attach 方法,通过远程调用 AMS 的 attachApplication 来将 ApplicationThread 传递给 AMS,同时在该方法中会调用 ApplicationThread 的 bindApplication 方法,同样也是跨进程的。该方法最终跳转到 ActivityThread 中运行,具体为 handleBindApplication,进而调用 installContentProviders 完成 ContentProvider 的加载
  • 外界无法直接访问 ContentProvider,只能通过 AMS 根据 URI 来获取对应的 ContentProvider 的 Binder 接口 IContentProvider,然后再访问相应数据

ContentProvider 可以做成多实例,通过属性控制,但是现实中很少采用因为浪费开销。外界访问其增删改查的任何一个方法都会导致 ContentProvider 的创建,并且拉起进程。

比如通过 query 方法启动 ContentProvider:

  1. 通过 acquireProvider 来获取 IContentProvider 对象
  2. 先检查本端 ActivityThread 中是否存在 ContentProvider,如果存在就直接返回(返回的是 IContentProvider 客户端接口对象)。如果没有,就调用远程 AMS.getContentProvider 方法
  3. ContentProvider 的启动伴随着进程的启动,启动进程靠的是 AMS 的 startProcessLocked,新进程启动后其入口方法为 ActivityThread.main() 方法,然后在 attach 方法中通过 attachApplication 方法将 ApplicationThread 作为参数传递给 AMS
  4. AMS 的方法中又调用了ApplicationThread.bindApplication
  5. ActivityThread 的 bindApplication 发送 BIND_APPLICATION 消息给 mH 使得 ActivityThread 完成一系列工作,包括(按步骤顺序):
  • 创建 ContextImpl 和 instrumrent
  • 创建 Application 对象
  • 启动当前进程的 ContentProvider 并调用其 onCreate 方法,将创建好的 Contentprovider 保存在 AMS 中的 providerMap 中,这样外部调用者就能直接从 AMS 中获取 ContentProvider 了,然后调用其 onCreate 方法
  • 调用 Application 的 onCreate 方法

上述四个步骤之后,外部就能在 AMS 中拿到 ContentProvider 访问其接口了,但拿到的其实是 Binder 类型对象 IContentProvider。其具体实现是 ContentProvider.Transport,所以外界调用 query 方法时候,是通过调用 ContentProvider.Transport 中的 query 方法,其远程调用 ContentProvider.query 方法,结果通过 Binder 返回给调用者,其它方法调用类似。

1.1 ContentProvider

ContentProvider 作为 Android 四大组件之一,并没有 Activity 那样复杂的生命周期,只有简单的 onCreate 过程。

ContentProvider 是一个抽象类,当要实现自己的 ContentProvider 类时,需继承于 ContentProvider,并且实现以下六个 abstract 方法即可:

  • insert(Uri, ContentValues):插入新数据
  • delete(Uri, String, String[]):删除已有数据
  • update(Uri, ContentValues, String, String[]):更新数据
  • query(Uri, String[], String, String[], String):查询数据
  • onCreate():执行初始化工作
  • getType(Uri):获取数据 MIME 类型

1.2 类图

在这里插入图片描述

1.3 重要成员变量

类名 成员变量 含义
AMS CONTENT_PROVIDER_PUBLISH_TIMEOUT 默认值为10s
AMS mProviderMap 记录所有 contentProvider
AMS mLaunchingProviders 记录存在客户端等待 publish 的 ContentProviderRecord
ProcessRecord pubProviders 该进程创建的 ContentProviderRecord
ProcessRecord conProviders 该进程使用的 ContentProviderConnection
ActivityThread mLocalProviders 记录所有本地的 ContentProvider,以 IBinder 以 key
ActivityThread mLocalProvidersByName 记录所有本地的 ContentProvider,以组件名为 key
ActivityThread mProviderMap 记录该进程的 contentProvider
ActivityThread mProviderRefCountMap 记录所有对其他进程中的 ContentProvider 的引用计数
  • CONTENT_PROVIDER_PUBLISH_TIMEOUT:provider 所在进程发布其 ContentProvider 的超时时长为 10s,超过 10s 则会被系统所杀
  • mProviderMap: AMS 和 ActivityThread 都有的一个同名的成员变量,AMS 的数据类型为 ProviderMap,而 AT 则是以 ProviderKey 为 key 的 ArrayMap 类型
  • mLaunchingProviders:ArrayList 的每一项是一个 ContentProviderRecord 对象,所有的存在 client 等待其发布完成的 contentProvider 列表,一旦发布完成则相应的 contentProvider 便会从该列表中移除
  • mLocalProviders 和 mLocalProvidersByName:都是用于记录所有本地的 ContentProvider,不同的只是 key

1.4 query流程图

在这里插入图片描述

二 发布ContentProvider

通过 ContentProvider 共享数据时,需要编写一个类继承 ContentProvier,创建数据库,并在 AndroidManifest 声明该 Provider,这样其他的进程才可以通过 ContentResolver 去查询其共享的信息。

先来看一下应用是如何发布 ContentProvider,提供给其它应用使用的。ContentProvider 一般是在应用进程启动的时候启动,是四大组件中最早启动的。

发布 ContentProvider 分两种情况:Provider 进程未启动,Provider 进程已经启动但未发布。

  • Provider 进程未启动
    system_server 进程调用 startProcessLocked 创建 provider 进程,并 attach 到 system_server 后,通过 binder 机制到 Provider 进程执行bindApplication方法,见 2.1 节
  • Provider 进程已经启动但未发布
    如果发现 provider 进程已经存在且 attach 到 system_server,但对应的 provider 还没有发布,则通过 binder 机制到 provider 进程执行scheduleInstallProvider 方法,见2.7节

这两种情况最后都会走到 installProvider 这个方法。

2.1 Provider进程未启动

2.1.1 ApplicationThread.bindApplication

ActivityThread.java#ApplicationThread

public final void bindApplication(String processName, ApplicationInfo appInfo,
              List<ProviderInfo> providers, ComponentName instrumentationName,
              ProfilerInfo profilerInfo, ......) {
   
              
          AppBindData data = new AppBindData();
          data.processName = processName;
          data.appInfo = appInfo;
          // provider
          data.providers = providers;
          ...
          sendMessage(H.BIND_APPLICATION, data);
}

发送 BIND_APPLICATION 消息到主线程。

需要注意的是参数 providers,这个是从 AMS 传递过来的,是本进程注册的 ContentProvider 信息,其中 AMS 是通过 PKMS 得到这些注册在 AndroidManifest.xml 文件中的 ContentProvider 信息的。

2.1.2 ActivityThread.handleMessage

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;
               }
               ...
  }

主线程收到 BIND_APPLICATION 后,执行 handleBindApplication 方法。

2.1.3 ActivityThread.handleBindApplication

ActivityThread.java

private void handleBindApplication(AppBindData data) {
   
       ......
       Application app;
       final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
       final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
       try {
   
           // 实例化 Application,会调用 attachBaseContext 方法
           app = data.info.makeApplication(data.restrictedBackupMode, null);
           
           // Propagate autofill compat state
           app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
           mInitialApplication = app;
           // 不是限制备份模式
           // don't bring up providers in restricted mode; they may depend on the
           // app's custom Application class
           if (!data.restrictedBackupMode) {
   
               if (!ArrayUtils.isEmpty(data.providers)) {
   
                   // 安装 ContentProvider,见2.4节
                   installContentProviders(app, data.providers);
               }
           }
           try {
   
               // 调用 Application 的 onCreate 方法
               mInstrumentation.callApplicationOnCreate(app);
           } catch (Exception e) {
   
               ......
           }
       } 
       ......
   }

2.1.4 ActivityThread.installContentProviders

ActivityThread.java

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

        for (ProviderInfo cpi : providers) {
   
            ......
            //安装 provider,见2.1.5节
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
   
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
   
            //发布 provider 见2.2.1节
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
   
            throw ex.rethrowFromSystemServer();
        }
}

2.1.5 ActivityThread.installProvider

ActivityThread.java

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) {
    // 这里 holder 为空
          ......
          Context c = null;
          ApplicationInfo ai = info.applicationInfo;
          // 初始化并赋值 context
          ......
          try {
   
              final java.lang.ClassLoader cl = c.getClassLoader();
              LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
              ......
              // 通过反射,创建目标 ContentProvider 对象
              localProvider = packageInfo.getAppFactory()
                      .instantiateProvider(cl, info.name);
              //获取 contentprovider        
              provider = localProvider.getIContentProvider();
              if (provider == null) {
   
                  ......
                  return null;
              }
              ......
              // XXX Need to create the correct context for this provider.
              // 回调目标 ContentProvider.this.onCreate() 方法
              localProvider.attachInfo(c, info);
          } catch (java.lang.Exception e) {
   
              ......
              return null;
          }
      } else {
   
          provider = holder.provider;
          if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                  + info.name);
      }
      
      // retHolder 赋值,用于将 contentProvider 放入缓存
      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);
                  mLocalProviders.put(jBinder, pr);
                  mLocalProvidersByName.put(cname, pr);
              }
              retHolder = pr.mHolder;
          } else {
   
              ......
          }
      }
      return retHolder;
  }

这里主要是通过反射生成 ContentProvider,并回调其 onCreate 方法,然后生成 ProviderClientRecord 对象,并把这个对象添加到 mLocalProviders 和 mLocalProvidersByName 中,最后对 ContentProviderHolder 进行赋值并返回。

2.1.6 AMS.publishContentProviders

ActivityManagerService.java

public final void publishContentProviders(IApplicationThread caller,
            List<ContentProviderHolder> providers) {
   
        ......
        synchronized (this) {
   
            final ProcessRecord r = getRecordForAppLocked(caller);
            ......
            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);
                    // 将该 provider 添加到 mProviderMap
                    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 中移除
                            mLaunchingProviders.remove(j);
                            wasInLaunchingProviders = true;
                            j--;
                            launchingCount--;
                        }
                    }
                    if (wasInLaunchingProviders) {
   
                        // 移除超时消息
                        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
                    }
                    synchronized (dst) {
   
                        dst.provider = src.provider;
                        dst.proc = r;
                        // 唤醒客户端的 wait 等待方法
                        dst.notifyAll();
                    }
                    // 更新 adj
                    updateOomAdjLocked(r, true);
                    maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
                            src.info.authority);
                }
            }

            Binder.restoreCallingIdentity(origId);
        }
    }

ContentProviders 一旦 publish 成功,则会移除超时发布的消息,并调用 notifyAll 来唤醒所有等待 client 端进程。

2.2 Provider 进程启动但未发布

2.2.1 ApplicationThread.scheduleInstallProvider

ActivityThread.java#ApplicationThread

@Override
public void scheduleInstallProvider(ProviderInfo provider) {
   
    sendMessage(H.INSTALL_PROVIDER, provider);
}

public void handleMessage(Message msg) {
   
      ......
      case INSTALL_PROVIDER:
            handleInstallProvider((ProviderInfo) msg.obj);
            break;
      ......
}

ContentResolver#query 分为2个主要步骤:获取 Provider 和 query 查询。

2.2.2 ActivityThread.handleInstallProvider

public void handleInstallProvider(ProviderInfo info) {
   
       final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
       try {
   
           installContentProviders(mInitialApplication, Arrays.asList(info));
       } finally {
   
           StrictMode.setThreadPolicy(oldPolicy);
       }
}

这个方法之后就是 2.1.4 节的流程 installContentProviders

三 查询 ContentResolver

一般使用 ContentProvider 的时候,会先得到 ContentResolver,之后通过 uri 可以获取相应的信息,下面就从查询方法开始,分析这个过程的源码流程。

ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://com.skytoby.articles/android/1");
Cursor cursor = contentResolver.query(uri, null, null, null, null);

3.1 ContextImpl.getContentResolver

ContextImpl.java

@Override
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值