Android 4大组件

1.   Android 4组件简介

Android系统中有4组件: ActivityServiceBroadcastContentProvider.

 

一个Android应用程序(Apk可以包含一个或多个组件. 开发者的角度来说, 我们想一个Apk, 一般会弄一个文件夹, 文件夹下面会有Android.mkJava代码res文件, 另外还有一个重要的文件AndroidManifest.xml. 例如下面这个:


 

AndroidManifest.xml里面就定义了该Apk到底包含哪些组件, 例如下图:

红框代表着定义了一个Activity组件, 类似, 我们也可以用 <service  …… </service>定义一个service组件; <receiver …… </receiver>定义一个BroadcastReceiver组件; <provider …… </provider> 定义一个ContentProvider组件.

每个组件下面会有很多android:xxxx形式的属性.

 

AndroidManifest.xml里面定义的这些信息谁负责去解析的呢? 答案是PackageManagerService. 解析的时机有两个: 对于那些预装APK, Android启动过程中解析的; 对于后装APK, 是在install过程中解析的. PKMS把所有解析出来的信息存储起来, 这样通过PKMS我们就能知道系统中有哪些APK每个APK下面有哪些组件以及每个组件有哪些属性.

 

PKMS里面的这些信息有什么作用? 我们想启动一个Apk时(更准确的说法是启动某个Apk某个组件时, 告知系统的其实是一字符串. 例如通过am从控制台启动计算器的命令 am start -n com.android.calculator2/com.android.calculator2.Calculator, 这个字符串的标准格式是包名(package)/包名.组件 (包名代表一个APK的属性, 它也是AndroidManifest.xml定义的. 当系统收到这串字符后, 借助PKMS存储的信息, 通过包名就能知道是哪个APK, 通过包名.组件名能知道启动哪个组件.

 

组件的启动是流程是本文的重点, 我们将在接下来的文章中详细分析.

1.1. Android 4组件进程的关系

Linux, 我们说一个程序就是一个进程, Android也是, 所有的组件都是运行在进程上的.

 

默认情况下, 一个Apk就是一个进程, 所有Apk组件都运行在它的进程. 这是最常见的情况, 除此之外, 一个Apk组件可以选择性的运行在独立的进程上, 这样一个Apk有多个进程了. 另外, 多个Apk组件也可以运行在同一个进程上, 这样做的目的主要是为了节省系统资源.

 

组件运行在哪个进程上是由组件的android:process属性决定的: 如果没有指定该属性, 那就是默认运行在Apk进程上; 如果指定了, 就是运行与独立进程或共享进程上. 到底是独立进程还是共享进程, 取决于android:process命名:

  • : 号开头, 代表新建一个线程 , 例如 android:process = :proc_name, 进程的名字叫package_name.proc_name
  • 以字符开头, 代表该组件运行与某个共享进程(前提是要有相应的权限), 例如 android:process = com.xxxx.yyy, 进程名就是com.xxxx.yyy

 

启动组件的时候, 系统(这个系统就是ActivityManagerService会检查对应的进程是否存在, 如果不存在, 系统会首先创建进程, 然后在运行组件. 里面的细节后文会详说, 不同组件的进程创建流程都是一样的, 因此后文会单独先讲进程创建流程, 然后在分别讲各个组件的启动流程.

1.2. Android 4组件AMS的关系

AMSAndroid所有组件的总管家, 所有组件所依附的进程、各个组件的状态等等都有AMS统一管理.

AMS运行system_server进程, system_server进程是由Zygote进程fork而来, system_serverZygote孵化的第一个进程 Zygote进程是由init进程通过解析init.rc文件后fork生成的).

 

AMS实现binder服务端, ServiceManger注册. 所有AMS交互的进程都可以通过ServiceManager获取AMS客户端代理, 然后通过binder方式与AMS通信. Android所有组件所依附的应用进程都是通过这种方式与AMS交互.

 

我们想启动一个组件, 发起方例如Launch界面点某个应用的图标, Launch就是发起方, 它想启动某个应用Activity组件通过binder方式把这个请求发给AMS, AMS收到请求后会决定是否启动新的进程创建组件对应的数据结构以存储组件信息、最后通知组件所在进程执行组件生命周期的相关函数(onCreate, onXXX.

上面说了三件事, 看似简单, 其实每一件事都不简单:

         首先, AMS决定是否启动新的进程, 通过什么条件决定? 这个倒还简单: AMS保存了所有已存在的应用进程的信息(每个进程对应一个ProcessRecord结构), 对于要新启动的组件, 通过PMKS可以查询到该组件想要依附的进程的名字(这个名字是在AndroidManifest.xml里面定义的), 然后通过这个名字查询当前系统中是否存在该进程, 如果不存在, 则创建并启动新的进程.

 

进程的创建过程就比较麻烦了, 我们需要单独的一章来说明, 参考2.1 进程创建流程

         其次, 创建组件对应的数据结构. 这个还比较好理解, AMSActivityRecordServiceRecordContentProviderRecordBroadcastRecord4数据结构, 分别对应不同的组件. 小节最后会列出ApkAMS涉及到的各种java文件, 大家有个宏观印象.

         最后, 通知组件所在进程以执行组件生命周期的相关函数. 这里组件所在的进程(简称其为Apk进程)可能是前面新创建的, 可能是之前已经存在的. 不管怎么说, 这个Apk进程与AMS两个不同的进程, AMS如何向它发送消息? 答案是通过binder, Apk进程会实现binder服务端, 然后AMS拿到它的客户端, 进而AMS可向Apk进程发送消息了.

 

不过这里有一个隐蔽的点, Apk虽然实现了binder服务端, 但是它并没有向ServiceManager注册. , 既然没有注册, AMS是如何获取到客户端代理的呢? 答案Android实现了一套与ServiceManager类似的东西, ActivityManagerProxy.javaActivityManagerNative.java部分代码实现, 唯一的功能就是获取Apk binder服务的客户端代理, 把这个代理交给AMS. 后文会详述这个细节. 参考《2.2 建立新进程与AMS交互通道》.

Android系统这样做的目的可能是觉得Apk类型binder服务可能会有很多, 不想撑爆ServiceManager.

 

AMS创建了一个线程, 名字ActivityManager. 这是一个looper + handler性质的线程, 它的地位很重要, AMS很多事情都是在这个线程中完成的. 注意这个线程是存在于system_server进程中的, looper+handler模式只是用于进程内部线程间的交互, 因此只有system_server进程中的代码才能向这个线程发送message, 从而让这个线程开始工作.

我们的疑问是这个线程与组件有何关系? 实际上, 组件AMS交互的唯一通道是binder. 组件需要AMS什么事时, 会通过binder发送请求给AMS.  AMS处理请求时, 运行在binder线程中的(binder线程由系统自动创建和销毁, 详见binder一文的介绍, 如果有需要, 这个binder线程可以发送messageActivityManager线程, 帮忙处理一些耗时的事情.

 

理解AMS这些进程的关系, 助于分析ANR问题. 关于ANR, 后文细说.

1.2.1.  从文件路径的角度看组件AMS关系

APP进程

  • frameworks/base/core/java/android/app/
    • ActivityThread.java 入口函数main
      • main里面开启了 loop循环 + handler处理, 它就是通常所说的UI主线程, 主要任务是控制组件的生命周期 (也就是回调组件的各种OnXXX函数).

 

  //ApplicationThread的主要目的是接收并处理AMS发过来的消息  class ApplicationThread 继承并实现了ApplicationThreadNative, 相当于Bn端.

  • ActivityThread.java中的ApplicationThread 类
  • ApplicationThreadNative.java中的ApplicationThreadNative类
  • IApplicationThread.java

 

  • Activity.java : Activity组件
    • .startActivity 创建Activity
  • Service.java
  • Instrumentation.java
  •  

 

  //以下3个代表AMS服务的Bp

  • ActivityManager.java
  • ActivityManagerNative.java中的ActivityManagerProxy
  • IActivityManager.java
  •  
  • ContextImpl.java
    • .startService 创建service
    • .registerReceiver 注册broadcastReceiver
  • frameworks/base/core/java/android/content/
    • Context.java
    • ContextWrapper.java
    •  
    • BroadcastReceiver.java
    • IntentFilter.java

System_Server(AMS) 进程

  • frameworks/base/core/java/android/app/
    • ApplicationThreadNative.java中的ApplicationThreadProxy, 它相当于Bp, AMS会利用它向APP进程发送消息
    • ActivityManagerNative.java中的ActivityManagerNative, 它相当于BinderBn, AMS会继承它并实现相关的函数
  • frameworks/base/services/core/java/com/android/server/am/
    • ActivityManagerService.java

 

//创建activity相关

  • ActivityStackSupervisor.java
  • ActivityStack.java

 

//创建service相关

  • ActiveServices.java

 

//创建contentprovider相关

  • AMS.java :: getContentProviderImpl

 

//创建Broadcast相关

  • BroadcastQueue.java
  • BroadcastFilter.java
  • ReceiverList.java

 

  • ActivityRecord.java : 与Activity组件一一对应, 用于在AMS中记录某个Activity的相关信息
  • ServiceRecord.java : 与Service组件一一对应, 用于在AMS中记录某个Service的相关信息
  • ContentProviderRecord.java  : 与ContentProvider组件一一对应, 用于在AMS中记录某个contentprovider的相关信息
  • BroadcastRecord.java : 与Broadcast组件一一对应, 用于在AMS中记录某个Broadcast的相关信息
  • ProcessRecord.java : 与进程一一对应, 用于在AMS中记录某个进程的相关信息

2.   Android进程创建

2.1. 新进程创建流程

Android进程创建的宏观流程是这样的:

当发起进程想启动某个组件时, 会通过binder告知system_server进程中的AMS, AMS判断是否需要为组件新建进程, 如果需要, 则通过socket发送消息给Zygote进程. Zygote进程中有个runSelectLoop循环, 专门负责读取socket消息并处理这些消息.

 

这里不打算分析代码调用细节, 网上有篇不错的介绍, 参考system_server发起请求.

 

需要提醒的两点:

         首先是socket通信管道是如何建立的以及socket名称是什么? 答案如下:

Process.start流程调用openZygoteSocketIfNeeded建立与Zygotesocket通信管道. Socket名称有两个, 优先选择ZYGOTE_SOCKET, 其次选择 SECONDARY_ZYGOTE_SOCKET.

         第二进程的创建一个同步过程, system_server进程通过socket发送请求给Zygote进程, 会一直等待, 直到Zygote完成进程创建反馈结果. 代码如下:

writer.flush代表通过socket请求发送给另一端的Zygote; inputStream.readInt代表等待Zygote返回执行结果. 并且这个等待没有考虑超时的情况!

 

我们知道Linux中创建新进程需要使用fork调用, Android也是一样的. Zygote收到socket管道过来的请求后, 会通过forkAndSpecialize函数最终执行fork操作, 一块的代码调用流程可以参考zygote创建进程.

 

fork调用后, 代码一分为二: 一部分在原进程(也就是Zygote进程)中运行; 一部分在新创建的进程(也就是Apk进程)中运行. 这一块的代码调用流程请参考新进程运行. 图示如下:

从上图可知, handleChildProc新进程执行的第一个函数, 会做一些必要的初始化动作, 最终调用到ActivityThread.main方法.

 

从应用程序的角度来说, 不关心底层细节, 上述流程对应用都是透明的, ActivityThread.main相当于是Apk进程的入口函数, 可以简单的理解它为Linux程序中的main().

 

ActivityThread.main后就是组件的天下了, 组件即将开始运行. 不过在组件运行之前还有一件重要的事情, 就是建立与AMS交互通道. 下节将会说明这流程.

2.2. 建立进程AMS交互通道

AMS总管家, 所有Apk进程都要跟它交互, 新建的这个Apk进程也不例外.

所谓交互就是能互相发送消息, ApkAMS发送消息很简单, 通过ServiceManager获取AMS客户端代理即可. 麻烦的是AMSApk进程发消息怎么办?

 

前文说道Apk进程也会实现一个binder服务端, 但是它并没有向ServiceManager注册, AMS如果拿到这个服务的客户端呢? 请看下面这幅图:

当调用Fork创建新的进程后, 新进程的ActivityThread.main开始运行. ActivityThread.main中做了一件重要的事情, 就是图中的第三步, 调用AMP.attachApplication().

AMP.attachApplication

AMPActivityManagerProxy的缩写. ActivityManagerNative.java中定义了两个类, 一个是ActivityManagerNative, 另一个是ActivityManagerProxy. AMN是服务端, AMS继承并实现相关的功能函数, AMP则是客户端, 供其它进程调用AMS服务.

 

新建的这个APK进程通过ServiceManager能轻易拿到AMP, 然后调用它的attachApplication函数. 我们看下代码:

AMP. attachApplication():

通过binder通信, 随机跳转到AMN.onTransact. ATTACH_APPLICATION_TRANSACTION

上面代码中的两个红框, 通过匿名binder机制, AMS拿到了APK服务的客户端. 关于匿名binder, 参考Binder一文的《7.2              匿名binder通信.

 

data.readStrongBinder其实只是相当于拿到了一个句柄, ApplicationThreadNative. asInterface把这个句柄封装成了ApplicationThreadProxy, 以后AMS用这个ATP就可以与APK服务通信了.

ApplicationThreadNative.java中也定义了两个类: 一个是ApplicationThreadNative, 相当于服务端; 另一个ApplicationThreadProxy, 相当于客户端. ActivityThread.java中的ApplicationThread继承了ATN, 并实现了服务端的相关接口; ATP则作为客户端供其它进程调用APK服务.

AMS类似, ApplicationThread收到来至binder的请求后, 多数情况下会发消息给ActivityThread.main这个主线程, 主线程里面有一个looper会循环读取和处理这些消息(AMS收到来至binder的请求后会发消息给ActivityManager线程处理).

 

至此, APKAMS之间的双向通信通道就建立起来了 : APK进程用AMP这个客户端与AMS通信; AMS进程则利用ATP这个客户端与APK通信. 最后用一副图片总结一下:

2.3. 准备启动组件

注意这里所说的准备工作只针对与新建进程. 如果需要启动的组件所依附的进程已经存在, 则不需要创建新进程, 因此也不会走下面的准备流程了.

 

2.2节说新进程创建完毕之后, 会通过AMP.attachApplicationAMS报道, 从而建立起AMSAPK的通信通道. 下面我们细看一下AMS收到attachApplication请求后会做哪些事情.

AMS. attachApplicationLocked

attachApplicationLocked主要做了两件事: 第一件是调用ATP.bindApplication, 通知APK进程做一下准备工作; 第二件是查询是否有需要启动的组件.

ATP.bindApplication

ATP.bindApplication最终会调用到ActivityThread.java中的ApplicationThread. 我们在这里只是简单总结一下AT. ApplicationThread所做的事情, 关于代码细节, 请参考标题链接.

 

AT. ApplicationThread会发送SET_CORE_SETTINGSBIND_APPLICATION这两个消息给APK主线程. 主线程收到消息后做如下处理:

SET_CORE_SETTINGS

          如果core settings发生改变, 则请求所有的activities重启

BIND_APPLICATION

          设置进程名, 也就是说进程名是在进程真正创建以后的BIND_APPLICATION过程中才取名

          低内存设备, persistent进程不采用硬件加速绘制,以节省内存使用量

          获取LoadedApk对象

          创建ContextImpl上下文, 详见理解Android Context

          创建目标应用Application对象, 详见理解Application创建过程

查询是否有需要启动的组件

AMS.attachApplicationLocked中有如下代码, 不多解释了:

 

ContentProvider的启动请阅读第6.

3.   Activity

我们先用一副图来看下Activity启动的整体流程:

         首先, 启动Activity的既可以是Launch进程, 比如桌面Launcher或者某个应用; 可以是APK进程本身, 例如启动本APK内部的另外一个Activity.

         不管是哪种启动方式, 它们在走完红色的共同道路后会分道扬镳 : 如果需要创建新的APK进程则走蓝色道路(新进程创建的细节参考第2章); 否则走紫色道路.

         之后又殊途同归, 都进入realStartActivityLocked方法, 走黑色道路.

         进程间的通信机制有的是binder, 有的是socket, 都已在图中标明.

3.1. StackTaskActivityProcess的关系

从图中可以看出,AMS是按层次关系进行管理所有的Activity的。

1)         ActivityStackSupervisor的成员mStacksAMS管理层次的顶层,类型为ArrayList<ActivityStack>,它只包含两个stack: mHomeStackmFocusedStackmHomeStack保存Luncher AppTask(activity), mFocusedStack保存非Laucher AppTask(activity)

2)         mHomeStackmFocusedStack的类型是ActivityStack,它有一个ArrayList<TaskRecord>类型的成员mTaskHistory,用于存储TaskRecord

3)         TaskRecord有一个类型为ArrayList<ActivityRecord>的成员mActivities,用于保存属于该Task的所有Activity。类型为ActivityStack的成员stack,记录所属的numActivities记录当前TaskActivity的数量。

4)         每个ActivityRecord会对应到一个TaskRecordActivityRecord中类型TaskRecord的成员task,记录所属的Task

5)         AMS通过辅助类ActivityStackSupervisor来操作Task, 通过ActivityStack操作Activity。想找到某个Activity,需要按层次查找:先找到对应的, 再找到中的Task,再在该Task中查找Activity

