永恒的码流

万物皆流,无物常驻

0%

说明

参考闹钟服务编写一个HelloWorldService服务,功能为打印字符串”Hello, World!”,然后基于编译后的系统,编写APP使用该服务。

1
2
3
4
5
6
7
闹钟服务相关接口和类
- /frameworks/base/core/java/android/app/IAlarmManager.aidl 接口定义
- out/soong/.intermediates/frameworks/base/framemwork/android_common/
gen/aidl/frameworks/base/core/java/android/app/IAlarmManager.java 自动生成的接口以及Stub、Proxy
- /frameworks/base/services/core/java/com/android/server/AlarmManagerService.java 服务实现
- /frameworks/base/core/java/android/app/AlarmManager.java 客户端使用的服务代理

阅读全文 »

说明

Android服务框架包括Java服务框架(Java层)和本地服务框架(C++层),两层通过JNI交互。

代码分析版本:Android 10

框架概览

静态分层结构

说明:Android服务框架可分为四层

  1. 服务层:包含特定功能函数的服务层。IFooService规定服务提供的函数,继承IInterface;FooManager引用IFooService;FooService继承Binder和IFooService。
  2. RPC层:客户端生成RPC代码和数据,服务端根据RPC代码查找对应函数并传递RPC数据。IFooService.Stub.Proxy持有BinderProxy引用,BinderProxy由用户检索服务时获得。IFooService.Stub为抽象类,由FooService或其成员实现。
  3. IPC层:将RPC代码和数据封装为Binder IPC数据,并传递给Binder Driver。
  4. Binder Driver层:根据Binder IPC数据查找指定服务并返回。
阅读全文 »

本文主要探讨如何在子线程中不通过Handler而直接更新主线程的UI的问题,仅探讨。

问题场景

之前被问到一个问题:如何在子线程中不通过Handler而直接更新主线程的UI,比如在主线程创建的TextView,如何在子线程直接调用setText()方法更新文本?实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends AppCompatActivity {
private TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv1 = findViewById(R.id.tv_hw);
tv1.setOnClickListener(v -> new Thread(() -> {
tv1.setText("子线程刷新UI");
}).start());
}
}

直接运行上述代码会导致程序崩溃,调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.xxx.viewsettextdemo, PID: 4387
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
at android.view.View.requestLayout(View.java:24469)
at android.view.View.requestLayout(View.java:24469)
at android.view.View.requestLayout(View.java:24469)
at android.view.View.requestLayout(View.java:24469)
at android.view.View.requestLayout(View.java:24469)
at android.view.View.requestLayout(View.java:24469)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3146)
at android.view.View.requestLayout(View.java:24469)
at android.widget.TextView.checkForRelayout(TextView.java:9681)
at android.widget.TextView.setText(TextView.java:6269)
at android.widget.TextView.setText(TextView.java:6097)
at android.widget.TextView.setText(TextView.java:6049)
at com.xxx.viewsettextdemo.MainActivity.lambda$onCreate$0$MainActivity(MainActivity.java:22)
at com.xxx.viewsettextdemo.-$$Lambda$MainActivity$_uRJsNnQ0-zyKYjBJhwoyKb6E_I.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:919)

有什么方法,保证程序不崩溃且能够更新UI?

阅读全文 »

简介

Android 开机流程,即从用户按开机键到Launcher启动完成这一整个流程。由于Android 系统基于 Linux,因此整个启动流程会包括Linux的启动流程。

Android启动流程,大致分为六个阶段,如下所示:

android-start-flow-1

  • Boot ROM。用于加载并执行Boot Loader。Android 设备上电后,开始执行固化在 CPU芯片里的 Boot ROM 中的预设代码,程序将 Boot Loader 代码加载到 ROM 中。这一步由芯片厂商负责设计和实现。
  • Boot Loader。用于将系统代码加载到 RAM 中。具体功能有:检查 RAM、初始化系统的硬件参数等功能,找到 Linux kernel 的代码,设置启动参数,并最终加载到RAM中。
  • Kernel。Linux 内核开始启动,初始化各种软硬件环境、加载驱动程序、挂载根文件系统、并执行 init 程序,由此开启 Android 的世界。
  • Init。Init 进程负责启动Android系统关键的几个核心进程,其中最关键的是Zygote 进程,Zygote 进程中创建了 ART 或 Dalvik 虚拟机。
  • Zygote。Zygote 是 Android 系统的启动进程,Zygote 会启动 System Servers。
  • System Servers。是Android系统的核心,负责启动和管理整个Java FrameWork,包含ActivityManagerService, WorkManagerService,PagerManagerService,PowerManagerService等服务。

待服务启动完后,系统开始启动 APP。

阅读全文 »

当我们调用TextView.setTextColor()之后,UI就会更新,这一过程是怎样的?本文通过较完整的视图绘制流程分析Android的图形系统。

这里的绘制流程是指UI从数据更改触发到最终更新的流程。

代码基于Android 10。

概述

对于Android图形系统,可以这样理解:App中我们见到状态栏、Activity界面、弹窗等都是一个个窗口,对应App端的Window和服务端的WindowState;每个窗口包含多个控件,对应View和ViewGroup;每个窗口独占单独的画布,对应Surface(持有Canvas);多个画布内容通过SurfaceFlinger合成一帧画面,最后通过显示设备输出。

