一图解惑之Android调用ContentProvider基本流程

准备

  • 基本使用
    ContentProvider作为四大组件之一,在开发过程中经常被使用到。我们的常规做法是定义一个ContentProvider,然后在使用的时候使用ContentResolver提供的接口来访问数据。一个进程可以访问自己定义的ContentProvider,也可以访问其他进程定义的ContentProvider。
  • ContentProviderHolder:
    ContentProviderHolder是定义在IActivityManager类中的内部类,它的作用主要是承载ContentProvider的信息和ContentProvider接口内容,并在SystemServer和App进程间传递。其主要成员变量如下:
    1. info :ProviderInfo 描述该ContentProvider的信息。
    2. provider :IContentProvider 描述该ContentProvider对应的ContentProvider接口,它是一个Binder代理对象。
  • 大致流程:假设App B定义了一个ContentProvider,App A需要访问该ContentProvider。整个调用过程简单梳理会经过以下几个步骤:
    1. App A先向SystemServer查询要访问的ContentProvider接口。
    2. SystemServer将查询到的ContentProvider接口返回给App ,该ContentProvider接口是定义在App B中的ContentProvider的Binder代理。
    3. App A再通过该Binder代理调用定义在App B中的ContentProvider的逻辑。
  • 本地缓存:ActivityThread内部会缓存当前进程已经访问过的ContentProvider或者是当前进程内自己定义的ContentProvider。缓存在其成员变量mProviderMap中。这里注意参考上一篇一图解惑对比区分,在AMS中也有个同名的成员变量mProviderMap,可以大致理解为他们都用来缓存ContentProvider接口,只不过一个是在本地进程中缓存,一个是在SystemServer中全局缓存。

上图

这次不墨迹,先上图再说:


Provider时序图 (1).png

查看原图

图中每个步骤都标了序号,整个过程涉及到3个进程间的通信。App A是ContentProvider的使用方,App B是ContentProvider的定义方,SystemServer是系统进程。下面就按序号顺序做简要说明。
简要总结:
1.业务逻辑层通过直接使用ContentResolver提供的接口去访问数据(如query,insert等等)。
2.ContentResolver需要获取到ContentProvider接口才能对该ContentProvider访问,所以这里ContentResolver先通过ActivityThread去获取一个ContentProvider接口。
3.上面提到ActivityThread的mProviderMap成员会缓存已经获取的ContentProvider接口或定义在自己进程内的ContentProvider接口。所以在这里ActivityThread会先通过acquireExistingProvider()方法在本地查找是否已有缓存。如果有,则直接跳到第13步返回结果给ContentResolver。如果没有则会继续通过进程间通信向SystemServer中的AMS模块查询ContentProvider接口。
4.向AMS查询ContentProvider接口。在AMS的mProviderMap成员中保存了当前运行的ContentProvider接口,查询过程中大方向会有两个分支:如果要查询的ContentProvider接口不在mProviderMap缓存中,则一般情况下也说明该ContentProviders所在的进程还没启动,则继续往下执行第5步启动该进程。否则直接跳到第12步,将从mProviderMap成员中查询到的结果返回给ActivityThread。
5.启动要查找的ContentProvider所在的进程。接下来便是大家熟知的应用进程启动的过程。这里主要关注第10步和第11步。
6~9.应用进程启动初始化过程中与AMS的一系列交互。
10.通过调用installContentProviders()方法本地安装ContentProvider,这里可以简单理解所谓本地安装就是填充ActivityThread中保存ContentProvider接口的缓存,但是要注意当前的ActivityThread是表示定义ContentProvider的进程(App B),而不是使用ContentProvider的进程(App A)。
11.回调AMS的publishContentProviders()方法,将ContentProvider接口传给AMS。至此AMS内部的mProviderMap成员也获取到了ContentProvider接口并保存。当下次有进程再请求访问同一个ContentProvider接口时,就可以直接从AMS的mProviderMap成员中获取该ContentProvider接口直接返回给调用方了。
12.返回查询结果给ContentProvider的使用方(App A)。这里要注意返回的数据类型是ContentProviderHolder,它继承了Parcelable接口。因此可以在进程间传递,使用方需要的ContentProvider接口就保存在其成员变量provider中。
13.ContentProvider的使用方将查找回来的ContentProvider接口返回给ContentResolver。
14.ContentResolver通过ContentProvider接口再次发起Binder通信向真正ContentProvider的实现方(App B)发送数据访问请求。
15.ContentProvider实现方(App B)将数据返回给Content使用方(App A)。
16.ContentResolver完成数据的访问工作,将查询结果返回给业务逻辑层。

整个过程有可能只涉及一个进程(App A),比如以下几种情况:

  • ContentProvider的使用方(App A)已经有要访问的ContentProvider接口的缓存。
  • ContentProvider的定义与使用在相同的进程。

也可能涉及两个进程(App A和SystemServer):

  • ContentProvider的定义与使用在不同的进程,但是AMS中已经有该ContentProvider接口的缓存。

也可能涉及三个进程(App A,App B和SystemServer):

  • ContentProvider的定义与使用在不同的进程,并且AMS中也没有该ContentProvider接口的缓存。

And Then

我的分析默认是按照Android N原代码的逻辑来分析的。如果在国产机上发现逻辑与之有冲突不要惊慌,国产机ROM在AOSP基础上做了很多“优化”。或者不同的Android版本之间可能也存在微小差别。比如我的测试过程中就发现手上一台华为在App B定义了ContentProvider,但是App B进程还没有启动时,App A使用该ContentProvider并不会把App B拉起,而是抛一个Warning:Unknown URL content://xxxxxxxxxxxxx。
四大组件中之所以没有先写Activity和Broadcast是因为网上他们哥两的介绍相对多的多。不过后面我也会抽空把一图解惑系列的四大组件都补全。一图解惑系列的目的是希望尽量简单直接的将基本的原理逻辑说清楚,如果需要了解内部细节,当然还是需要Read The Fucking Source Code。

Thank you~

你可能感兴趣的:(一图解惑之Android调用ContentProvider基本流程)