6)         同一个TaskRecordActivity可以分别处于不同的进程中,每个Activity所处的进程跟所处的Task没有关系。

 

更多代码细节参考标题链接。

3.2. RefLink

关于Activity启动的代码细节, 网上有写的很好的文章, 这里直接给出参考链接.

         startActivity启动过程分析 , 从代码的层面描述上面那张图.

         简述Activity生命周期 , 侧重介绍ActivityonCreateonStartonResumeonPause等调用时机.

         ActivityRecord , 每一个ActivityAMS都会存在一个对应的ActivityRecord数据结构.

4.   Service

         首先, 启动Service的既可以是Launch进程, 比如桌面Launcher或者某个应用; 也可以是APK进程本身, 例如启动本APK内部的某个Service.

         不管是哪种启动方式, 它们在走完红色的共同道路后会分道扬镳 : 如果需要创建新的APK进程则走蓝色道路(新进程创建的细节参考第2章); 否则走紫色道路.

         之后又殊途同归, 都进入realStartServiceLocked方法, 走黑色道路.

         Service的启动流程中, 多了一个ANR的启动机制, 如果Service启动超时则会出发ANR. 图中的绿色箭头是买炸弹的过程, 黄色箭头是拆炸弹的过程, 如果在炸弹爆炸之前没来得及拆除, 则会引发ANR. 更多细节参考ANR一文.

         上图只显示了onCreate的流程, onStartCommand的流程与之类似, 细节请查看RefLink中的文章.