说得比较粗糙,实际情况较复杂,待以后优化 TODO

简单的对应关系如图:

wms-client-server-ui

Window和WindowState都间接持有Surface的引用,WindowState持有与SurfaceFlinger的连接Session。可通俗理解为,多个View控件构成一个Surface内容,多个Surface内容通过SurfaceFlinger合成为一帧最终输出的图像。

下面以具体代码来分析Android图形系统,分析TextView.setTextColor()从调用到UI更新的调用链流程,可分为:

  • 准备阶段:计算脏区位置和宽高(Dirty Rect,就是待更新区域)、测量、布局,之后调用ViewRootImpl.performDraw()尝试硬件绘制,不能或不成功则进行软件绘制。
  • 绘制操作:区分软件绘制和硬件绘制,绘制数据有所差别,但最终都是将绘制数据填充到GraphicBuffer,通知SurfaceFlinger进行合成。
  • 合成展示:SurfaceFlinger利用硬件(OpenGL 和 HardWare Composer)将 GraphicBuffer 数据合成并交给Display Buffer去显示。

整个流程如下:

android-draw-total-flow

阅读全文 »

说明

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。Activity.startActivity()
  • 创建窗口。Activity启动流程中会调用ActivityThread.performLaunchActivity()方法,该方法内部通过Activity.attach()方法创建PhoneWindow实例,并与Activity绑定。
  • 创建视图树。ActivityThread.performLaunchActivity()方法中待创建PhoneWindow后,接着会执行Instrumentation.callActivityOnCreate()Activity.onCreate(), 最终会调用Activity.setContentView()解析视图树并与Window绑定。
  • 绘制视图。在Activity启动流程的方法ActivityStackSupervisor.realStartActivityLocked()中会依次添加LaunchResume事务,待执行完ActivityThread.performLaunchActivity()Launch方法后,会接着执行ActivityThread.handleResumeActivity()Resume方法,该方法内部通过WindowManager.addView()方法绘制视图呈现UI。

简略时序图如下:

经过上面的流程,最终在App中视图相关的各类对象间关系为:

  • Activity持有PhoneWindow引用,PhoneWindow持有视图树根节点DecorView引用,DecorView对象又持有抽象根节点ViewRootImpl。
  • ViewRootImpl由WindowManagerGlobal创建和管理,而它自身管理视图树的绘制。
  • ViewRootImpl通过Session与WindowManagerService建立联系,由服务端管理窗口。
  • Activity、PhoneWindow、DecorView、ViewRootImpl、Session和WindowState是一一对应关系。
  • WindowManagerGlobal为单例,App进程全局唯一。

上面的箭头表示有联系,直接持有引用或间接联系。

阅读全文 »

前言

基于Android 10 源码 。Activity启动分为两种:

  • 普通Activity启动:APP已经启动,一般在APP内从一个Activity启动另一个Activity。
  • 根Activity启动:也称为APP冷启动,App还没有启动,一般从Launcher界面启动。

两者主流程基本一致,区别在于根Activity启动流程还包含启动APP进程和实例化Application的流程。

普通Activity启动流程

流程概述

分析方法Activity.startActivity()Activity.onCreate()的流程,流程图如下。

aosp10-activity-start-create

流程说明:

  1. 客户端发起启动Activity请求。客户端里的Activity通过Instrumentation.execStartActivity()请求服务端的ATMS启动Activity。
  2. 服务端完成Intent解析和数据、权限、状态等检查的准备工作。ATMS通过ActivityStarter、ActivityStack、ActivityStackSupervisor等完成Intent解析、检查等准备工作,并将数据传递给客户端的ApplicationThread,请求后者完成剩余启动工作。
  3. 客户端完成Activity的创建、数据绑定、onCreate回调等工作。ApplicationThread通过ActivityThread将数据传到其主线程的Handler中进行处理,完成Activity的创建、数据绑定和onCreate回调等工作。
阅读全文 »

说明

基于Ubuntu 20.04编译AOSP里的Android 10系统,并烧录进Pixel 2实机设备里。

烧录指将编译后的系统文件刷入实机或模拟器中

也适用Ubuntu18.4,不一致的地方会做说明,主要是python版本有差异。

应该说AOSP官网对整个流程都有描述,但有些地方可能说得不够清楚明白,为避免不常编译系统的开发者少走弯路,故有此总结。另外需要说明的是,优先看官网文档,官网有不清楚地方再看其他资料比如本文。

AOSP官网有两个,国际版本和国内版本,内容一致,但国内版本速度较快些。

阅读全文 »

前言

内存泄漏:对于Java来说,就是new出来的对象无法被GC回收。内存泄漏发生时的主要表现为内存抖动,可用内存慢慢变少。

https://pic3.zhimg.com/80/v2-4483280b8b2bab984b40251648f92222_720w.png

在Android中,造成内存泄漏的情形有:

  1. 使用全局静态变量持有Activity的强引用,而没有及时清空。
  2. 活在Activity生命周期之外的对象或线程,持有Activity的强引用,而没有及时清空。
  3. 忘记释放资源,比如忘记关闭Cursor,或忘记反注册传感器等。
阅读全文 »