文章目录
- 参考博客
- 一、 Linux内核的启动画面
- 1.1 frame buffer初始化
- 1.2 fbmem.c:register_framebuffer(struct fb_info*)
- 1.3 帧缓冲区控制台 fbcon.c
- 1.3.1 初始化
- 1.3.2 监听帧缓冲硬件设备的注册事件
- 1.3.3 设置系统所使用的控制台 do_fbcon_takeover(int)
- 1.3.4 do_take_over_console
- 1.3.5 初始化系统控制台 fbcon.c:fbcon_init(vc_data*,int)
- 1.3.5.1 准备开机画面 fbcon.c:fbcon_prepare_logo(vc_data*,fb_info*,int,int,int,int)
- 1.3.5.2 获取logo内容 fbmem.c:fb_prepare_logo(fb_info*,int)
- 1.3.5.3 根据编译选项选择内核开机画面 logo.c:fb_find_logo(int)
- 1.3.6 系统控制台切换 fbcon.c:fbcon_switch(vc_data*)
- 二、init 进程的启动画面
- 三、 BootAnimation 启动动画
- 3.1 init.rc:late-init
- 3.2 init.hardware.rc:late-fs
- 3.3 SurfaceFlinger的启动 main_surfaceflinger.cpp:main()
- 3.4 实例化SurfaceFlinger DisplayUtils.cpp:getSFInstance
- 3.5 SurfaceFlinger.cpp:init()
- 3.6 设置属性启动bootanim服务 StartPropertySetThread.cpp:start
- 3.7 启动bootanim进程 bootanimation_main.cpp:main()
- 3.8 初始化BootAnimation对象 BootAnimation.cpp
- 3.9 播放默认Android开机动画 BootAnimation.cpp:android()
- 3.10 加载自定义动画 BootAnimation.cpp:movie()
参考博客
- Android系统的开机画面显示过程分析
- Android修改kernel logo和开机动画(android)
- Framebuffer 驱动学习总结(一) ---- 总体架构及关键结构体
- android logo:内核、android开机动画
- 为什么要有uboot?带你全面分析嵌入式linux系统启动过程中uboot的作用
主要是跟着老罗的博客,深入研究学习下Android的UI架构,彻底搞清楚绘制流程。鉴于老罗的博客写成时间有点久,分析的Andorid版本也有点低。现在从Android P的源码开始分析。
同样的,从Android启动显示的画面顺序依次分析。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RR5shdXf-1619057218850)(http://www.swallowj.xyz/static/upload/20180812/upload_22c4a8dc3549b5d881f4879c8bd3cbe6.png)]
Android系统在启动的过程中,最多可以出现四个画面,每一个画面都用来描述一个不同的启动阶段。
- Linux uboot显示(静态)
- Linux 内核的启动画面(静态)
- init 进程的启动画面(静态)
- BootAnimation 启动动画(动态)
uboot 其实就是 Bootloader ,用来启动、部署操作系统以及操作flash等硬件驱动,并提供了一个供人操作的界面,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jw1pbhkY-1619057218868)(http://www.swallowj.xyz/static/upload/20180812/upload_aebabe41066d94863ca0b6728a7b53f7.png)]
进入bootloader的方式:adb reboot bootloader 或者通过各个厂商自定义的按键方式进入。关于 uboot ,可以观看博文: http://www.bubuko.com/infodetail-2302475.html。
一般来说 前三个启动画面都会做成相同的。uboot 的开机logo会影响开机速度,这个暂不深入分析,直接从内核logo开始看。
众所周知,Android也是linux派生出来的,因此Android的显示机制用的是Linux一样的机制:Framebuffer。所以上述的这些画面都是在帧缓冲区(frame buffer,简称fb)上进行渲染的。帧缓冲设备对应的设备文件为/dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32个,分别为/dev/fb0到/dev/fb31。
一、 Linux内核的启动画面
Android系统的内核开机画面其实是Linux内核的启动画面,需要在编译的时候开启以下两个编译选项:
- CONFIG_FRAMEBUFFER_CONSOLE :内核支持帧缓冲区控制台
- CONFIG_LOGO :内核在启动的过程中,需要显示LOGO
1.1 frame buffer初始化
帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件android/kernel/msm-4.9/drivers/video/fbdev/core/fbmem.c中。
帧缓冲设备在Linux中也可以看做是一个完整的子系统,大体由fbmem.c和xxxfb.c组成。向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,即xxxfb.c部分的实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67rB0TRE-1619057218869)(http://www.swallowj.xyz/static/upload/20180812/upload_99f5f107d09d9db66ba10f84bc6579c8.png)]
fbmem.c 的初始化函数如下所示:
/**
* fbmem_init - init frame buffer subsystem
* Initialize the frame buffer subsystem.
* NOTE: This function is _only_ to be called by drivers/char/mem.c.
*/
static int __init
fbmem_init(void)
{
int ret;
// 在/proc目录下创建了一个fb文件
if (!proc_create("fb", 0, NULL, &fb_proc_fops))
return -ENOMEM;
// 注册一个名称为 fb 的字符设备
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}
// 在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统。
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
return 0;
err_class:
unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
remove_proc_entry("fb", NULL);
return ret;
}
fbmem.c中除了做初始化,还会导出一个重要的函数:register_framebuffer
1.2 fbmem.c:register_framebuffer(struct fb_info*)
在Linux内核中,每一个帧缓冲区硬件都是使用一个结构体fb_info来描述的,该结构体记录了Framebuffer设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。
在Linux内核中,每一个硬件设备都有一个主设备号和一个从设备号,它们用来唯一地标识一个硬件设备。对于帧缓冲区硬件设备来说,它们的主设备号定义为FB_MAJOR(29),而从设备号则与注册的顺序有关,它们的值依次等于0,1,2等。
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
EXPORT_SYMBOL(register_framebuffer);
static int do_register_framebuffer(struct fb_info *fb_info)
{
// 注册 fb
......
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
......
}
......
// 由于frame buffer可能不止1个(最多32个),用数组registered_fb保存注册的fb
registered_fb[i] = fb_info;
......
// 通知帧缓冲区控制台,有新的 fb 被注册进内核
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
if (!lockless_register_fb)
console_unlock();
return 0;
}
1.2.1 fb_notify.c:fb_notifier_call_chain(unsigned long,void*)
/**
* fb_notifier_call_chain - notify clients of fb_events
*/
int fb_notifier_call_chain(unsigned long val, void *v)
{
return blocking_notifier_call_chain(&fb_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(fb_notifier_call_chain);
1.2.2 notifier.c:blocking_notifier_call_chain(struct blocking_notifier_head*,unsigned long,void*)
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v)
{
return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}
EXPORT_SYMBOL_GPL(blocking_notifier_call_chain);
int __blocking_notifier_call_chain(struct blocking_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
if (rcu_access_pointer(nh->head)) {
down_read(&nh->rwsem);
ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
nr_calls);
up_read(&nh->rwsem);
}
return ret;
}
EXPORT_SYMBOL_GPL(__blocking_notifier_call_chain);
static int notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference_raw(*nl);
while (nb && nr_to_call) {
next_nb = rcu_dereference_raw(nb->next);
#ifdef CONFIG_DEBUG_NOTIFIERS
if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
WARN(1, "Invalid notifier called!");
nb = next_nb;
continue;
}
#endif
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
NOKPROBE_SYMBOL(notifier_call_chain);
如上,最终调用到 notifier_block->notifier_call(notifier_block*,unsigned long,void*),我们看一下这个结构体的构造:
struct notifier_block;
typedef int (*notifier_fn_t)(struct notifier_block *nb,
unsigned long action, void *data);
struct notifier_block {
notifier_fn_t notifier_call;
struct notifier_block __rcu *next;
int priority;
};
这里设计Linux内核的RCU同步机制,不太了解。不过每一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb%d,其中%d表示一个从设备号。例如,第一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb0。用户空间的应用程序通过这个设备文件就可以操作帧缓冲区硬件设备了,即将要显示的画面渲染到帧缓冲区硬件设备上去。
这里最后会通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了。回过头看看帧缓冲区控制台的实现。
1.3 帧缓冲区控制台 fbcon.c
帧缓冲区控制台在内核中对应的驱动程序模块为fbcon.c,其实现位于文件:android/kernel/msm-4.9/drivers/video/console/fbcon.c
1.3.1 初始化
static struct notifier_block fbcon_event_notifier = {
.notifier_call = fbcon_event_notify,
};
static int __init fb_console_init(void)
{
int i;
console_lock();
// 监听帧缓冲区硬件设备的注册事件,其实现是 fbcon_event_notify
fb_register_client(&fbcon_event_notifier);
// 创建一个类别为graphics的设备fbcon
fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), NULL,
"fbcon");
if (IS_ERR(fbcon_device)) {
printk(KERN_WARNING "Unable to create device "
"for fbcon; errno = %ld\n",
PTR_ERR(fbcon_device));
fbcon_device = NULL;
} else
fbcon_init_device();
for (i = 0; i < MAX_NR_CONSOLES; i++)
con2fb_map[i] = -1;
console_unlock();
fbcon_start();
return 0;
}
fs_initcall(fb_console_init);
1.3.2 监听帧缓冲硬件设备的注册事件
static int fbcon_event_notify(struct notifier_block *self,
unsigned long action, void *data)
{
struct fb_event *event = data;
struct fb_info *info = event->info;
......
switch(action) {
......
case FB_EVENT_FB_REGISTERED: //帧缓冲区硬件设备注册监听
ret = fbcon_fb_registered(info);
break;
......
}
done:
return ret;
}
// 监听实现
static int fbcon_fb_registered(struct fb_info *info)
{
int ret = 0, i, idx;
idx = info->node;
// 检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备
// 如果是的话,那么就将它的信息记录下来
fbcon_select_primary(info);
// 全局变量info_idx表示系统当前所使用的帧缓冲区硬件的编号。
// 如果它的值等于-1,那么就说明系统当前还没有设置好当前所使用的帧缓冲区硬件设备。
if (info_idx == -1) {
for (i = first_fb_vc; i <= last_fb_vc; i++) {
// 在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。
if (con2fb_map_boot[i] == idx) {
// 将当前所注册的帧缓冲区硬件设备编号idx保存在全局变量info_idx中
info_idx = idx;
break;
}
}
// con2fb_map_boot中存在当前注册的帧缓冲区硬件设备编号
if (info_idx != -1)
// 设置系统所使用的控制台
// 这里的参数1代表要显示内核开机动画
ret = do_fbcon_takeover(1);
} else {
for (i = first_fb_vc; i <= last_fb_vc; i++) {
if (con2fb_map_boot[i] == idx)
set_con2fb_map(i, idx, 0);
}
}
return ret;
}
显然开机的时候,正常就会调用函数do_fbcon_takeover(1).
1.3.3 设置系统所使用的控制台 do_fbcon_takeover(int)
static int do_fbcon_takeover(int show_logo)
{
int err, i;
if (!num_registered_fb)
return -ENODEV;
// 全局变量logo_shown的初始值为FBCON_LOGO_CANSHOW,表示可以显示内核开机画面
// 显然当传入的show_logo为0时,代表不显示内核开机画面
if (!show_logo)
logo_shown = FBCON_LOGO_DONTSHOW;
// 将当前可用的控制台的编号都映射到当前正在注册的帧缓冲区硬件设备的编号info_idx中
// 去,表示当前可用的控制台与缓冲区硬件设备的实际映射关系。
for (i = first_fb_vc; i <= last_fb_vc; i++)
con2fb_map[i] = info_idx;
// 初始化系统当前所使用的控制台。如果它的返回值不等于0,那么就表示初始化失败。
err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc,
fbcon_is_default);
if (err) {
// 控制台初始化失败时,将映射回退,表示当前控制台没有与缓冲区硬件设备映射
for (i = first_fb_vc; i <= last_fb_vc; i++)
con2fb_map[i] = -1;
info_idx = -1;
} else {
fbcon_has_console_bind = 1;
}
return err;
}
1.3.4 do_take_over_console
int do_take_over_console(const struct consw *csw, int first, int last, int deflt)
{
int err;
err = do_register_con_driver(csw, first, last);
if (err == -EBUSY)
err = 0;
if (!err)
do_bind_con_driver(csw, first, last, deflt);
return err;
}
EXPORT_SYMBOL_GPL(do_take_over_console);
实际上这里就是向系统注册一系列回调函数,以便系统可以通过这些回调函数来操作当前所使用的控制台。这些回调函数使用结构体consw来描述。
初始化系统控制台时所注册的结构体consw是由全局变量fb_con来指定的,它的定义如下所示:
static const struct consw fb_con = {
.owner = THIS_MODULE, // 当前模块
.con_startup = fbcon_startup,
.con_init = fbcon_init, // 初始化控制台
.con_deinit = fbcon_deinit,
.con_clear = fbcon_clear,
.con_putc = fbcon_putc,
.con_putcs = fbcon_putcs,
.con_cursor = fbcon_cursor,
.con_scroll = fbcon_scroll,
.con_switch = fbcon_switch, // 控制台切换
.con_blank = fbcon_blank,
.con_font_set = fbcon_set_font,
.con_font_get = fbcon_get_font,
.con_font_default = fbcon_set_def_font,
.con_font_copy = fbcon_copy_font,
.con_set_palette = fbcon_set_palette,
.con_scrolldelta = fbcon_scrolldelta,
.con_set_origin = fbcon_set_origin,
.con_invert_region = fbcon_invert_region,
.con_screen_pos = fbcon_screen_pos,
.con_getxy = fbcon_getxy,
.con_resize = fbcon_resize,
.con_debug_enter = fbcon_debug_enter,
.con_debug_leave = fbcon_debug_leave,
};
这里我们只关注初始化和切换控制台两个方法。
1.3.5 初始化系统控制台 fbcon.c:fbcon_init(vc_data*,int)
// vc_data 用来描述正在初始化的控制台
static void fbcon_init(struct vc_data *vc, int init)
{
// 通过控制台编号(vc_num)找到对应的帧缓冲区硬件设备编号
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops;
// vc_display_fg 描述当前可见的控制台
struct vc_data **default_mode = vc->vc_display_fg;
struct vc_data *svc = *default_mode;
struct display *t, *p = &fb_display[vc->vc_num];
// logo 为1代表需要显示开机画面,反之不显示
int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256;
......
// 以下三个条件满足其一就不显示开机画面
// 1. 初始化的控制台非当前可见的控制台
// 2. logo_shown 为 FBCON_LOGO_DONTSHOW,即不显示开机画面
// 3. 当前正在初始化的控制台对应的帧缓冲区硬件设备的显示方式被设置为文字显示(FB_TYPE_TEXT)
if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||
(info->fix.type == FB_TYPE_TEXT))
logo = 0;
......
if (logo)
// 1.3.5.1 准备开机logo
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
......
}
1.3.5.1 准备开机画面 fbcon.c:fbcon_prepare_logo(vc_data*,fb_info*,int,int,int,int)
static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
int cols, int rows, int new_cols, int new_rows)
{
......
int logo_height;
......
// 1.3.5.2 获取logo内容并保存至 fb_info中
logo_height = fb_prepare_logo(info, ops->rotate);
// 计算显示logo所需的控制台行数,DIV_ROUND_UP是向上取整的意思
logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
......
// 若所需行数大于控制台最大行数,则不显示logo
if (logo_lines > vc->vc_bottom) {
logo_shown = FBCON_LOGO_CANSHOW;
printk(KERN_INFO
"fbcon_init: disable boot-logo (boot-logo bigger than screen).\n");
} else if (logo_shown != FBCON_LOGO_DONTSHOW) {
// 表示开机logo处于等待渲染的状态
logo_shown = FBCON_LOGO_DRAW;
vc->vc_top = logo_lines;
}
}
1.3.5.2 获取logo内容 fbmem.c:fb_prepare_logo(fb_info*,int)
int fb_prepare_logo(struct fb_info *info, int rotate)
{
// 获取帧缓冲区硬件设备的颜色深度depth
int depth = fb_get_color_depth(&info->var, &info->fix);
unsigned int yres;
memset(&fb_logo, 0, sizeof(struct logo_data));
......
// 寻找开机logo,并将其保存在全局变量fb_logo.logo中
fb_logo.logo = fb_find_logo(depth);
......
return fb_prepare_extra_logos(info, fb_logo.logo->height, yres);
}
1.3.5.3 根据编译选项选择内核开机画面 logo.c:fb_find_logo(int)
fb_find_logo的函数实现在文件:android/kernel/msm-4.9/drivers/video/logo/logo.c
这里根据不同编译选项选择内核开机画面的内容。
const struct linux_logo * __ref fb_find_logo(int depth)
{
const struct linux_logo *logo = NULL;
// nologo 默认值等于0,表示要显示内核开机画面
if (nologo || logos_freed)
return NULL;
if (depth >= 1) { // 黑白色
#ifdef CONFIG_LOGO_LINUX_MONO
/* Generic Linux logo */
logo = &logo_linux_mono;
#endif
#ifdef CONFIG_LOGO_SUPERH_MONO
/* SuperH Linux logo */
logo = &logo_superh_mono;
#endif
}
if (depth >= 4) { // 16色VGA
#ifdef CONFIG_LOGO_LINUX_VGA16
/* Generic Linux logo */
logo = &logo_linux_vga16;
#endif
#ifdef CONFIG_LOGO_BLACKFIN_VGA16
/* Blackfin processor logo */
logo = &logo_blackfin_vga16;
#endif
#ifdef CONFIG_LOGO_SUPERH_VGA16
/* SuperH Linux logo */
logo = &logo_superh_vga16;
#endif
}
if (depth >= 8) { // 224种颜色
#ifdef CONFIG_LOGO_LINUX_CLUT224
/* Generic Linux logo */
logo = &logo_linux_clut224;
#endif
#ifdef CONFIG_LOGO_BLACKFIN_CLUT224
/* Blackfin Linux logo */
logo = &logo_blackfin_clut224;
#endif
#ifdef CONFIG_LOGO_DEC_CLUT224
/* DEC Linux logo on MIPS/MIPS64 or ALPHA */
logo = &logo_dec_clut224;
#endif
#ifdef CONFIG_LOGO_MAC_CLUT224
/* Macintosh Linux logo on m68k */
if (MACH_IS_MAC)
logo = &logo_mac_clut224;
#endif
#ifdef CONFIG_LOGO_PARISC_CLUT224
/* PA-RISC Linux logo */
logo = &logo_parisc_clut224;
#endif
#ifdef CONFIG_LOGO_SGI_CLUT224
/* SGI Linux logo on MIPS/MIPS64 */
logo = &logo_sgi_clut224;
#endif
#ifdef CONFIG_LOGO_SUN_CLUT224
/* Sun Linux logo */
logo = &logo_sun_clut224;
#endif
#ifdef CONFIG_LOGO_SUPERH_CLUT224
/* SuperH Linux logo */
logo = &logo_superh_clut224;
#endif
#ifdef CONFIG_LOGO_M32R_CLUT224
/* M32R Linux logo */
logo = &logo_m32r_clut224;
#endif
}
return logo;
}
EXPORT_SYMBOL_GPL(fb_find_logo);
这些编译选项定义在:android/kernel/msm-4.9/include/linux/linux_logo.h
#define LINUX_LOGO_MONO 1 /* 黑白单色 */
#define LINUX_LOGO_VGA16 2 /* 16色VGA文字调色板 */
#define LINUX_LOGO_CLUT224 3 /* 224色 */
#define LINUX_LOGO_GRAY256 4 /* 256级灰度 */
extern const struct linux_logo logo_linux_mono;
extern const struct linux_logo logo_linux_vga16;
extern const struct linux_logo logo_linux_clut224; // 一般用的是这个
extern const struct linux_logo logo_blackfin_vga16;
extern const struct linux_logo logo_blackfin_clut224;
extern const struct linux_logo logo_dec_clut224;
extern const struct linux_logo logo_mac_clut224;
extern const struct linux_logo logo_parisc_clut224;
extern const struct linux_logo logo_sgi_clut224;
extern const struct linux_logo logo_sun_clut224;
extern const struct linux_logo logo_superh_mono;
extern const struct linux_logo logo_superh_vga16;
extern const struct linux_logo logo_superh_clut224;
extern const struct linux_logo logo_m32r_clut224;
extern const struct linux_logo logo_spe_clut224;
显然系统控制台初始化过程中会将开机logo从磁盘中读取并保存到 帧缓冲区硬件设备对应的结构体 fb_info 中。图片读取存入内存后,现在就是将其渲染出来。
1.3.6 系统控制台切换 fbcon.c:fbcon_switch(vc_data*)
static int fbcon_switch(struct vc_data *vc)
{
struct fb_info *info, *old_info = NULL;
struct fbcon_ops *ops;
struct display *p = &fb_display[vc->vc_num];
struct fb_var_screeninfo var;
int i, ret, prev_console, charcnt = 256;
info = registered_fb[con2fb_map[vc->vc_num]];
......
if (logo_shown >= 0) {
// vc_cons存有各个系统控制台的私有数据,如光标的位置、缓冲区的起始地址、缓冲区大小等等
struct vc_data *conp2 = vc_cons[logo_shown].d;
if (conp2->vc_top == logo_lines
&& conp2->vc_bottom == conp2->vc_rows)
conp2->vc_top = 0;
logo_shown = FBCON_LOGO_CANSHOW;
}
......
// 开机logoy已经准备完毕,处于待渲染状态
if (logo_shown == FBCON_LOGO_DRAW) {
// fg_console 代表系统当前可见控制台
logo_shown = fg_console;
// 1.3.6.1 渲染内核开机logo
fb_show_logo(info, ops->rotate);
update_region(vc,
vc->vc_origin + vc->vc_size_row * vc->vc_top,
vc->vc_size_row * (vc->vc_bottom -
vc->vc_top) / 2);
return 0;
}
return 1;
}
1.3.6.1 渲染内核开机logo fbmem.c:fb_show_logo(fb_info*,int)
int fb_show_logo(struct fb_info *info, int rotate)
{
int y;
y = fb_show_logo_line(info, rotate, fb_logo.logo, 0,
num_online_cpus());
y = fb_show_extra_logos(info, y, rotate);
return y;
}
static int fb_show_logo_line(struct fb_info *info, int rotate,
const struct linux_logo *logo, int y,
unsigned int n)
{
u32 *palette = NULL, *saved_pseudo_palette = NULL;
unsigned char *logo_new = NULL, *logo_rotate = NULL;
// fb_image 描述开机logo,包含了图片在控制台显示的位置,图片宽高以及一个指向图片数据的指针等
struct fb_image image;
......
// rotate用来描述屏幕的当前旋转方向
if (rotate) {
logo_rotate = kmalloc(logo->width *
logo->height, GFP_KERNEL);
if (logo_rotate)
fb_rotate_logo(info, logo_rotate, &image, rotate);
}
// 执行渲染
fb_do_show_logo(info, &image, rotate, n);
......
return logo->height;
}
static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
int rotate, unsigned int num)
{
unsigned int x;
if (rotate == FB_ROTATE_UR) { //无需旋转,正常显示
for (x = 0;
x < num && image->dx + image->width <= info->var.xres;
x++) {
// fb_info描述帧缓冲区硬件设备,其成员函数fbops指向了一系列回调
// 如下调用的 fb_imageblit 就是用来在指定的帧缓冲区硬件设备渲染指定的图像
info->fbops->fb_imageblit(info, image);
image->dx += image->width + 8;
}
} else if (rotate == FB_ROTATE_UD) {
for (x = 0; x < num; x++) {
info->fbops->fb_imageblit(info, image);
image->dx -= image->width + 8;
}
} else if (rotate == FB_ROTATE_CW) {
for (x = 0;
x < num && image->dy + image->height <= info->var.yres;
x++) {
info->fbops->fb_imageblit(info, image);
image->dy += image->height + 8;
}
} else if (rotate == FB_ROTATE_CCW) {
for (x = 0; x < num; x++) {
info->fbops->fb_imageblit(info, image);
image->dy -= image->height + 8;
}
}
}
回调函数 fb_imageblit 是在文件中:android/kernel/msm-4.9/include/linux/fb.h 申明的。调到这里其实内核开机画面已经渲染完成了。
/* 图形填充 */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
/* 绘制光标 */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
上面的渲染旋转方向的宏也是定义在文件:android/kernel/msm-4.9/include/uapi/linux/fb.h
/*
* Display rotation support
*/
#define FB_ROTATE_UR 0 //无需旋转,正常显示
#define FB_ROTATE_CW 1 //内容顺时针转90度
#define FB_ROTATE_UD 2 //上下倒置(180度)
#define FB_ROTATE_CCW 3 //内容逆时针旋转90度
二、init 进程的启动画面
init进程启动的过程可以参考 Android启动流程,这里不再赘述。直接看init进程启动,当kernel启动kernel_init挂载根文件系统并指定init路径后,就进入了init进程。
在文件:android/system/core/init/init.cpp
2.1 init进程启动 init.cpp:main(int argc, char** argv)
int main(int argc, char** argv) {
......
// 添加触发器early-init,执行on early-init内容
am.QueueEventTrigger("early-init");
// 等待冷启动完毕
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// 从硬件RNG的设备文件/dev/hw_random中读取512字节并写到Linux RNG的设备文件/dev/urandom中。
am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
// 初始化组合键监听模块
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
// 2.2 系统控制台初始化,在Android5.0以前这边都是会显示init进程的启动画面的
am.QueueBuiltinAction(console_init_action, "console_init");
// 添加触发器init,执行on init内容,主要包括创建/挂载一些目录,以及symlink等
am.QueueEventTrigger("init");
......
// 获取属性 ro.bootmode 的值,以便判断设备启动的方式
// 比如关机充电启动状态下不会挂载文件系统以及启动Android核心服务等
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
// 这里触发了后面的 BootAnimation 开机动画
// 包括启动zygote进程、加载资源等等
am.QueueEventTrigger("late-init");
}
}
2.2 准备init开机画面 init.cpp:console_init_action
static Result<Success> console_init_action(const BuiltinArguments& args) {
std::string console = GetProperty("ro.boot.console", "");
if (!console.empty()) {
// default_console默认值为 /dev/console
default_console = "/dev/" + console;
}
return Success();
}
从上面可以看到,这里先是获取属性"ro.boot.console",然后如果该属性不为null,则将default_console改为 /dev/+属性值。其实从Android 5.0以后就是不是这里显示init进程的开机画面了,也就是Android字样的画面。不过这个画面会由各个厂商自己通过其他途径实现。不继续分析了,接下来重点分析下开机动画——BootAnimation
三、 BootAnimation 启动动画
开机动画是由应用程序bootanimation来负责显示的。应用程序bootanimation在启动脚本init.rc中被配置成了一个服务——bootanim。
定义在文件:android/frameworks/base/cmds/bootanimation/bootanim.rc
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
# 用来启动应用程序bootanimation的服务是disable的,即init进程在启动的时候,不会主动将应用程序bootanimation启动起来
disabled
oneshot
writepid /dev/stune/top-app/tasks
SurfaceFlinger 服务启动的过程中会修改系统属性"ctl.start"的值以通知init进程启动bootanim来显示开机动画。当系统关键服务启动完毕后会由AMS通知SurfaceFlinger修改系统属性"ctl.stop"来通知init进程停止执行bootanim关闭动画。关于init进程是如何监听系统属性变化的,这个可以后续研究,大概就是在每次写入属性的时候通知init。
接下来看SurfaceFlinger的启动,在2.1的init进程启动过程中,当设备的启动方式非充电模式(关机连接充电器)时,就会触发 late-init.
3.1 init.rc:late-init
on late-init
trigger early-fs
trigger factory-fs
trigger fs
trigger post-fs
# 启动 SurfaceFlinger、composer、时间守护进程以及启动bootanim动画进程
trigger late-fs
trigger post-fs-data
# 启动zygote进程
trigger zygote-start
# Load persist properties and override properties (if enabled) from /data.
trigger load_persist_props_action
# Remove a file to wake up anything waiting for firmware.
trigger firmware_mounts_complete
trigger early-boot
trigger boot
trigger mmi
3.2 init.hardware.rc:late-fs
on late-fs
# Start devices by sysfs trigger
start vendor.devstart_sh
# 启动surfaceflinger
start surfaceflinger
# 启动bootanim进程播放开机动画
start bootanim
start vendor.hwcomposer-2-1
start vendor.configstore-hal
start vendor.gralloc-2-0
# Wait for hwservicemanager ready since fsck might be triggered in mount_all --late
# In such case, init won't responce the property_set from hwservicemanager and then
# cause services for bootanim not running.
wait_for_prop hwservicemanager.ready true
# Mount RW partitions which need run fsck
mount_all /vendor/etc/fstab.${ro.hardware} --late
# Required for time_daemon
mkdir /persist/time 0770 system system
# Start time daemon early so that the system time can be set early
start vendor.time_daemon
在这里我们要关注的是SurfaceFlinger服务的启动。
3.3 SurfaceFlinger的启动 main_surfaceflinger.cpp:main()
在文件:android/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
int main(int, char**) {
signal(SIGPIPE, SIG_IGN);
hardware::configureRpcThreadpool(1 /* maxThreads */,
false /* callerWillJoin */);
startGraphicsAllocatorService();
// 启动SF后将其进程的最大binder线程设置为4
ProcessState::self()->setThreadPoolMaxThreadCount(4);
// 启动线程池
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// 3.4 实例化surfaceflinger,注意这里用了智能指针,后续深入研究下
sp<SurfaceFlinger> flinger = DisplayUtils::getInstance()->getSFInstance();
setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);
set_sched_policy(0, SP_FOREGROUND);
if (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);
// 3.5 在客户端连接前初始化SurfaceFlinger的环境,包括启动渲染引擎等等
// 这里同时也设置了ctl.start 通知了init 进程启动bootanim
flinger->init();
// 向ServiceManager中注册SurfaceFlinger服务
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
// 注册GPU服务
sp<GpuService> gpuservice = new GpuService();
sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
// 在SurfaceFlinger注册完毕后启动DisplayService
startDisplayService(); // dependency on SF getting registered above
struct sched_param param = {0};
param.sched_priority = 2;
if (sched_setscheduler(0, SCHED_FIFO, ¶m) != 0) {
ALOGE("Couldn't set SCHED_FIFO");
}
// 启动surface flinger
flinger->run();
return 0;
}
3.4 实例化SurfaceFlinger DisplayUtils.cpp:getSFInstance
DisplayUtils::DisplayUtils() {
char value[PROPERTY_VALUE_MAX] = {};
// 这个属性默认是没有设置的,所以是0
property_get("vendor.display.disable_qti_bsp", value, "0");
int disable_qti_bsp = atoi(value);
// 默认为true
sUseExtendedImpls = !disable_qti_bsp;
}
DisplayUtils* DisplayUtils::getInstance() {
if (sDisplayUtils == NULL) {
sDisplayUtils = new DisplayUtils();
}
return sDisplayUtils;
}
SurfaceFlinger* DisplayUtils::getSFInstance() {
if (sUseExtendedImpls) {
// 这里的ExSurfaceFlinger其实是对SurfaceFlinger的部分重构
// 细节暂且不表,其继承了SurfaceFlinger,其构造函数只是初始化一些参数
return new ExSurfaceFlinger();
} else {
return new SurfaceFlinger();
}
}
3.5 SurfaceFlinger.cpp:init()
void SurfaceFlinger::init() {
......
// 启动渲染引擎
getBE().mRenderEngine =
RE::impl::RenderEngine::create(HAL_PIXEL_FORMAT_RGBA_8888,
hasWideColorDisplay
? RE::RenderEngine::WIDE_COLOR_SUPPORT
: 0);
......
//3.6 另起一个线程设置相关属性
if (mStartPropertySetThread->Start() != NO_ERROR) {
ALOGE("Run StartPropertySetThread failed!");
}
mLegacySrgbSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(HWC_DISPLAY_PRIMARY,
Dataspace::SRGB_LINEAR);
ALOGV("Done initializing");
}
3.6 设置属性启动bootanim服务 StartPropertySetThread.cpp:start
status_t StartPropertySetThread::Start() {
return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}
bool StartPropertySetThread::threadLoop() {
// Set property service.sf.present_timestamp, consumer need check its readiness
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
// 启动bootanim服务之前确保exit状态为0
property_set("service.bootanim.exit", "0");
// 启动bootanim
property_set("ctl.start", "bootanim");
return false;
}
3.7 启动bootanim进程 bootanimation_main.cpp:main()
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
// 3.7.1 是否显示动画
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// 3.7.2 等待SurfaceFlinger服务准备完毕
waitForSurfaceFlinger();
// 3.8 创建BootAnimation对象,且是智能指针,会调用到onFirstRef方法内
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");
// 需要与SurfaceFlinger通信,所以启动一个binder线程池
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
3.7.1 判断是否显示动画 BootAnimationUtil.cpp:bootAnimationDisabled()
这里首先判断属性 “debug.sf.nobootanimation” 的值是否大于0,大于0则显示动画,否则继续判断属性 “ro.boot.quiescent” 的值是否大于0,同样,大于0则显示动画,否则不显示动画。
bool bootAnimationDisabled() {
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
if (atoi(value) > 0) {
return true;
}
property_get("ro.boot.quiescent", value, "0");
return atoi(value) > 0;
}
3.7.2 等待SurfaceFlinger服务初始化完毕 BootAnimationUtil.cpp:waitForSurfaceFlinger()
这里的逻辑比较简单就是每隔100ms检查一下ServiceManager里是否已存在了SurfaceFlinger服务,也就是SurfaceFlinger是否准备完毕。
void waitForSurfaceFlinger() {
// TODO: replace this with better waiting logic in future, b/35253872
int64_t waitStartTime = elapsedRealtime();
sp<IServiceManager> sm = defaultServiceManager();
const String16 name("SurfaceFlinger");
const int SERVICE_WAIT_SLEEP_MS = 100;
const int LOG_PER_RETRIES = 10;
int retry = 0;
while (sm->checkService(name) == nullptr) {
retry++;
if ((retry % LOG_PER_RETRIES) == 0) {
ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms",
elapsedRealtime() - waitStartTime);
}
usleep(SERVICE_WAIT_SLEEP_MS * 1000);
};
int64_t totalWaited = elapsedRealtime() - waitStartTime;
if (totalWaited > SERVICE_WAIT_SLEEP_MS) {
ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);
}
}
3.8 初始化BootAnimation对象 BootAnimation.cpp
首先调用BootAnimation的构造函数,生成了一个用来和SurfaceFlinger服务通信的SurfaceComposerClient类对象。
然后调用了onFirstRef方法,该方法内启动了BootAnimation的线程。注意再启动之前先调用了readToRun()方法,然后启动线程threadLoop。
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) {
// SurfaceComposerClient类型的对象mSession是用来和SurfaceFlinger通信的,其内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,
// 这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。
mSession = new SurfaceComposerClient();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
}
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}
status_t BootAnimation::readyToRun() {
......
// session就是SurfaceComposerClient类的对象,这里createSurface最终调用到SurfaceFlinger,返回一个类型为SurfaceLayer的Binder代理对象
// 接着再使用这个Binder代理对象来创建一个SurfaceControl对象。这个SurfaceControl对象内部的成员mSurface就指向了由SurfaceFlinger返回的
// SurfaceLayer的Binder代理对象。
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::Transaction t;
t.setLayer(control, 0x40000000)
.apply();
sp<Surface> s = control->getSurface();
// 初始化 opengl 和 egl
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
context = eglCreateContext(display, config, NULL, NULL);
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
......
// vold.decrypt 是否解密,我们暂且不管这里
char decrypt[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt, "");
bool encryptedAnimation = atoi(decrypt) != 0 ||
!strcmp("trigger_restart_min_framework", decrypt);
if (!mShuttingDown && encryptedAnimation) {
static const char* encryptedBootFiles[] =
{PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE};
for (const char* f : encryptedBootFiles) {
if (access(f, R_OK) == 0) {
mZipFileName = f;
return NO_ERROR;
}
}
}
// 根据mShuttingDown判断当前播放开机还是关机动画
static const char* bootFiles[] =
{PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};
static const char* shutdownFiles[] =
{PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE};
for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) {
if (access(f, R_OK) == 0) { //依次判断文件是否存在,第一个存在的作为开/关机动画
mZipFileName = f;
return NO_ERROR; // 进入threadLoop
}
}
return NO_ERROR;
}
bool BootAnimation::threadLoop()
{
bool r;
// 判断文件是否为空
if (mZipFileName.isEmpty()) {
// 动画都不存在的情况下,播放镂空的"ANDROID"动画
// 3.9 播放默认Android开机动画
r = android();
} else {
// 3.10 加载播放自定义动画
r = movie();
}
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return r;
}
这里的开关机动画文件存储位置:
static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";
3.9 播放默认Android开机动画 BootAnimation.cpp:android()
bool BootAnimation::android()
{
ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
// 这两张图片保存在frameworks/base/core/res/assets/images目录中,它们最终会被编译
// 在framework-res模块(frameworks/base/core/res)中,即编译在framework-res.apk文件中。
// 编译在framework-res模块中的资源文件可以通过AssetManager类来访问。
// 这里首先根据这两张图片创建纹理贴图
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
mCallbacks->init({});
// 先清空屏幕内容
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// 计算纹理贴图显示位置
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime(); // 记录开始时间
do {
nsecs_t now = systemTime();
double time = now - startTime;
// 计算logo-shine的偏移
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
// 12fps: 保持12帧避免消耗CPU资源
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
总的来说,就是通过images/android-logo-mask.png和images/android-logo-shine.png两张图片生成动画。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XRUHAwaz-1619057218891)(http://www.swallowj.xyz/static/upload/20180812/upload_4dc018b4de64116be71ed5ebeb5fee89.png)]
images/android-logo-mask.png
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YuTvRDEv-1619057218908)(http://www.swallowj.xyz/static/upload/20180812/upload_f1266017e3ca79e24257eb3acf628d19.png)]
images/android-logo-shine.png
logo-mask是镂空图片,logo-shine是横向黑白渐变图片,通过混合渲染两张图片生成的纹理(每帧位移logo-shine)制成动画。
3.10 加载自定义动画 BootAnimation.cpp:movie()
bool BootAnimation::movie()
{
// 3.10.1 加载动画文件,这里的mZipFileName就是在readyToRun中获取的动画文件位置
Animation* animation = loadAnimation(mZipFileName);
if (animation == NULL)
return false;
bool anyPartHasClock = false;
for (size_t i=0; i < animation->parts.size(); i++) {
if(validClock(animation->parts[i])) {
anyPartHasClock = true;
break;
}
}
if (!anyPartHasClock) {
mClockEnabled = false;
}
// Check if npot textures are supported
mUseNpotTextures = false;
String8 gl_extensions;
const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
if (!exts) {
glGetError();
} else {
gl_extensions.setTo(exts);
if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) ||
(gl_extensions.find("GL_OES_texture_npot") != -1)) {
mUseNpotTextures = true;
}
}
// Blend required to draw time on top of animation frames.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
bool clockFontInitialized = false;
if (mClockEnabled) {
clockFontInitialized =
(initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR);
mClockEnabled = clockFontInitialized;
}
if (mClockEnabled && !updateIsTimeAccurate()) {
mTimeCheckThread = new TimeCheckThread(this);
mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
}
// 这里根据读取的动画文件播放动画,最后也是通过渲染引擎每帧绘制动画的png图片
// 3.10.3 播放动画
playAnimation(*animation);
if (mTimeCheckThread != nullptr) {
mTimeCheckThread->requestExit();
mTimeCheckThread = nullptr;
}
// 动画播放完毕,释放相应资源
releaseAnimation(animation);
if (clockFontInitialized) {
glDeleteTextures(1, &animation->clockFont.texture.name);
}
return false;
}
3.10.1 开机动画格式 bootanimation.zip
每个压缩文件都必须包含有一个名称为“desc.txt”的文件,这是用来描述用户自定义的开机动画是如何显示的。
//宽度 高度 帧速
600 480 24
// 以下每行均描述一个动画片段
p/*指明动画片段*/ 1/*循序次数,0代表无限循环*/ 0/*两次循环显示间隔,单位为每帧时长*/ part1/*文件目录,保存一系列png文件,这些png依次显示在屏幕上*/
p 0 10 part2
以上面这个desct.txt文件的内容为例,它描述了一个大小为600 x 480的开机动画,动画的显示速度为24帧每秒。这个开机动画包含有两个片断part1和part2。片断part1只显示一次,它对应的png图片保存在目录part1中。片断part2无限循环地显示,其中,每两次循环显示的时间间隔为10 x (1 / 24)秒,它对应的png图片保存在目录part2中。
3.10.2 解析加载动画文件 BootAnimation.cpp:loadAnimation(String8&)
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn)
{
if (mLoadedFiles.indexOf(fn) >= 0) {
ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
fn.string());
return NULL;
}
ZipFileRO *zip = ZipFileRO::open(fn);
if (zip == NULL) {
ALOGE("Failed to open animation zip \"%s\": %s",
fn.string(), strerror(errno));
return NULL;
}
Animation *animation = new Animation;
animation->fileName = fn;
animation->zip = zip;
animation->clockFont.map = nullptr;
mLoadedFiles.add(animation->fileName);
// 解析读取desc.txt文件,设置相应animation参数
parseAnimationDesc(*animation);
// 解析所有片断数据,包括音频参数(每片断仅能包含一个audio文件)
// 如果存在音频也会在这里初始化AudioPlay
if (!preloadZip(*animation)) {
return NULL;
}
mLoadedFiles.remove(fn);
// 返回解析好的Animation
return animation;
}
3.10.3 播放动画 BootAnimation.cpp:playAnimation(Animation*)
bool BootAnimation::playAnimation(const Animation& animation)
{
// 获取动画片断数量
const size_t pcount = animation.parts.size();
// 计算每帧显示时长
nsecs_t frameDuration = s2ns(1) / animation.fps;
// 计算动画显示位置--显示屏中心
const int animationX = (mWidth - animation.width) / 2;
const int animationY = (mHeight - animation.height) / 2;
ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
// 依次播放每个片断
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != NULL) {
playAnimation(*part.animation);
if (exitPending())
break;
continue; //to next part
}
// 播放单个片断的png图片,这里的part.count就是该片断的播放次数,如果为0则表示无限循环播放
for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;
mCallbacks->playPart(i, part, r);
// 改变背景颜色
glClearColor(
part.backgroundColor[0],
part.backgroundColor[1],
part.backgroundColor[2],
1.0f);
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
const Animation::Frame& frame(part.frames[j]);
nsecs_t lastFrame = systemTime();
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
if (part.count != 1) {
glGenTextures(1, &frame.tid);
glBindTexture(GL_TEXTURE_2D, frame.tid);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
int w, h;
initTexture(frame.map, &w, &h);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
if (!clearReg.isEmpty()) {
Region::const_iterator head(clearReg.begin());
Region::const_iterator tail(clearReg.end());
glEnable(GL_SCISSOR_TEST);
while (head != tail) {
const Rect& r2(*head++);
glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
0, frame.trimWidth, frame.trimHeight);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
// 地址交换,显示mSurface内容。
eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
//ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
lastFrame = now;
if (delay > 0) {
struct timespec spec;
spec.tv_sec = (now + delay) / 1000000000;
spec.tv_nsec = (now + delay) % 1000000000;
int err;
do {
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
} while (err<0 && errno == EINTR);
}
checkExit();
}
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}
}
// 释放动画过程中创建的纹理贴图
for (const Animation::Part& part : animation.parts) {
if (part.count != 1) {
const size_t fcount = part.frames.size();
for (size_t j = 0; j < fcount; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}
return true;
}
这里开机动画播放的分析也可以告一段落。后续我们再详细分析这个动画是如何停止显示,进入launcher的。