4.1. RefLink

关于Service启动的代码细节, 网上有写的很好的文章, 这里直接给出参考链接.

         startService启动过程分析, 从代码的层面描述上面那张图. 另外还有onStartCommand的代码流程.

         bindService启动过程分析, bindService是启动Service的另外一种方法, 主要应用场景是一个进程想要访问某个remote service, 所谓remote service是指service组件存在于一个单独的进程中, 与访问它的进程是两个不同进程.

bindService会创建remote service依附的进程, 然后走create service流程, 一直到执行Service.onCreate. 整个流程走完后, 访问进程会拿到remote service的客户端代理, 随后就可以通过这个代理访问service.

         unbindService流程分析,  bindservice的反过程

         ServiceRecord, 每一个serviceAMS都会存在一个对应的ServiceRecord数据结构.

         ActivityService生命周期

5.   Broadcast

Broadcast机制稍微复杂一点, 可以分为三部分来描述:

         首先APK进程需要注册BroadcastReceiver, 指明IntentFilter, 代表自己可以处理哪些类型的广播.

         其次另外的一些APK进程需要发送广播, 指明此广播的Intent.

         最后是AMS中处理广播, 针对每一个广播, AMS从已注册的Receivers中找出可以处理该广播的Receivers, 经过一系列流程最终调用到这些ReceiveronReceive()函数.

 

