好几个月都没有更新过博客了,从今天开始,老罗将尝试对Android系统的UI实现作一个系统的分析,也算是落实之前所作出的承诺。提到Android系统的UI,我们最先接触到的便是系统在启动过程中所出现的画面了。Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段。本文将详细分析这三个开机画面的显示过程,以便可以开启我们对Android系统UI实现的分析之路。

第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面。第二个开机画面是在init进程启动的过程中出现的,它也是一个静态的画面。第三个开机画面是在系统服务启动的过程中出现的,它是一个动态的画面。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。接下来,我们就分别分析这三个画面是如何在fb上显示的。

1. 第一个开机画面的显示过程

Android系统的第一个开机画面其实是Linux内核的启动画面。在默认情况下,这个画面是不会出现的,除非我们在编译内核的时候,启用以下两个编译选项:

CONFIG_FRAMEBUFFER_CONSOLE

CONFIG_LOGO

第一个编译选项表示内核支持帧缓冲区控制台,它对应的配置菜单项为:Device Drivers ---> Graphics support ---> Console display driver support ---> Framebuffer Console support。第二个编译选项表示内核在启动的过程中,需要显示LOGO,它对应的配置菜单项为:Device Drivers ---> Graphics support ---> Bootup logo。配置Android内核编译选项可以参考在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文。

帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件kernel/goldfish/drivers/video/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)

{

proc_create("fb", 0, NULL, &fb_proc_fops);

if (register_chrdev(FB_MAJOR,"fb",&fb_fops))

printk("unable to get major %d for fb devs\n", FB_MAJOR);

fb_class = class_create(THIS_MODULE, "graphics");

if (IS_ERR(fb_class)) {

printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));

fb_class = NULL;

}

return 0;

}

这个函数首先调用函数proc_create在/proc目录下创建了一个fb文件,接着又调用函数register_chrdev来注册了一个名称为fb的字符设备,最后调用函数class_create在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统。

模块fbmem除了会执行上述初始化工作之外,还会导出一个函数register_framebuffer:

EXPORT_SYMBOL(register_framebuffer);

这个函数在内核的启动过程会被调用,以便用来执行注册帧缓冲区硬件设备的操作,它的实现如下所示:

/**

*      register_framebuffer - registers a frame buffer device

*      @fb_info: frame buffer info structure

*

*      Registers a frame buffer device @fb_info.

*

*      Returns negative errno on error, or zero for success.

*

*/

int

register_framebuffer(struct fb_info *fb_info)

{

int i;

struct fb_event event;

......

if (num_registered_fb == FB_MAX)

return -ENXIO;

......

num_registered_fb++;

for (i = 0 ; i

if (!registered_fb[i])

break;

fb_info->node = i;

mutex_init(&fb_info->lock);

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);

......

registered_fb[i] = fb_info;

event.info = fb_info;

fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);

return 0;

}

由于系统中可能会存在多个帧缓冲区硬件设备,因此,fbmem模块使用一个数组registered_fb保存所有已经注册了的帧缓冲区硬件设备,其中,每一个帧缓冲区硬件都是使用一个结构体fb_info来描述的。

我们知道,在Linux内核中,每一个硬件设备都有一个主设备号和一个从设备号,它们用来唯一地标识一个硬件设备。对于帧缓冲区硬件设备来说,它们的主设备号定义为FB_MAJOR(29),而从设备号则与注册的顺序有关,它们的值依次等于0,1,2等。

每一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb,其中,表示一个从设备号。例如,第一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb0。用户空间的应用程序通过这个设备文件就可以操作帧缓冲区硬件设备了,即将要显示的画面渲染到帧缓冲区硬件设备上去。

这个函数最后会通过调用函数fb_notifier_call_chain来通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了。

帧缓冲区控制台在内核中对应的驱动程序模块为fbcon,它实现在文件kernel/goldfish/drivers/video/console/fbcon.c中,它的初始化函数如下所示:

static struct notifier_block fbcon_event_notifier = {

.notifier_call  = fbcon_event_notify,

};

......

static int __init fb_console_init(void)

{

int i;

acquire_console_sem();

fb_register_client(&fbcon_event_notifier);

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

con2fb_map[i] = -1;

release_console_sem();

fbcon_start();

return 0;

}

这个函数除了会调用函数device_create来创建一个类别为graphics的设备fbcon之外,还会调用函数fb_register_client来监听帧缓冲区硬件设备的注册事件,这是由函数fbcon_event_notify来实现的,如下所示:

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;

......

int ret = 0;

......

