说明
参考闹钟服务编写一个HelloWorldService服务,功能为打印字符串”Hello, World!”,然后基于编译后的系统,编写APP使用该服务。
1 | 闹钟服务相关接口和类 |
参考闹钟服务编写一个HelloWorldService服务,功能为打印字符串”Hello, World!”,然后基于编译后的系统,编写APP使用该服务。
1 | 闹钟服务相关接口和类 |
Android服务框架包括Java服务框架(Java层)和本地服务框架(C++层),两层通过JNI交互。
代码分析版本:Android 10
说明:Android服务框架可分为四层
本文主要探讨如何在子线程中不通过Handler而直接更新主线程的UI的问题,仅探讨。
之前被问到一个问题:如何在子线程中不通过Handler而直接更新主线程的UI,比如在主线程创建的TextView,如何在子线程直接调用setText()
方法更新文本?实例代码如下:
1 | public class MainActivity extends AppCompatActivity { |
直接运行上述代码会导致程序崩溃,调用栈如下:
1 | E/AndroidRuntime: FATAL EXCEPTION: Thread-2 |
有什么方法,保证程序不崩溃且能够更新UI?
Android 开机流程,即从用户按开机键到Launcher启动完成这一整个流程。由于Android 系统基于 Linux,因此整个启动流程会包括Linux的启动流程。
Android启动流程,大致分为六个阶段,如下所示:
待服务启动完后,系统开始启动 APP。
当我们调用TextView.setTextColor()
之后,UI就会更新,这一过程是怎样的?本文通过较完整的视图绘制流程分析Android的图形系统。
这里的绘制流程是指UI从数据更改触发到最终更新的流程。
代码基于Android 10。
对于Android图形系统,可以这样理解:App中我们见到状态栏、Activity界面、弹窗等都是一个个窗口,对应App端的Window和服务端的WindowState;每个窗口包含多个控件,对应View和ViewGroup;每个窗口独占单独的画布,对应Surface(持有Canvas);多个画布内容通过SurfaceFlinger合成一帧画面,最后通过显示设备输出。
说得比较粗糙,实际情况较复杂,待以后优化 TODO
简单的对应关系如图:
Window和WindowState都间接持有Surface的引用,WindowState持有与SurfaceFlinger的连接Session。可通俗理解为,多个View控件构成一个Surface内容,多个Surface内容通过SurfaceFlinger合成为一帧最终输出的图像。
下面以具体代码来分析Android图形系统,分析TextView.setTextColor()
从调用到UI更新的调用链流程,可分为:
ViewRootImpl.performDraw()
尝试硬件绘制,不能或不成功则进行软件绘制。整个流程如下:
JNI,即Java本地接口(Java Native Interface),允许Java代码与C/C++等本地语言编写的代码进行交互操作。
JNI提供的接口声明位于:<JDK_HOME>/include/jni.h
中
注意:JNI是Java的特性而非Android独有特性,只因为Android使用了Java语言,因此可以利用JNI的功能。
Java虚拟机利用函数原型将Java声明的本地方法与运行库中的C函数通过函数映射表对应起来,这样Java代码就能与C/C++代码交互。
函数原型(英语:Function prototype)或函数接口(英语:Function interface)是用于指定函数的名称和类型签名(元数,参数的数据类型和返回值类型)的一种省略了函数体的函数声明。
思考一:Java虚拟机作为桥梁。Java代码和C/C++本地代码运行环境不一样,因此不能直接交互,而Java代码的容器Java虚拟机是C/C++代码编写的,因此可以使用Java虚拟机作为桥梁将Java代码和C/C++本地代码联系起来。
思考二:为什么用函数原型?Java方法和C/C++函数是不一样的,但它们可以在函数原型上变为一致,都有名称和签名,因此可以使用函数原型建立映射关系。
我们在Activity.onCreate()
方法里调用setContentView()
方法后,待Activity处于RESUME状态时就会呈现视图。这一过程涉及到Activity的启动以及视图的创建和绘制,那么Activity是如何与视图(Window、View)产生联系或绑定在一起的?
流程关键节点:
Activity.startActivity()
。ActivityThread.performLaunchActivity()
方法,该方法内部通过Activity.attach()
方法创建PhoneWindow实例,并与Activity绑定。ActivityThread.performLaunchActivity()
方法中待创建PhoneWindow后,接着会执行Instrumentation.callActivityOnCreate()
、Activity.onCreate()
, 最终会调用Activity.setContentView()
解析视图树并与Window绑定。ActivityStackSupervisor.realStartActivityLocked()
中会依次添加Launch和Resume事务,待执行完ActivityThread.performLaunchActivity()
等Launch方法后,会接着执行ActivityThread.handleResumeActivity()
等Resume方法,该方法内部通过WindowManager.addView()
方法绘制视图呈现UI。简略时序图如下:
经过上面的流程,最终在App中视图相关的各类对象间关系为:
上面的箭头表示有联系,直接持有引用或间接联系。
基于Android 10 源码 。Activity启动分为两种:
两者主流程基本一致,区别在于根Activity启动流程还包含启动APP进程和实例化Application的流程。
分析方法Activity.startActivity()
到Activity.onCreate()
的流程,流程图如下。
流程说明:
Instrumentation.execStartActivity()
请求服务端的ATMS启动Activity。基于Ubuntu 20.04编译AOSP里的Android 10系统,并烧录进Pixel 2实机设备里。
烧录指将编译后的系统文件刷入实机或模拟器中
也适用Ubuntu18.4,不一致的地方会做说明,主要是python版本有差异。
应该说AOSP官网对整个流程都有描述,但有些地方可能说得不够清楚明白,为避免不常编译系统的开发者少走弯路,故有此总结。另外需要说明的是,优先看官网文档,官网有不清楚地方再看其他资料比如本文。
AOSP官网有两个,国际版本和国内版本,内容一致,但国内版本速度较快些。