下文就分别介绍这3部分.

5.1             注册BroadcastReceiver

BroadcastReceiver分为两类:

         静态广播接收者:通过AndroidManifest.xml的标签来申明的BroadcastReceiver

         动态广播接收者:通过AMS.registerReceiver()方式注册的BroadcastReceiver,动态注册更为灵活,可在不需要时通过unregisterReceiver()取消注册

 

对于动态注册的广播, AMS中有两个池子来存储它们. 第一个池子是mRegisteredReceivers, 它是一个receiver IBinderkey, ReceiverList里面存储的是该receiver的所有intentflitervalueHashMap. 第二个池子是mReceiverResolver, 它存在的主要目的是通过广播发送者给出的Intent, 找到所有对应的动态注册的Receviers.

 

对于静态注册的广播, AMS中并没有池子存储它们, 每次通过Intent去查找对应的静态注册的Receivers, AMS都是通过PKMS找出对应的Receivers.

 

因此这里只描述动态广播的注册流程, 对于应用开发来说, 动态注册广播往往是在Activity/Service中调用registerReceiver()方法, ActivityService都间接继承于Context抽象类, 真正干活是交给ContextImpl.

         动态注册的BroadcastReceiver实现了一个binder的服务端InnerReceiver, 它的客户端最终会传入AMS.

不过虽然这里把客户端传过去了, AMS并没有通过binder调用InnerReceiver. 在《处理广播》时, AMS会把这个客户端回传给APK进程, 然后在APK进程中调用InnerReceiver.performReceive

         AMS中有一个mRegisteredReceivers存储所有动态注册的receivers.

         registerReceiver(…, Handler scheduler) 这个API中有一个参数叫scheduler, 用于指定接收到广播时, onReceiver在哪个线程执行. 默认情况下这个参数是null, 代表在应用的主线程中执行(更本质一点来说就是借用了主线程的loop).

另外只有动态注册的Receiver才有机会选择onReceiver的运行线程, 静态注册的广播只能在主线程中执行.

5.2             发送广播

发送广播的API

         普通广播:通过Context.sendBroadcast()发送,可并行处理

         有序广播:通过Context.sendOrderedBroadcast()发送,串行处理

         Sticky广播:通过Context.sendStickyBroadcast()发送

 

Sticky广播后面单独说, 这里重点看一下并行广播和串行广播. 类似于BroadcastReceiver, AMS中也有两个池子用于存储广播. 这两个池子是mFgBroadcastQueue (Fg代表前台)mBgBroadcastQueue (Bg代表后台). 每个池子里面有两个链表, 分别用于挂接并行广播和串行广播. 图示如下:

          根据广播的属性: 前台广播加入mFgBroadcastQueue, 后台广播加入mBgBroadcastQueue; 并行广播加入mParallelBroadcasts, 串行广播加入mOrderedBroadcasts

 