switch(action) {

......

case FB_EVENT_FB_REGISTERED:

ret = fbcon_fb_registered(info);

break;

......

}

done:

return ret;

}

帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的,它的实现如下所示:

static int fbcon_fb_registered(struct fb_info *info)

{

int ret = 0, i, idx = info->node;

fbcon_select_primary(info);

if (info_idx == -1) {

for (i = first_fb_vc; i <= last_fb_vc; i++) {

if (con2fb_map_boot[i] == idx) {

info_idx = idx;

break;

}

}

if (info_idx != -1)

ret = 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;

}

函数fbcon_select_primary用来检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备。如果是的话,那么就将它的信息记录下来。这个函数只有当指定了CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY编译选项时才有效,否则的话,它是一个空函数。

在Linux内核中,每一个控制台和每一个帧缓冲区硬件设备都有一个从0开始的编号,它们的初始对应关系保存在全局数组con2fb_map_boot中。控制台和帧缓冲区硬件设备的初始对应关系是可以通过设置内核启动参数来初始化的。在模块fbcon中,还有另外一个全局数组con2fb_map,也是用来映射控制台和帧缓冲区硬件设备的对应关系,不过它映射的是控制台和帧缓冲区硬件设备的实际对应关系。

全局变量first_fb_vc和last_fb_vc是全局数组con2fb_map_boot和con2fb_map的索引值,用来指定系统当前可用的控制台编号范围,它们也是可以通过设置内核启动参数来初始化的。全局变量first_fb_vc的默认值等于0,而全局变量last_fb_vc的默认值等于MAX_NR_CONSOLES - 1。

全局变量info_idx表示系统当前所使用的帧缓冲区硬件的编号。如果它的值等于-1,那么就说明系统当前还没有设置好当前所使用的帧缓冲区硬件设备。在这种情况下,函数fbcon_fb_registered就会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会将当前所注册的帧缓冲区硬件设备编号idx保存在全局变量info_idx中。接下来还会调用函数fbcon_takeover来初始化系统所使用的控制台。在调用函数fbcon_takeover的时候,传进去的参数为1,表示要显示第一个开机画面。

如果全局变量info_idx的值不等于-1,那么函数fbcon_fb_registered同样会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会调用函数set_con2fb_map来调整当前所注册的帧缓冲区硬件设备与控制台的映射关系,即调整数组con2fb_map_boot和con2fb_map的值。

为了简单起见,我们假设系统只有一个帧缓冲区硬件设备,这样当它被注册的时候,全局变量info_idx的值就会等于-1。当函数fbcon_fb_registered在全局数组con2fb_map_boot中发现有一个控制台的编号与这个帧缓冲区硬件设备的编号idx对应时,接下来就会调用函数fbcon_takeover来设置系统所使用的控制台。

函数fbcon_takeover的实现如下所示:

static int fbcon_takeover(int show_logo)

{

int err, i;

if (!num_registered_fb)

return -ENODEV;

if (!show_logo)

logo_shown = FBCON_LOGO_DONTSHOW;

for (i = first_fb_vc; i <= last_fb_vc; i++)

con2fb_map[i] = info_idx;

err = 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;

}

return err;

}

全局变量logo_shown的初始值为FBCON_LOGO_CANSHOW,表示可以显示第一个开机画面。但是当参数show_logo的值等于0的时候,全局变量logo_shown的值会被重新设置为FBCON_LOGO_DONTSHOW,表示不可以显示第一个开机画面。

中间的for循环将当前可用的控制台的编号都映射到当前正在注册的帧缓冲区硬件设备的编号info_idx中去,表示当前可用的控制台与缓冲区硬件设备的实际映射关系。

函数take_over_console用来初始化系统当前所使用的控制台。如果它的返回值不等于0,那么就表示初始化失败。在这种情况下,最后的for循环就会将全局数组con2fb_map的各个元素的值设置为-1,表示系统当前可用的控制台还没有映射到实际的帧缓冲区硬件设备中去。这时候全局变量info_idx的值也会被重新设置为-1。

调用函数take_over_console来初始化系统当前所使用的控制台,实际上就是向系统注册一系列回调函数,以便系统可以通过这些回调函数来操作当前所使用的控制台。这些回调函数使用结构体consw来描述。这里所注册的结构体consw是由全局变量fb_con来指定的,它的定义如下所示:

/*

*  The console `switch' structure for the frame buffer based console

*/

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_bmove              = fbcon_bmove,

.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,

};

安卓linux开机画面,Android系统的开机画面显示过程分析(1)相关推荐

  1. Android系统的开机画面显示过程分析(13)

          WindowManagerService类的成员函数performEnableScreen的实现如下所示: public class WindowManagerService extend ...

  2. X86 android r7 z3735,安卓工业平板电脑android系统下各大主流CPU性能大对比分析

    原标题:安卓工业平板电脑android系统下各大主流CPU性能大对比分析 针对工控领域客户在选择工业平板电脑时的困惑,南京研维组织多位业内专家,为大家连续推出10期工业平板电脑的选型要点分析,本文作为 ...

  3. Android系统手机开机画面各个阶段代码执行流程分析(Part1)

    提到Android系统的UI,我们最先接触到的便是系统在启动过程中所出现的画面了.Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段.本文将详细分析这三个开 ...

  4. Android系统手机开机画面各个阶段代码执行流程分析(Part2)

    3. 第三个开机画面的显示过程 第三个开机画面是由应用程序bootanimation来负责显示的.应用程序bootanimation在启动脚本init.rc中被配置成了一个服务,如下所示: servi ...

  5. Android系统的开机画面显示过程分析

    提到Android系统的UI,我们最先接触到的便是系统在启动过程中所出现的画面了.Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段.本文将详细分析这三个开 ...

  6. Android系统的开机画面显示过程分析(5)

    2. 第二个开机画面的显示过程       由于第二个开机画面是在init进程启动的过程中显示的,因此,我们就从init进程的入口函数main开始分析第二个开机画面的显示过程.       init进 ...

  7. Android系统的开机画面显示过程分析(8)

         3. 第三个开机画面的显示过程         第三个开机画面是由应用程序bootanimation来负责显示的.应用程序bootanimation在启动脚本init.rc中被配置成了一个服 ...

  8. Android系统定制开机logo和开机动画

    定制开机logo和开机动画 开机logo 开机动画 bootanimation.zip 生成zip文件 添加新的开机动画 开机logo MTK ASOP中lk阶段的logo是开机后的第一个界面,也被称 ...

  9. linux装回win10系统无法开机,Win10/Linux双系统删除之后出现grub无法开机修复方法...

    现在很多用户会把电脑安装成双系统,一个用于日常使用,一个用于测试学习,一些用户反馈Win10/linux双系统,在删除linux系统之后,Win10无法启动,显示grub>,那么遇到这样的问题要 ...

最新文章

  1. Unity Shaders
  2. 为什么要使用叶脊(leaf-spine)拓扑网络zz
  3. HtmlTextWriter学习
  4. 我遇到了Hibernate异常
  5. fatal error: Eigen3/Core: 没有那个文件或目录
  6. 开元弧焊机器人编程_【数据】2019年中国焊接机器人市场发展现状与趋势分析...
  7. centos 6.8 升级mysql_centos6.8 Mysql5.6.22 升级 mysql-5.7.20
  8. Could not find artifact com.sun:tools:jar:1.5.0 问题解决
  9. linux修改登录密码门,Linux更改用户密码
  10. 城建坐标与经纬度转换工具
  11. 苏宁大数据怎么运营_苏宁智慧门店是什么?智慧门店是如何运作的?
  12. 罗马数字转换python_Python实现将罗马数字转换成普通阿拉伯数字的方法
  13. 几何平均详解,及其与算术平均、调和平均、均方根的关系
  14. VSCode 之 设置 settings.json 配置文件
  15. 大数据相关面试题整理-带答案-难一点
  16. 2013蓝桥杯 CC++程序设计本科B组 第39级台阶
  17. 二、pixhawk光流传感器PX4FLOW
  18. 泛微金融行业方案合集,推动金融行业数字化转型
  19. php解析手机号 归属地,PHP通过API获取手机号码归属地,api手机号码_PHP教程
  20. 数据可视化工具之--百度图说

热门文章

  1. 电脑计算器_CPA考生注意!2020考场只允许带这种计算器
  2. 背包问题 贪心算法 java_JS基于贪心算法解决背包问题
  3. linux数据包注释,关于 linux中TCP数据包(SKB)序列号的小笔记
  4. java动态变量名反射_Java动态性—反射 - Eclipse666的个人空间 - OSCHINA - 中文开源技术交流社区...
  5. Sequence.js 实现带有视差滚动特效的图片滑块
  6. 移动端 fixed 固定按钮在屏幕下方,然后按钮被键盘顶上来...顶上来了有没有~
  7. 阅读react-redux源码 - 一
  8. 写在08年“愚人节”
  9. create your own github repository and build link to your local project
  10. # 20155337 2017-2018-1 《信息安全系统设计基础》第二周课堂实践+myod