android开机动画
有三次动画过程。
Linux内核启动显示第一动画
这里和linux的2个设备有关,一个是控制台设备(ttyx),一个是fb设备(显卡和显示器),内核启动的时候会初始化这两个设备,其中fb设备注册的时候会通知控制台设备,然后控制台设备处理响应:
//fb_info代表注册过的fb设备信息,idx是fb设备的设备编号
//con2fb_map_boot存储控制台和帧缓冲区硬件设备的初始对应关系(通过设置内核启动参数来初始化)
static int fbcon_fb_registered(struct fb_info *info)
{
int ret = 0, i, idx = info->node;
fbcon_select_primary(info);
if (info_idx == -1) {//i是控制台设备的编号,存在对应关系就把当前正在注册的fb设备编号记
//录下来。瞎猜:第一次主屏fb注册通知进来,info_idx为-1,找到对应
//关系后,转至fbcon_takeover,下次都是从屏
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;
}
static int fbcon_takeover(int show_logo)
{
int err, i;
if (!num_registered_fb)
return -ENODEV;
if (!show_logo)
logo_shown = FBCON_LOGO_DONTSHOW;
//让所有控制台的显示映射到当前正在注册的fb设备上
for (i = first_fb_vc; i <= last_fb_vc; i++)
con2fb_map[i] = info_idx;
//并为系统当前使用的所有控制台设备在某些行为下设置一些回调,例如fbcon_init,
//fbcon_switch
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;
}
//密集的回调
/*
* 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,
};
处理响应就是映射控制台设备和fb设备之间的关系,并为控制台设备设置回调。
内核系统现在指定要使用某个控制台了,首先初始化一个,然后在切换到它。
//vc参数中指定了要启用的控制台信息
static void fbcon_init(struct vc_data *vc, int init)
{ //通过控制台设备编号vc->vc_num找到对应的fb设备信息fb_info
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];
int logo = 1, new_rows, new_cols, rows, cols, charcnt = 256;
int cap;
if (info_idx == -1 || info == NULL)
return;
......
if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||
(info->fix.type == FB_TYPE_TEXT))
logo = 0;
......
if (logo)
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
......
}
可以看出:正在初始化的控制台不是系统当前可见的控制台;logo_shown的值等于FBCON_LOGO_DONTSHOW;正在初始化的控制台所对应的帧缓冲区硬件设备的显示方式为文本方式,这三种情况就不要显示这一阶段的开机动画了。
fbcon_prepare_logo之后也只是为全局变量fb_logo的成员变量logo设置了linux_logo类型的值,linux_logo的data成员其中就是动画数据,可能是文件路径,可能是数据的指针,可能...管他呢。并没有开启动画。
fbcon_switch才开启了动画:
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, prev_console, charcnt = 256;
......
if (logo_shown == FBCON_LOGO_DRAW) {
logo_shown = fg_console;
/* This is protected above by initmem_freed */
fb_show_logo(info, ops->rotate);
......
return 0;
}
return 1;
}
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());
......
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;
struct fb_image image;
/* Return if the frame buffer is not mapped or suspended */
if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
info->flags & FBINFO_MODULE)
return 0;
image.depth = 8;
image.data = logo->data;//数据转换到image结构
if (fb_logo.needs_cmapreset)
fb_set_logocmap(info, logo);
if (fb_logo.needs_truepalette ||
fb_logo.needs_directpalette) {
palette = kmalloc(256 * 4, GFP_KERNEL);
if (palette == NULL)
return 0;
if (fb_logo.needs_truepalette)
fb_set_logo_truepalette(info, logo, palette);
else
fb_set_logo_directpalette(info, logo, palette);
saved_pseudo_palette = info->pseudo_palette;
info->pseudo_palette = palette;
}
if (fb_logo.depth <= 4) {
logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL);
if (logo_new == NULL) {
kfree(palette);
if (saved_pseudo_palette)
info->pseudo_palette = saved_pseudo_palette;
return 0;
}
image.data = logo_new;
fb_set_logo(info, logo, logo_new, fb_logo.depth);
}
image.dx = 0;
image.dy = y;
image.width = logo->width;
image.height = logo->height;
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);
kfree(palette);
if (saved_pseudo_palette != NULL)
info->pseudo_palette = saved_pseudo_palette;
kfree(logo_new);
kfree(logo_rotate);
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++) {
info->fbops->fb_imageblit(info, image);//开启动画
image->dx += image->width + 8;
}
} else if (rotate == FB_ROTATE_UD) {
for (x = 0; x < num && image->dx >= 0; 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 && image->dy >= 0; x++) {
info->fbops->fb_imageblit(info, image);
image->dy -= image->height + 8;
}
}
}
init进程启动的过程中显示第二画面
int main(int argc, char **argv)
{
int fd_count = 0;
struct pollfd ufds[4];
......
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
......
//直接插入console_init_action到对列中
queue_builtin_action(console_init_action, "console_init");
......
for(;;) {
int nr, i, timeout = -1;
//一次循环取出前面入队的一个action来执行
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
......
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
有一次取到了console_init_action来执行,于是本次的开机画面出现了:
static int console_init_action(int nargs, char **args)
{
int fd;
char tmp[PROP_VALUE_MAX];
if (console[0]) {
snprintf(tmp, sizeof(tmp), "/dev/%s", console);
console_name = strdup(tmp);
}
fd = open(console_name, O_RDWR);
if (fd >= 0)
have_console = 1;
close(fd);
if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n" // console is 40 cols x 30 lines
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}
return 0;
}
#define INIT_IMAGE_FILE "/initlogo.rle"
/* 565RLE image format: [count(2 bytes), rle(2 bytes)] */
int load_565rle_image(char *fn)
{
struct FB fb;
struct stat s;
unsigned short *data, *bits, *ptr;
unsigned count, max;
int fd;
if (vt_set_mode(1))
return -1;
fd = open(fn, O_RDONLY);
if (fd < 0) {
ERROR("cannot open '%s'\n", fn);
goto fail_restore_text;
}
if (fstat(fd, &s) < 0) {
goto fail_close_file;
}
data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
goto fail_close_file;
if (fb_open(&fb))
goto fail_unmap_data;
max = fb_width(&fb) * fb_height(&fb);
ptr = data;
count = s.st_size;
bits = fb.bits;//获取帧缓冲地址
while (count > 3) {
unsigned n = ptr[0];
if (n > max)
break;
android_memset16(bits, ptr[1], n << 1);
bits += n;//直接往帧缓冲地址处一点一点的写数据
max -= n;
ptr += 2;
count -= 4;
}
munmap(data, s.st_size);
fb_update(&fb);
fb_close(&fb);
close(fd);
unlink(fn);
return 0;
fail_unmap_data:
munmap(data, s.st_size);
fail_close_file:
close(fd);
fail_restore_text:
vt_set_mode(0);
return -1;
}
//将控制台的显示方式设备为图形方式
static int vt_set_mode(int graphics)
{
int fd, r;
fd = open("/dev/tty0", O_RDWR | O_SYNC);
if (fd < 0)
return -1;
r = ioctl(fd, KDSETMODE, (void*) (graphics ? KD_GRAPHICS : KD_TEXT));
close(fd);
return r;
}
//打开fb设备,并mmap到fb设备的帧缓存地址
static int fb_open(struct FB *fb)
{
fb->fd = open("/dev/graphics/fb0", O_RDWR);
if (fb->fd < 0)
return -1;
if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0)
goto fail;
if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0)
goto fail;
fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE,
MAP_SHARED, fb->fd, 0);
if (fb->bits == MAP_FAILED)
goto fail;
return 0;
fail:
close(fb->fd);
return -1;
}
SurfaceFlinger初始化时开启第三开机动画
status_t SurfaceFlinger::readyToRun()
{
LOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
......
mReadyToRunBarrier.open();
/*
* We're now ready to accept clients...
*/
// start boot animation
property_set("ctl.start", "bootanim");
return NO_ERROR;
}
上一个节我们知道,init进程会通过pool系统调用监视一些文件描述符,其中就有“property_service”这个socket文件描述符,当多进程共享的属性区域中有属性变化的时候(客户端(SurfaceFlinger)进程对其操作的时候),init进程会检测到,从而做出相应:
void handle_property_set_fd()
{
prop_msg msg;
int s;
int r;
int res;
struct ucred cr;
struct sockaddr_un addr;
socklen_t addr_size = sizeof(addr);
socklen_t cr_size = sizeof(cr);
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
/* Check socket options here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to recieve socket options\n");
return;
}
r = recv(s, &msg, sizeof(msg), 0);
close(s);
if(r != sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size recieved: %d expected: %d\n",
r, sizeof(prop_msg));
return;
}
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
//处理关于属性的控制消息
if(memcmp(msg.name,"ctl.",4) == 0) {//"ctl.start", "bootanim"
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid: %d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.pid);
}
} else {//处理非控制属性
if (check_perms(msg.name, cr.uid, cr.gid)) {
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
}
break;
default:
break;
}
}
//"start", "bootanim"
void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
//"bootanim"
static void msg_start(const char *name)
{
struct service *svc;
char *tmp = NULL;
char *args = NULL;
if (!strchr(name, ':'))
svc = service_find_by_name(name);
else {
tmp = strdup(name);
args = strchr(tmp, ':');
*args = '\0';
args++;
svc = service_find_by_name(tmp);
}
if (svc) {
service_start(svc, args);//启动bootanim应用程序
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
}
上面启动所对应的应用程序为/system/bin/bootanimation,它就是第三动画程序,这个动画是无限次动画,需要程序的其他地方来停止它。
直到当Lunach应用的Activity组件启动起来的时候(还未可见),会向ActivityManagerService发送一个Activity组件空闲通知:
IActivityManager am = ActivityManagerNative.getDefault();
ActivityClientRecord prev;
do {
......
if (a.activity != null && !a.activity.mFinished) {
try {
am.activityIdle(a.token, a.createdConfig);
a.createdConfig = null;
} catch (RemoteException ex) {
}
}
ActivityManagerService接收到,会向WindowManagerService发送:
void enableScreenAfterBoot() {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
SystemClock.uptimeMillis());
mWindowManager.enableScreenAfterBoot();
}
WindowManagerService接收到,会向SurfaceFlinger发FIRST_CALL_TRANSACTION消息:
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
......
public void performEnableScreen() {
synchronized(mWindowMap) {
if (mDisplayEnabled) {
return;
}
if (!mSystemBooted) {
return;
}
......
mDisplayEnabled = true;
......
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
//Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION,
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");
}
}
......
}
......
}
SurfaceFlinger接收到消息FIRST_CALL_TRANSACTION后,会设置系统属性ctl.stop的值:
void SurfaceFlinger::bootFinished()
{
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
mBootFinished = true;
property_set("ctl.stop", "bootanim");
}
属性有变化了,int进程会检测到,做出相应:
void handle_control_message(const char *msg, const char *arg)
{
if (!strcmp(msg,"start")) {
msg_start(arg);
} else if (!strcmp(msg,"stop")) {
msg_stop(arg);
} else {
ERROR("unknown control msg '%s'\n", msg);
}
}
static void msg_stop(const char *name)
{
struct service *svc = service_find_by_name(name);
if (svc) {
service_stop(svc);
} else {
ERROR("no such service '%s'\n", name);
}
}
第三动画停止,此时Lanch应用的MainActivty也可见了。
这是对老罗博客的笔记,对第一和第二动画有点担心,因为不知道他们的动画是否异步,之间是否会出现黑屏,第二和第三之间是否黑屏?码完。