发送广播的宏观逻辑就是 : 当有人调用API发送一个广播时(发送者会给出Intent、前台/后台等信息, AMS收到这个广播后, 会根据广播的Intent, PKMS中查询所有能处理此广播的receivers(静态注册的接收者) mReceiverResolver中查询所有能处理此广播的registeredReceivers(动态注册的接收者). 然后把这个广播和它的静动态receivers封装成一个新的数据结构BroadcastRecord. 再后, 根据广播的属性, 把新生成的这个BroadcastRecord加入对应的队列. 最后, 调用queue.scheduleBroadcastsLocked()开始处理广播. 细节如下图:

         发送广播是在ActivityService中调用sendBroadcast()方法,而ActivityService都间接继承于Context抽象类,真正干活是交给ContextImpl.

         Step6 : 如果有人发送了一个并行广播, 而且存在能接收此广播的动态注册的BroadcastReceiver, 则把它们封装成一个BroadcastRecordenqueueParallelBroadcastLocked.

那是不是所有的并行广播都会加入ParallelBroadcast队列呢? 不是!假设有人发送了一个并行广播, 此时系统中有10BroadcastReceiver能接收此广播, 但是有5个是动态注册的, 另外5个是静态注册的. 这种情况下会封装两个BroadcastRecord, 一个加入ParallelBroadcast队列, 另一个加入OrderedBroadcast队列.

因此我们可以得出一个结论 : 哪些广播会进入ParallelBroadcast队列?

         并行广播和能接收此广播的动态注册的BroadcastReceiver.

         Step7 : 这一步的目的是合并动态注册和静态注册的receiver. 随后在step8中会把这些receivers与串行广播配对并加入OrderedBroadcast队列.

对于哪些广播会进入OrderedBroadcast队列, 我们的结论是:

         并行广播和能接收此广播的静态注册的BroadcastReceiver.

         串行广播和能接收此广播的动态注册的BroadcastReceiver.

         串行广播和能接收此广播的静态注册的BroadcastReceiver.

5.3             处理广播

处理广播是从queue.scheduleBroadcastsLocked()开始的, 我们看一下流程图, 比较复杂:

         首先, scheduleBroadcastsLocked会向AMSActivityManager线程sendMessage, ActivityManager收到消息后会调用processNextBroadcast. 因此scheduleBroadcastsLocked会增加ActivityManager线程的负担.

注意这里的mHandler虽然是在BroadcastQueue中定义的, 但是它的loop任然是ActivityManager线程. 相当于BroadcastQueue只是提供了一个处理函数, 做循环调度的还是ActivityManager线程.

         processNextBroadcast中会先处理mParallelBroadcasts中的所有广播. 它的逻辑是在一个循环中, 针对每个BroadcastRecord的每一个receiver, 调用deliverToRegisteredReceiverLocked. 它不会等一个receiveronReceive方法执行完毕后再处理下一个receiver, 因此并行广播的处理不会引发ANR.

         然后processNextBroadcast开始处理依次处理mOrderedBroadcasts中的每一个广播, 针对广播的每一个receiver, 它会先等该receiveronReceive函数执行完毕(即收到APK进程发过来的finishReceiver消息)后才开始处理下一个receiver. 注意在处理下一个receiver, 是工作在binder线程, 因此不会增加ActivityManager线程的负担.

另外串行广播有可能触发ANR, 前台广播和后台广播的时限也不同.

         APK Process这边, 静态注册的receiveronReceive方法运行在APK的主线程中. 动态注册的receiveronReceive方法默认运行在APK的主线程, 但是也可以运行于某个指定线程, 这取决于《注册BroadcastReceiver》时给定的参数.

5.4             sticky广播

首先sticky广播与普通的广播有相同之处: sticky广播即可以是并行广播(Context.sendStickyBroadcast), 也可以是串行广播(Context.sendStickyOrderedBroadcast); 处理逻辑上也与普通的广播一致, 发送完广播后, AMS会寻找相应的BroadcastReceiver并最终调用onReceive函数.

 

不过sticky广播与普通广播也有不同之处: 普通广播只会被系统处理一次, 随后会被删除; 但是所有的sticky广播都会存储在AMSmStickyBroadcasts(代码细节见增加sticky广播, 只有显示的调用Context.removeStickyBroadcast才能从系统中删除一个sticky广播.

 

sticky广播存储起来有什么用? 在注册BroadcastReceiver的时候, 如果mStickyBroadcasts存在匹配的广播, 则会马上enqueueParallelBroadcastLockedscheduleBroadcastsLocked. 代码细节见AMS.registerReceiver中如下一段:

这个动作会导致BroadcastReceiveronReceive随后被调度.

 

因此, sticky广播主要不同之处是当我们注册一个BroadcastReceiver, 这个Receiver能接收在注册的时间点之前已经发送的sticky广播. (当然, 这个Receiver也能接收这个时间点之后发送的sticky广播).

 

Android系统为什么要设计sticky广播? 因为系统中有一些重要的状态改变通知需要被持久保存. 比如电量改变的通知, 电量可能需要很久才会改变一次, 使用sticky广播能让应用一启动就能立马知道上一次系统电量还剩多少, 而不用傻傻的等到系统下一次通知时才知道.

5.5             RefLink

关于Broadcast的代码细节, 网上有写的很好的文章, 这里直接给出参考链接.

         Android Broadcast广播机制分析, 从代码的层面分析了《注册BroadcastReceiver》、《发送广播》、《处理广播》这3个过程.

         BroadcastRecord, 每一个广播与它的接收者们AMS中都会有一个对应的BroadcastRecord数据结构.

5.6             FAQs

dumpsys activity b [history]

可显示如下内容:

         可显示所有动态注册的接收者信息. [ Note, 静态注册的不会显示]. 例如

          可显示某个接收者关心哪几个Action (Registered Receivers:)

          可显示某个Action有几个接收者 (Receiver Resolver Table:)

         可显示当前正在处理和等待处理的广播

         可显示已经处理过的广播

          保留最多50个已处理广播的详细信息 (MAX_BROADCAST_HISTORY 可调)

          保留最多300个已处理广播的概要信息 (MAX_BROADCAST_SUMMARY_HISTORY可调)

如何获得广播的执行结果

只能用有序广播才能得到返回结果.

应用层示例参考http://www.voidcn.com/article/p-ojtnpxuj-yv.html.

系统层参考runSendBroadcast

如何控制优先级

IntentFilter.java -> setPriority 可定义接收者的优先级

sortResults函数会根据优先级对接收者进行排序, 从而使得高优先级接收者先运行

如果进程被杀死, 那它动态注册的广播是否会被移除

AMS.javaregisterReceiver函数里面linkToDeath会绑定注册者, 当注册者被杀死后, ReceiverList.java里面的binderDied会自动调用unregisterReceiver

如果一个广播没有任何接收者, 系统会反馈什么消息给广播发送者

在发送有序广播时 (sendOrderedBroadcast), 可获取广播的执行结果(resultCode, 发送者可以设定一个初始值, 如果没有任何人处理广播, resultCode不会被修改.

指定广播只能被某些应用处理, 以免三方应用监听并处理广播

Reflink : https://blog.csdn.net/mingli198611/article/details/17762149

protected-broadcast

此类广播只能由system用户发送.

 

定义protected-broadcast的方法如下:

 

AMS.java -> broadcastIntentLocked里面会保证只有system用户才能发送protected-broadcast.

8.x的变化

两种类型的广播:

  1. 显式广播(Explicit Broadcast):发送的Intent是显示Intent的广播。通过指定Intent组件名称来实现的,它一般用在知道目标组件名称的前提下,去调用以下方法。意图明确,指定了要激活的组件是哪个组件,一般是在相同的应用程序内部实现的。

Intent.setComponent()

Intent.setClassName()

Intent.setClass()

new Intent(A.this,B.class)

  1. 隐式广播(Implicit Broadcast):通过Intent Filter来实现的,它一般用在没有明确指出目标组件名称的前提下。Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。一般是用于在不同应用程序之间。

 

Android8.0的后台限制

注意是针对targetSDK >= 26的应用,也就是说,targetSDK小于26的话,暂不受影响

Oreo中,为了进一步提升用户体验,进一步节省功耗,对应用在后台运行时可以执行的操作又进一步施加了限制。

但对于这些隐式广播,可以通过运行时注册(动态注册)的方式注册。

对于显式广播,则依然可以通过清单注册(静态注册)的方式监听

 

具体广播限制和对应赦免清单

API24及以上应用,静态注册的广播接收器无法监听网络变化:android.net.conn.CONNECTIVITY_CHANGE

Android7.0设备上,App无法发送或者接收ACTION_NEW_PICTUREACTION_NEW_VIDEO广播。

只不过,在Android8.0上,又进一步的增强了限制,除了以下隐式广播外,其他所有隐式广播均无法通过在AndroidManifest.xml中注册监听。参考官网。

 

Android 8.0 上不限制的隐式广播

 

开机广播

 Intent.ACTION_LOCKED_BOOT_COMPLETED

Intent.ACTION_BOOT_COMPLETED

"保留原因:这些广播只在首次启动时发送一次,并且许多应用都需要接收此广播以便进行作业、闹铃等事项的安排。"

 

增删用户

Intent.ACTION_USER_INITIALIZE

"android.intent.action.USER_ADDED"

"android.intent.action.USER_REMOVED"

"保留原因:这些广播只有拥有特定系统权限的app才能监听,因此大多数正常应用都无法接收它们。"

 

时区、ALARM变化

"android.intent.action.TIME_SET"

Intent.ACTION_TIMEZONE_CHANGED

AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED

"保留原因:时钟应用可能需要接收这些广播,以便在时间或时区变化时更新闹铃"

 

语言区域变化

Intent.ACTION_LOCALE_CHANGED

"保留原因:只在语言区域发生变化时发送,并不频繁。 应用可能需要在语言区域发生变化时更新其数据。"

 

Usb相关

UsbManager.ACTION_USB_ACCESSORY_ATTACHED

UsbManager.ACTION_USB_ACCESSORY_DETACHED

UsbManager.ACTION_USB_DEVICE_ATTACHED

UsbManager.ACTION_USB_DEVICE_DETACHED

"保留原因:如果应用需要了解这些 USB 相关事件的信息,目前尚未找到能够替代注册广播的可行方案"

 

 

蓝牙状态相关

BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED

BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED

BluetoothDevice.ACTION_ACL_CONNECTED

BluetoothDevice.ACTION_ACL_DISCONNECTED

"保留原因:应用接收这些蓝牙事件的广播时不太可能会影响用户体验"

 

Telephony相关

CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED

TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED

TelephonyIntents.SECRET_CODE_ACTION

TelephonyManager.ACTION_PHONE_STATE_CHANGED

TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED

TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED

"保留原因:设备制造商 (OEM) 电话应用可能需要接收这些广播"

 

账号相关

AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION

"保留原因:一些应用需要了解登录帐号的变化,以便为新帐号和变化的帐号设置计划操作"

 

应用数据清除

Intent.ACTION_PACKAGE_DATA_CLEARED

"保留原因:只在用户显式地从 Settings 清除其数据时发送,因此广播接收器不太可能严重影响用户体验"

 

软件包被移除

Intent.ACTION_PACKAGE_FULLY_REMOVED

"保留原因:一些应用可能需要在另一软件包被移除时更新其存储的数据;对于这些应用,尚未找到能够替代注册此广播的可行方案"

 

外拨电话

Intent.ACTION_NEW_OUTGOING_CALL

"保留原因:执行操作来响应用户打电话行为的应用需要接收此广播"

 

当设备所有者被设置、改变或清除时发出

DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED

"保留原因:此广播发送得不是很频繁;一些应用需要接收它,以便知晓设备的安全状态发生了变化"

日历相关

CalendarContract.ACTION_EVENT_REMINDER

"保留原因:由日历provider发送,用于向日历应用发布事件提醒。因为日历provider不清楚日历应用是什么,所以此广播必须是隐式广播。"

 

安装或移除存储相关广播

Intent.ACTION_MEDIA_MOUNTED

Intent.ACTION_MEDIA_CHECKING

Intent.ACTION_MEDIA_EJECT

Intent.ACTION_MEDIA_UNMOUNTED

Intent.ACTION_MEDIA_UNMOUNTABLE

Intent.ACTION_MEDIA_REMOVED

Intent.ACTION_MEDIA_BAD_REMOVAL

"保留原因:这些广播是作为用户与设备进行物理交互的结果:安装或移除存储卷或当启动初始化时(当可用卷被装载)的一部分发送的,因此它们不是很常见,并且通常是在用户的掌控下"

 

短信、WAP PUSH相关

Telephony.Sms.Intents.SMS_RECEIVED_ACTION

Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION

注意:需要申请以下权限才可以接收

"android.permission.RECEIVE_SMS"

"android.permission.RECEIVE_WAP_PUSH"

"保留原因:SMS短信应用需要接收这些广播"

 

谷歌给出了允许隐式注册的广播列表:https://developer.android.com/preview/features/background-broadcasts.html

6.   ContentProvider

6.1             概述

ContentProvider(内容提供者)用于提供数据的统一访问格式,封装底层的具体实现。对于数据的使用者来说,无需知晓数据的来源是数据库、文件,或者网络,只需简单地使用ContentProvider提供的数据操作接口,也就是增(insert)(delete)、改(update)、查(query)四个过程。

 

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类型。

 

Uri: ContentProvider的数据操作方法可以看出都依赖于Uri,对于Uri有其固定的数据格式,例如:content://com.gityuan.articles/android/3

字段

含义

对应项

前缀

默认的固定开头格式

content://

授权

唯一标识provider

com.gityuan.articles

路径

数据类别以及数据项

/android/3

6.2             流程

ContentProvider的流程很复杂, 下面这幅图也只是描述了部分, 还有些细节没法展开了.

当有人调用ContentProvider, 一般是想使用它的增///改操作. 下面我们以查询操作(query)为例介绍一下流程:

         app或者进程想要操作ContentProvider, 需要先获取其相应的ContentResolver, 再利用ContentResolver类来完成对数据的增删改查操作. 例如:

每个应用进程的ContextImpl构造函数会创建一个mContentResolvergetContentResolver其实就是通过层层调用获取到这个mContentResolver.

         在执行具体的操作时, 比如执行query,  APK会先看看自己是否已经缓存了这个ContentProvider, 如果已经缓存则直接通过binder与服务端交互. (图中左侧的binder, 注意每个ContentProvider都实现了binder的服务端, 其它进程想使用此Provider, 实际上是通过binder客户端与这个服务端通信).

如果APK没有缓存这个ContentProvider, 情况就比较复杂了, 需要走图中右侧的流程 : 这个流程会先看Provider所在的进程是否启动, 如果没有启动会先创建APK进程; 然后会进入一个循环等待, 等待provider发布(不管是否需要创建新的APK进程, 都会进入循环等待); APK进程启动后, 会调用publishContentProviders发布provider, AMS收到这个信息后会唤醒循环等待, 最终导致右侧的流程返回. 这一套流程的主要目的其实只是会了拿到ContentProvider的客户端代理, 拿到之后, 已缓存ContentProvider一样的路.

         图中也标示出了set ANR time bomb”和“remove ANR bomb”的时机.

6.3             RefLink

关于ContentProvider的代码细节, 网上有写的很好的文章, 这里直接给出参考链接.

         理解ContentProvider原理, 从代码层面梳理了上面那副图.

         ContentProvider引用计数, 使用Provider/释放Provider都涉及到引用计数的问题.

  ContentProviderRecord, 每一个ContentProviderAMS中存在一个对应的ContentProviderRecord数据结构.

posted @ 2020-12-06 18:00  johnliuxin  阅读(482)  评论(0)    收藏  举报