Android recovery UI实现分析

大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述Android recovery UI实现分析相关的知识,希望能为你提供帮助。
android recovery模式为何物?
关于这个问题, baidu上已经有无数的答案。不理解的朋友先补习一下。
从纯技术角度来讲, recovery和android本质上是两个独立的rootfs,    仅仅是recovery这个rootfs存在的意义就是为android这个rootfs服务,因此被解释为Android系统的一部分。 
  recovery作为一个简单的rootfs, 提供了很有限的几个功能,仅仅包括了几个简单的库,UI的显示採用的是直接刷framebuffer的形式,作为android framework及app层的码农,对这样的形式相对陌生,特抽点时间梳理了一番。   首先,浏览一下reocvery的main函数代码中UI相关的语句

main(int argc, char **argv) {......Device* device = make_device(); ui = device-> GetUI(); gCurrentUI = ui; ui-> Init(); ui-> SetLocale(locale); ui-> SetBackground(RecoveryUI::NONE); if (show_text) ui-> ShowText(true); ......if (status != INSTALL_SUCCESS || ui-> IsTextVisible()) { prompt_and_wait(device, status); }...... }

1、首先新建了一个Device类的对象, Device类封装了一些操作,包含UI的操作 2、调用Device类的GetUI()返回一个DefaultUI对象,recovery中涉及到三个UI类,三个类之间为继承关系,分别为DefaultUI、     ScreenRecoveryUI、RecoveryUI 3、调用DefaultUI类的Init(), DefaultUI类没有Init()方法,因此将调用它的父类ScreenRecoveryUI的Init() 4、同理。调用ScreenRecoveryUI类的SetLocale()来标识几个比較特别的区域 5、同理。调用ScreenRecoveryUI类的SetBackground()设置初始状态的背景图6、显示recovery的主界面,即一个选择菜单 
void ScreenRecoveryUI::Init() { gr_init(); gr_font_size(& char_width, & char_height); text_col = text_row = 0; text_rows = gr_fb_height() / char_height; if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; text_cols = gr_fb_width() / char_width; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; LoadBitmap("icon_installing", & backgroundIcon[INSTALLING_UPDATE]); backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; LoadBitmap("icon_error", & backgroundIcon[ERROR]); backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; LoadBitmap("progress_empty", & progressBarEmpty); LoadBitmap("progress_fill", & progressBarFill); LoadLocalizedBitmap("installing_text", & backgroundText[INSTALLING_UPDATE]); LoadLocalizedBitmap("erasing_text", & backgroundText[ERASING]); LoadLocalizedBitmap("no_command_text", & backgroundText[NO_COMMAND]); LoadLocalizedBitmap("error_text", & backgroundText[ERROR]); int i; progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames * sizeof(gr_surface)); for (i = 0; i < indeterminate_frames; ++i) { char filename[40]; // "indeterminate01.png", "indeterminate02.png", ... sprintf(filename, "indeterminate%02d", i+1); LoadBitmap(filename, progressBarIndeterminate+i); }if (installing_frames > 0) { installationOverlay = (gr_surface*)malloc(installing_frames * sizeof(gr_surface)); for (i = 0; i < installing_frames; ++i) { char filename[40]; // "icon_installing_overlay01.png", // "icon_installing_overlay02.png", ... sprintf(filename, "icon_installing_overlay%02d", i+1); LoadBitmap(filename, installationOverlay+i); } } else { installationOverlay = NULL; }pthread_create(& progress_t, NULL, progress_thread, NULL); RecoveryUI::Init(); }



【Android recovery UI实现分析】 
1、gr_init()    初始化图形设备,分配Pixelflinger库渲染的内存
2、gr_font_size()   将字体相应的surface长宽赋值给char_width和char_height
3、LoadBitmap()  将png生成surface, 每一个png图片相应一个surface, 全部surface存放在一个数组中 4、LoadLocalizedBitmap()  将区域文字所在的图片中的text信息依据当前的locale提取出来,生成相应的surface, 所以     surface也存放在一个数组中 6、pthread_create(& progress_t, NULL, progress_thread, NULL) 创建一个线程,该线程的任务是一个死循环,在该循环中不停     地检測currentIcon以及progressBarType来决定是不是要更新进度条。 7、调用RecoveryUI的Init()。初始化输入事件处理。 

void ScreenRecoveryUI::SetLocale(const char* locale) { if (locale) { char* lang = strdup(locale); for (char* p = lang; *p; ++p) { if (*p == ‘_‘) { *p = ‘\0‘; break; } }// A bit cheesy: keep an explicit list of supported languages // that are RTL. if (strcmp(lang, "ar") == 0 ||// Arabic strcmp(lang, "fa") == 0 ||// Persian (Farsi) strcmp(lang, "he") == 0 ||// Hebrew (new language code) strcmp(lang, "iw") == 0 ||// Hebrew (old language code) strcmp(lang, "ur") == 0) {// Urdu rtl_locale = true; } free(lang); } }


  ScreenRecoveryUI类的SetLocale, 该函数依据locale推断所用的字体是否属于阿拉伯语系。阿拉伯语的书写习惯是从右到左,假设是阿拉伯语系的话,就设置一个标志,后面依据这个标志决定从右到左显示文字或进度条。

SetLocale的參数locale赋值逻辑是这种,先从command文件里读取, command文件里设置locale的命令如"--locale=zh_CN“。假设没有传入locale,初始化过程中会尝试从/cache/recovery/last_locale中读取locale, 假设该文件也没有,则locale不会被赋值。就默认用English.    
void ScreenRecoveryUI::SetBackground(Icon icon) { pthread_mutex_lock(& updateMutex); // Adjust the offset to account for the positioning of the // base image on the screen. if (backgroundIcon[icon] != NULL) { gr_surface bg = backgroundIcon[icon]; gr_surface text = backgroundText[icon]; overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2; overlay_offset_y = install_overlay_offset_y + (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2; }currentIcon = icon; update_screen_locked(); pthread_mutex_unlock(& updateMutex); }

  SetBackground函数比較简洁,关键部分在update_screen_locked,以下我们重点分析一下。   update_screen_locked和update_progress_locked是recovery的UI部分的关键函数。update_screen_locked用来更新背景,  update_progress_locked用来更新进度条,由于显示的画面会一直在更新。所以这两个函数会在不同的地方被重复调用
void ScreenRecoveryUI::update_screen_locked() { draw_screen_locked(); gr_flip(); }

  update_screen_locked包括两个操作,一是更新screen, 二是切换前后buffer。  
void ScreenRecoveryUI::draw_screen_locked() { draw_background_locked(currentIcon); draw_progress_locked(); if (show_text) { SetColor(TEXT_FILL); gr_fill(0, 0, gr_fb_width(), gr_fb_height()); int y = 0; int i = 0; if (show_menu) { SetColor(HEADER); for (; i < menu_top + menu_items; ++i) { if (i == menu_top) SetColor(MENU); if (i == menu_top + menu_sel) { // draw the highlight bar SetColor(MENU_SEL_BG); gr_fill(0, y-2, gr_fb_width(), y+char_height+2); // white text of selected item SetColor(MENU_SEL_FG); if (menu[i][0]) gr_text(4, y, menu[i], 1); SetColor(MENU); } else { if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top); } y += char_height+4; } SetColor(MENU); y += 4; gr_fill(0, y, gr_fb_width(), y+2); y += 4; ++i; }SetColor(LOG); // display from the bottom up, until we hit the top of the // screen, the bottom of the menu, or we‘ve displayed the // entire text buffer. int ty; int row = (text_top+text_rows-1) % text_rows; for (int ty = gr_fb_height() - char_height, count = 0; ty > y+2 & & count < text_rows; ty -= char_height, ++count) { gr_text(4, ty, text[row], 0); --row; if (row < 0) row = text_rows-1; } } }


  draw_background_locked函数的实现代码中又出现了几个以gr_开头的函数,以gr_开头的函数来自minui库,minui库的代码在recovery源代码下的minui文件夹下,minui提供的接口实现了图形的描绘以及固定大小的文字显示。
gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);   /* 设置字体颜色 */   gr_fill(int x, int y, int w, int h);   /* 填充矩形区域,參数分别代表起始坐标、矩形区域大小 */   gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);   /* 填充由source指定的图片 */  
draw_background_locked函数先将整个渲染buffer填充为黑色,然后计算背景surface的长宽,文字surface的长宽,再结合fb的长宽计算出背景surface以及文字surface显示的坐标。有长宽和坐标就能够调用Pixelflinger的接口在渲染buffer上进行渲染。  
void ScreenRecoveryUI::draw_progress_locked() { if (currentIcon == ERROR) return; if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { draw_install_overlay_locked(installingFrame); }if (progressBarType != EMPTY) { int iconHeight = gr_get_height(backgroundIcon[INSTALLING_UPDATE]); int width = gr_get_width(progressBarEmpty); int height = gr_get_height(progressBarEmpty); int dx = (gr_fb_width() - width)/2; int dy = (3*gr_fb_height() + iconHeight - 2*height)/4; // Erase behind the progress bar (in case this was a progress-only update) gr_color(0, 0, 0, 255); gr_fill(dx, dy, width, height); if (progressBarType == DETERMINATE) { float p = progressScopeStart + progress * progressScopeSize; int pos = (int) (p * width); if (rtl_locale) { // Fill the progress bar from right to left. if (pos > 0) { gr_blit(progressBarFill, width-pos, 0, pos, height, dx+width-pos, dy); } if (pos < width-1) { gr_blit(progressBarEmpty, 0, 0, width-pos, height, dx, dy); } } else { // Fill the progress bar from left to right. if (pos > 0) { gr_blit(progressBarFill, 0, 0, pos, height, dx, dy); } if (pos < width-1) { gr_blit(progressBarEmpty, pos, 0, width-pos, height, dx+pos, dy); } } }if (progressBarType == INDETERMINATE) { static int frame = 0; gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy); // in RTL locales, we run the animation backwards, which // makes the spinner spin the other way. if (rtl_locale) { frame = (frame + indeterminate_frames - 1) % indeterminate_frames; } else { frame = (frame + 1) % indeterminate_frames; } } } }


  draw_progress_locked函数的原理与  update_screen_locked函数类似。 终于是将进度条的surface输出到渲染buffer,  recovery中各个场景的画面,就是由背景、文字、进度条的重叠,不同的是所用的surface 以及surface的坐标。  recovery main函数中的UI代码基本上已经分析过了。最后一点主菜单的显示。就是通过上面介绍的这些接口将文字图片显示出来,因此就不再多讲。总的来说。recovery的UI显示部分难度不大,应用层调用minui库实现了图形的描绘以及固定大小的文字显示。minui库调用了Pixelflinger库来进行渲染。

  附上minui部分接口的说明,供參考
int gr_init(void); /* 初始化图形显示,主要是打开设备、分配内存、初始化一些參数 */ void gr_exit(void); /* 注销图形显示,关闭设备并释放内存 */int gr_fb_width(void); /* 获取屏幕的宽度 */ int gr_fb_height(void); /* 获取屏幕的高度 */ gr_pixel *gr_fb_data(void); /* 获取显示数据缓存的地址 */ void gr_flip(void); /* 刷新显示内容 */ void gr_fb_blank(bool blank); /* 清屏 */void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); /* 设置字体颜色 */ void gr_fill(int x, int y, int w, int h); /* 填充矩形区域,參数分别代表起始坐标、矩形区域大小 */ int gr_text(int x, int y, const char *s); /* 显示字符串 */ int gr_measure(const char *s); /* 获取字符串在默认字库中占用的像素长度 */ void gr_font_size(int *x, int *y); /* 获取当前字库一个字符所占的长宽 */void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); /* 填充由source指定的图片 */ unsigned int gr_get_width(gr_surface surface); /* 获取图片宽度 */ unsigned int gr_get_height(gr_surface surface); /* 获取图片高度 */ /* 依据图片创建显示资源数据,name为图片在mk文件指定的相对路径 */ int res_create_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); /* 释放资源数据 */







    推荐阅读