今天来分析一下Android的关机充电动画是如何实现的。至于充电启动的流程,之后有时间再做分析,这里主要分析跟Android关机充电相关的部分。不多说直接开始分析代码吧。
healthd_mode_charger_init
首先是healthd_mode_charger.cpp文件的healthd_mode_charger_init函数,这个函数是充电模式animation的初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
void healthd_mode_charger_init(struct healthd_config* config) { int ret; // charger的状态是charger_state charger* charger = &charger_state; int i; int epollfd; // 初始化kernel log dump_last_kmsg(); LOGW("--------------- STARTING CHARGER MODE ---------------\n"); ret = ev_init(std::bind(&input_callback, charger, std::placeholders::_1, std::placeholders::_2)); if (!ret) { epollfd = ev_get_epollfd(); healthd_register_event(epollfd, charger_event_handler, EVENT_WAKEUP_FD); } // 解析animation.txt文件的内容,初始化animation结构体,下面会详细分析 // (大家可以先阅读这个函数的内容是什么,然后再返回这里往下看) animation* anim = init_animation(); charger->batt_anim = anim; // 看函数名及参数可知,用于创建电池状态出错的界面 ret = res_create_display_surface(anim->fail_file.c_str(), &charger->surf_unknown); if (ret < 0) { LOGE("Cannot load custom battery_fail image. Reverting to built in.\n"); ret = res_create_display_surface("charger/battery_fail", &charger->surf_unknown); if (ret < 0) { LOGE("Cannot load built in battery_fail image\n"); charger->surf_unknown = NULL; } } GRSurface** scale_frames; int scale_count; int scale_fps; // 从函数名可知,从png文件中创建多帧动画界面 // 这个函数会根据png图片头部信息,将一张png图切成多个frame // 因为之前UI部门给的图片都是动画切出来的单张图片,所以这个函数很少用到 ret = res_create_multi_display_surface(anim->animation_file.c_str(), &scale_count, &scale_fps, &scale_frames); // 判断出错信息 if (ret < 0) { LOGE("Cannot load battery_scale image\n"); anim->num_frames = 0; anim->num_cycles = 1; } else if (scale_count != anim->num_frames) { LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count, anim->num_frames); anim->num_frames = 0; anim->num_cycles = 1; } else { for (i = 0; i < anim->num_frames; i++) { anim->frames[i].surface = scale_frames[i]; } } ev_sync_key_state( std::bind(&set_key_callback, charger, std::placeholders::_1, std::placeholders::_2)); charger->next_screen_transition = -1; charger->next_key_check = -1; charger->next_pwr_check = -1; healthd_config = config; charger->boot_min_cap = config->boot_min_cap; } |
init_animation
init_animation函数同样在healthd_mode_charger.cpp文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
animation* init_animation() { bool parse_success; std::string content; // 读取animation.txt文件信息,animation_desc_path定义如下 // static constexpr const char* animation_desc_path="/res/values/charger/animation.txt"; if (base::ReadFileToString(animation_desc_path, &content)) { // 解析读取到的animation文件的内容,该函数在下面分析 // 大家也可以往下拉看怎么分析的,再回来这里往下看 parse_success = parse_animation_desc(content, &battery_animation); } else { LOGW("Could not open animation description at %s\n", animation_desc_path); parse_success = false; } if (!parse_success) { // 解析失败,使用默认的animation动画 LOGW("Could not parse animation description. Using default animation.\n"); battery_animation = BASE_ANIMATION; battery_animation.animation_file.assign("charger/battery_scale"); battery_animation.frames = default_animation_frames; battery_animation.num_frames = ARRAY_SIZE(default_animation_frames); } if (battery_animation.fail_file.empty()) { // 未定义电池信息未知动画,就采用默认的电池信息未知动画 battery_animation.fail_file.assign("charger/battery_fail"); } // 输出解析到的内容 LOGV("Animation Description:\n"); LOGV(" animation: %d %d '%s' (%d)\n", battery_animation.num_cycles, battery_animation.first_frame_repeats, battery_animation.animation_file.c_str(), battery_animation.num_frames); LOGV(" fail_file: '%s'\n", battery_animation.fail_file.c_str()); LOGV(" clock: %d %d %d %d %d %d '%s'\n", battery_animation.text_clock.pos_x, battery_animation.text_clock.pos_y, battery_animation.text_clock.color_r, battery_animation.text_clock.color_g, battery_animation.text_clock.color_b, battery_animation.text_clock.color_a, battery_animation.text_clock.font_file.c_str()); LOGV(" percent: %d %d %d %d %d %d '%s'\n", battery_animation.text_percent.pos_x, battery_animation.text_percent.pos_y, battery_animation.text_percent.color_r, battery_animation.text_percent.color_g, battery_animation.text_percent.color_b, battery_animation.text_percent.color_a, battery_animation.text_percent.font_file.c_str()); // 输出每帧动画的信息,显示时间,最低显示电量和最高显示电量 for (int i = 0; i < battery_animation.num_frames; i++) { LOGV(" frame %.2d: %d %d %d\n", i, battery_animation.frames[i].disp_time, battery_animation.frames[i].min_level, battery_animation.frames[i].max_level); } return &battery_animation; } |
parse_animation_desc
parse_animation_desc函数在AnimationParser.cpp文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
bool parse_animation_desc(const std::string& content, animation* anim) { // 定义animation.txt文件可以解析的字段 static constexpr const char* animation_prefix = "animation: "; static constexpr const char* fail_prefix = "fail: "; static constexpr const char* clock_prefix = "clock_display: "; static constexpr const char* percent_prefix = "percent_display: "; static constexpr const char* frame_prefix = "frame: "; // 帧动画vector std::vector<animation::frame> frames; // for循环逐行解析 // 以 animation: 3 2 charger/boot_charger_02 为例 for (const auto& line : base::Split(content, "\n")) { animation::frame frame; const char* rest; // can_ignore_line用于跳过空行和'#'开头的行 if (can_ignore_line(line.c_str())) { continue; // remove_prefix会根据第二个参数是否符合,上面的例子刚好符合这一项 // 符合对应xxx_prefix后,会将"animation: "之后的内容,即"3 2 charger/boot_charger_02" // 返回给rest,remove_prefix函数下面也会分析 } else if (remove_prefix(line, animation_prefix, &rest)) { int start = 0, end = 0; // sscanf将3赋给anim->num_cycles,其余类似 // 需要说明的是%n用于获取sscanf到%n未知读取的字符数,然后赋给start或者end // 这里"%n%*s%n"就是将"charger/boot_charger_02"字符串在line中的 // 起始结束位置赋给start、end if (sscanf(rest, "%d %d %n%*s%n", &anim->num_cycles, &anim->first_frame_repeats, &start, &end) != 2 || end == 0) { LOGE("Bad animation format: %s\n", line.c_str()); return false; } else { // 如果上面解析成功,就将"charger/boot_charger_02"赋给animation_file anim->animation_file.assign(&rest[start], end - start); } // 下面的内容类似 } else if (remove_prefix(line, fail_prefix, &rest)) { anim->fail_file.assign(rest); } else if (remove_prefix(line, clock_prefix, &rest)) { if (!parse_text_field(rest, &anim->text_clock)) { LOGE("Bad clock_display format: %s\n", line.c_str()); return false; } } else if (remove_prefix(line, percent_prefix, &rest)) { if (!parse_text_field(rest, &anim->text_percent)) { LOGE("Bad percent_display format: %s\n", line.c_str()); return false; } } else if (sscanf(line.c_str(), " frame: %d %d %d", &frame.disp_time, &frame.min_level, &frame.max_level) == 3) { frames.push_back(std::move(frame)); } else { LOGE("Malformed animation description line: %s\n", line.c_str()); return false; } } if (anim->animation_file.empty() || frames.empty()) { LOGE("Bad animation description. Provide the 'animation: ' line and at least one 'frame: ' " "line.\n"); return false; } anim->num_frames = frames.size(); anim->frames = new animation::frame[frames.size()]; std::copy(frames.begin(), frames.end(), anim->frames); return true; } |
这里因为公司使用的是将gif动画切出来的一张张独立的图,不像默认动画battery_scale.png图那样,一张图包含所有的充电动画surface信息,所以这里我们可以修改这个函数:
- 在animation结构体里面的frame结构体中添加一个string类型的字段frame_file,用来记录对应的frame文件名
- 修改frame: 这个prefix的解析内容,添加frame文件的文件名字段解析,例如:frame: 50 16 20 charger/charging_animation_09,将最后的charger/charging_animation_09内容记录到新添加的frame_file字段中
- 根据frame_file指定的文件名,通过res_create_display_surface函数来为每一帧动画图创建对应的frame,因为animation的frames指针就是用来记录动画内容的,所以将创建的frame记录到frames中即可。
- 需要注意注释掉healthd_mode_charger_init函数中通过charger/scale创建muti frame的代码
remove_prefix
remove_prefix函数同样在AnimationParser.cpp文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
bool remove_prefix(const std::string& line, const char* prefix, const char** rest) { const char* str = line.c_str(); int start; char c; // 经过解析后format为" animation: %n%c" std::string format = base::StringPrintf(" %s%%n%%c", prefix); // sscanf解析后,start为"animation: 3 2 charger/boot_charger_02"字符串中3的所在位置 if (sscanf(str, format.c_str(), &start, &c) != 1) { return false; } // rest为"3 2 charger/boot_charger_02"字符串 *rest = &str[start]; return true; } |
到这里,动画的初始化过程就基本讲完了,不过还有一个需要注意的地方就是,在调用res_create_display_surface的时候会调用open_png函数来打开png图片,在这个函数里面会判断png图片是否符合要求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
............... if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) { // 8-bit RGB images: great, nothing to do. } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) { // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. png_set_expand_gray_1_2_4_to_8(*png_ptr); } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) { // paletted images: expand to 8-bit RGB. Note that we DON'T // currently expand the tRNS chunk (if any) to an alpha // channel, because minui doesn't support alpha channels in // general. png_set_palette_to_rgb(*png_ptr); *channels = 3; } else { fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth, *channels, color_type); result = -7; goto exit; } ................. |
从上面一段代码可以看出,animation动画图支持3通道的RGB图,4通道的RGBA类型的图是支持不了的,这里的A指的是Alpha通道;另外就是单通道灰阶图也是支持的。下面分析一下动画显示过程
update_screen_state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
static void update_screen_state(charger* charger, int64_t now) { animation* batt_anim = charger->batt_anim; int disp_time; if (!batt_anim->run || now < charger->next_screen_transition) return; // 如下一段代码主要是为了点亮屏幕,如果无法点亮屏幕就退出 if (healthd_draw == nullptr) { if (healthd_config && healthd_config->screen_on) { if (!healthd_config->screen_on(batt_prop)) { LOGV("[%" PRId64 "] leave screen off\n", now); batt_anim->run = false; charger->next_screen_transition = -1; if (charger->charger_connected) request_suspend(true); return; } } healthd_draw.reset(new HealthdDraw(batt_anim)); #ifndef CHARGER_DISABLE_INIT_BLANK healthd_draw->blank_screen(true); #endif } // 下面判断是否动画已经结束,如果结束清除中间变量然后返回 if (batt_anim->num_cycles > 0 && batt_anim->cur_cycle == batt_anim->num_cycles) { reset_animation(batt_anim); charger->next_screen_transition = -1; healthd_draw->blank_screen(true); LOGV("[%" PRId64 "] animation done\n", now); if (charger->charger_connected) request_suspend(true); return; } // 获取该帧显示的时间 disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time; // 动画开始 if (batt_anim->cur_frame == 0) { LOGV("[%" PRId64 "] animation starting\n", now); if (batt_prop) { // 从battery service中获取当前点亮和电池状态 batt_anim->cur_level = batt_prop->batteryLevel; batt_anim->cur_status = batt_prop->batteryStatus; if (batt_prop->batteryLevel >= 0 && batt_anim->num_frames != 0) { // 找到第一张符合对应电量要求的帧 for (int i = 0; i < batt_anim->num_frames; i++) { if (batt_anim->cur_level >= batt_anim->frames[i].min_level && batt_anim->cur_level <= batt_anim->frames[i].max_level) { batt_anim->cur_frame = i; break; } } // 动画第一帧可以设置重复显示次数,所以需要更新第一帧显示的时间 disp_time = batt_anim->frames[batt_anim->cur_frame].disp_time * batt_anim->first_frame_repeats; } } } // 如果动画显示循环次数设置为0就,就直接灭屏 if (batt_anim->cur_cycle == 0) healthd_draw->blank_screen(false); // 调用redraw_screen刷新屏幕 healthd_draw->redraw_screen(charger->batt_anim, charger->surf_unknown); if (batt_anim->num_frames == 0 || batt_anim->cur_level < 0) { LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now); charger->next_screen_transition = now + BATTERY_UNKNOWN_TIME; batt_anim->cur_cycle++; return; } // 计算下一张动画的切换时间 charger->next_screen_transition = now + disp_time; // 如果还插着充电器 if (charger->charger_connected) { // 记录下一帧动画的索引 batt_anim->cur_frame++; while (batt_anim->cur_frame < batt_anim->num_frames && (batt_anim->cur_level < batt_anim->frames[batt_anim->cur_frame].min_level || batt_anim->cur_level > batt_anim->frames[batt_anim->cur_frame].max_level)) { batt_anim->cur_frame++; } if (batt_anim->cur_frame >= batt_anim->num_frames) { batt_anim->cur_cycle++; batt_anim->cur_frame = 0; } } else { // 拔出了充电器,重置变量 batt_anim->cur_frame = 0; batt_anim->cur_cycle++; } } |
动画显示无非就是判断当前电池的状态,然后选择到对应的frame,再通过healthd_draw显示到屏幕上面。下面我们再简单看看healthd_draw的redraw_screen方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) { clear_screen(); // 首先判断电池状态和动画内容是否有问题 if (batt_anim->cur_level < 0 || batt_anim->num_frames == 0) // 有问题显示battery_fail动画 draw_unknown(surf_unknown); else // 否者显示电池充电动画 draw_battery(batt_anim); // 刷新屏幕 gr_flip(); } void HealthdDraw::draw_battery(const animation* anim) { const animation::frame& frame = anim->frames[anim->cur_frame]; if (anim->num_frames != 0) { // 如函数名,将动画显示到屏幕中间 draw_surface_centered(frame.surface); LOGV("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame, frame.min_level, frame.disp_time); } // 如果有设置显示时钟和电池百分百,则同时描绘 draw_clock(anim); draw_percent(anim); } int HealthdDraw::draw_surface_centered(GRSurface* surface) { int w = gr_get_width(surface); int h = gr_get_height(surface); int x = (screen_width_ - w) / 2 + kSplitOffset; int y = (screen_height_ - h) / 2; LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y); gr_blit(surface, 0, 0, w, h, x, y); if (kSplitScreen) { x += screen_width_ - 2 * kSplitOffset; LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y); gr_blit(surface, 0, 0, w, h, x, y); } return y + h; } |
所以,动画的连续显示也显得比较简单。如果想要做修改,理清显示流程即可。
另外,看上面的流程可以知道,如果需要显示电量百分比,可以提供对应的font字体文件,当然更简单的就是提供一到十和百分号的png图,然后按照上面解析动画的流程一样,将百分比对应的图片也加载进来,最后显示即可。