Recovery UI更新分析
文章目录
1.Recovery流程
2.Recovery UI初始化流程分析
之前讲过Recovery 的流程。那么在定制化的过程当中,可能涉及到Recovery UI的改动。下面就介绍一下Recovery UI的一些东西。
首先还是从recovery的main函数开始分析;
int main(int argc, char **argv){
//中间处理命令的部分省略
1.这里就是开始创建UI的一个对象
Device* device = make_device();
ui = device->GetUI();
gCurrentUI = ui;
ui->SetLocale(locale);
//实例完之后开始进行初始化
ui->Init();//开启thread,监听按键
...
if (update_package != NULL) {
status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, mount_required);
...
if (status != INSTALL_SUCCESS) {
ui->Print("Installation aborted.\n");
// If this is an eng or userdebug build, then automatically
// turn the text display on if the script fails so the error
// message is visible.
if (is_ro_debuggable()) {//如果是debug版的话就显示升级的log
ui->ShowText(true);
}
}
}
}
下面将上面分为两部分来分析:
1.UI部分分析
首先看一下make_device里面做了什么事情
Device* make_device() {
return new Device(new ScreenRecoveryUI);
}
实现很简单,就是new了一个ScreenRecoveryUI的对象。
看一下ScreenRecoveryUI是如何实现的:
bootable/recovery/screen_ui.h
class ScreenRecoveryUI : public RecoveryUI {}
ScreenRecoveryUI继承自RecoveryUI。ScreenRecoveryUI中实现了画图所需要的各种接口.
下面看一下上面涉及到的接口
void ScreenRecoveryUI::Init() {
gr_init();//初始化显示设备,加载字体
gr_font_size(&char_width, &char_height);
text_rows_ = gr_fb_height() / char_height;
text_cols_ = gr_fb_width() / char_width;
text_ = Alloc2d(text_rows_, text_cols_ + 1);
file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1);
menu_ = Alloc2d(text_rows_, text_cols_ + 1);
text_col_ = text_row_ = 0;
text_top_ = 1;
//加载图片资源
backgroundIcon[NONE] = nullptr;
LoadBitmapArray("icon_installing", &installing_frames, &installation);
backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr;
backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
LoadBitmap("icon_error", &backgroundIcon[ERROR]);
backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
LoadBitmap("progress_empty", &progressBarEmpty);
LoadBitmap("progress_fill", &progressBarFill);
LoadBitmap("stage_empty", &stageMarkerEmpty);
LoadBitmap("stage_fill", &stageMarkerFill);
LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]);
LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]);
LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this);
//调用父类的初始化函数。
RecoveryUI::Init();
}
3.字体加载函数gr_init
int gr_init(void)
{
gr_init_font();//加载字体
//打开显示模块
if (!gr_draw) {
gr_backend = open_fbdev();
gr_draw = gr_backend->init(gr_backend);
if (gr_draw == NULL) {
return -1;
}
}
}
static void gr_init_font(void)
{
gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1));
int res = res_create_alpha_surface("font", &(gr_font->texture));//读取font图片中的数据
···
}
4.下面看一下图片加载函数LoadBitmap
void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) {
int result = res_create_display_surface(filename, surface);
if (result < 0) {
LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
}
}
int res_create_display_surface(const char* name, GRSurface** pSurface) {
GRSurface* surface = NULL;
//打开图片,获取图片的详细参数
result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
if (result < 0) return result;
//根据图片得参数初始化一个GRSurface,用来保存图片数据
surface = init_display_surface(width, height);
if (surface == NULL) {
result = -8;
goto exit;
}
//将图片中的数据按行的读取方式读取出来,保存到surface中。
for (y = 0; y < height; ++y) {
png_read_row(png_ptr, p_row, NULL);
transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
}
free(p_row);
*pSurface = surface;
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
if (result < 0 && surface != NULL) free(surface);
return result;
}
以上是如何读取图片并且保存的。在recovery里面的文字也是采用的这种方式读取的,这里就不再写出来了。这里需要说明的是,recovery中的文字也是从图片中截取出来,然后在显示出来的。
5.Recovery按键事件的监听
void RecoveryUI::Init() {
ev_init(InputCallback, this);//打开/dev/input,从里面读取event,并且注册按键处理函数InputCallback
pthread_create(&input_thread_, nullptr, InputThreadLoop, nullptr);//开启一个thread去监听key event
}
static void* InputThreadLoop(void*) {
while (true) {
if (!ev_wait(-1)) {
ev_dispatch();//分发事件调用InputCallback
}
}
return nullptr;
}
6.Recovery 中图片的显示
上面介绍了图片的读取,下面通过分期进度条的显示来看recovery的显示
void ScreenRecoveryUI::draw_progress_locked() {
...
if (progressBarType != EMPTY) {
//读取图片的宽度和高度
int em_width = gr_get_width(progressBarEmpty);
int em_height = gr_get_height(progressBarEmpty);
int fl_width = gr_get_width(progressBarFill);
int fl_height = gr_get_height(progressBarFill);
//获取到lcd的宽度
int dx = gr_fb_width();
int dy = (3*gr_fb_height() - 2*em_height)/4;
if (progressBarType == DETERMINATE) {
float p = progressScopeStart + progress * progressScopeSize;
int pos = (int) (p * em_height);//计算进度条的显示位置
if (rtl_locale){//right to left
if (pos >= 0) {//画有颜色进度条
gr_blit(progressBarFill, 0, fl_height, fl_width, dy/2 + pos, dx/2, dy/2);
}
if (pos < em_height-1){//画空进度条
gr_blit(progressBarEmpty, 0, 0, em_width, dy/2 + em_height - pos, dx/2, dy/2 + fl_height-pos);
}
}else {//left to right
if (pos >= 0) {
gr_blit(progressBarFill, 0, 0, fl_width, pos, dx/2, dy/2);
}
// this maybe incorrect. but if use em_height, maybe not fullfill
if (pos < fl_height-1){
gr_blit(progressBarEmpty, 0, pos, em_width, fl_height - pos, dx/2, dy/2 + pos);
}
}
}
}
}
这里面主要的就是gr_blit,下面看一下这个函数
/*
*source:被画的图片数据
*sx/sy:这是一个坐标对,表示从哪个点开始画,这是一个相对dx、dy的数据
*w/h:source的长和宽
*dx/dy:开始从哪个点开始画
*/
void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
if (source == NULL) return;
if (gr_draw->pixel_bytes != source->pixel_bytes) {
printf("gr_blit: source has wrong format\n");
return;
}
dx += overscan_offset_x;
dy += overscan_offset_y;
if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;//指向将要画出的数据
unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;//指向将要画到哪里
int i;
//将数据复制过去
for (i = 0; i < h; ++i) {
memcpy(dst_p, src_p, w * source->pixel_bytes);
src_p += source->row_bytes;
dst_p += gr_draw->row_bytes;
}
}
到目前为止,只是将数据放入到显示buffer中,并不能显示。要显示的话需要调用函数gr_flip()。这个函数就是将buffer中的数据显示到屏幕上。
7.Recovery UI 更新
下面讲一下,Recovery UI是如何得到更新的消息的。之前已经将Recovery 升级的过程讲过了,这里不再赘述。直接将关键的部分。
static int
try_update_binary(const char* path, ZipArchive* zip, bool* wipe_cache) {
···
//从升级包中获取升级的bin文件,即META-INF/com/google/android/update-binary
const ZipEntry* binary_entry =
mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);
const char* binary = "/tmp/update_binary";
unlink(binary);
int fd = creat(binary, 0755);
bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);读取到句柄中
int pipefd[2];
pipe(pipefd);
const char** args = (const char**)malloc(sizeof(char*) * 5);
args[0] = binary;
args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
char* temp = (char*)malloc(10);
sprintf(temp, "%d", pipefd[1]);
args[2] = temp;
args[3] = (char*)path;
args[4] = NULL;
//开体格新的thread
pid_t pid = fork();
if (pid == 0) {
umask(022);
close(pipefd[0]);
execv(binary, (char* const*)args);//在子进程中运行update-binary
fprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));
_exit(-1);
}
close(pipefd[1]);
*wipe_cache = false;
char buffer[1024];
FILE* from_child = fdopen(pipefd[0], "r");//在父进程中读取来自子进程中的消息
while (fgets(buffer, sizeof(buffer), from_child) != NULL) {
char* command = strtok(buffer, " \n");
if (command == NULL) {
continue;
} else if (strcmp(command, "progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
char* seconds_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
int seconds = strtol(seconds_s, NULL, 10);
ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);
} else if (strcmp(command, "set_progress") == 0) {
char* fraction_s = strtok(NULL, " \n");
float fraction = strtof(fraction_s, NULL);
ui->SetProgress(fraction);
} else if (strcmp(command, "ui_print") == 0) {
char* str = strtok(NULL, "\n");
if (str) {
ui->Print("%s", str);
} else {
ui->Print("\n");
}
fflush(stdout);
} else if (strcmp(command, "wipe_cache") == 0) {
*wipe_cache = true;
} else if (strcmp(command, "clear_display") == 0) {
ui->SetBackground(RecoveryUI::NONE);
} else if (strcmp(command, "enable_reboot") == 0) {
// packages can explicitly request that they want the user
// to be able to reboot during installation (useful for
// debugging packages that don't exit).
ui->SetEnableReboot(true);
} else {
LOGE("unknown command [%s]\n", command);
}
}
fclose(from_child);
int status;
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
return INSTALL_ERROR;
}
return INSTALL_SUCCESS;
}
下面将ota升级包中的脚本简单贴一部分
ui_print("Target: ASKEY/CDR6013/msm8952_64:6.0.1/CDR6013-DS-180307-1-40-4,release-keys");
show_progress(0.750000, 0);
ui_print这个函数是怎么实现的看一下
RegisterFunction("ui_print", UIPrintFn);
Value* UIPrintFn(const char* name, State* state, int argc, Expr* argv[]) {
char** args = ReadVarArgs(state, argc, argv);
//中间
uiPrint(state, buffer);
return StringValue(buffer);
}
void uiPrint(State* state, char* buffer) {
char* line = strtok(buffer, "\n");
UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
while (line) {
fprintf(ui->cmd_pipe, "ui_print %s\n", line);
line = strtok(NULL, "\n");
}
fprintf(ui->cmd_pipe, "ui_print\n");
}
这样父进程接收到ui_print命令之后就会处理UI上的显示了
到这里Recovery的UI就结束了。
字体图片: