
修改Recovery中的文字提示二上一篇我们把Recovery界面上那些死文字逐个翻了个底朝天讲了怎么在资源文件里找到文案、替换成自己想要的说法也提了一句改完刷不进去多半是签名问题。但说实话那一篇只能解决最表面的需求——把现有文字换成另一行文字。真正到了生产环境你会发现更麻烦的问题一箩筐为什么中文替换上去变花屏为什么想显示刷入成功 耗时12秒这种动态内容不知道怎么下手为什么在A设备上显示正常的文案放到B设备上就跑到屏幕外面去了这篇文章就把这些更底层的东西一次性说清楚。1. 先补一个前提Recovery里的字到底是谁画的在动手改任何文字前得先明白一件事Recovery界面里的文字并不像普通App那样走系统的TextView渲染。Recovery环境里没有完整的图形系统屏幕上每一个像素基本都是Recovery自己的UI引擎直接画上去的。这个引擎在官方Recovery源码里叫minui它负责屏幕初始化、画点划线、刷新画面以及——把字体点阵摆到屏幕上。minui渲染文字的方式非常原始它会从一份预先编译好的字体数据里根据字符编码找到对应的点阵位图然后逐位判断哪些像素需要点亮最后画到framebuffer上。也就是说Recovery环境下的字不是矢量字体是点阵字体。你看到的每一个字本质上都是一组0和1组成的小格子图。这就引出了一连串问题。首先官方Recovery内置的字体数据通常只覆盖了基础拉丁字符和少量符号字库数量很少。你如果直接往字符串资源里塞中文minui在字体表中找不到对应字符常见的表现是显示成空白、方框、问号或者干脆乱码花屏。这跟文字没替换成功完全是两码事是两个层面的故障。另一个要注意的点是Recovery里的文案来源其实不止一处。上一篇我们主要讲了res目录下的字符串资源但实际操作中你会发现除了资源文件还有相当一部分文字是硬编码在UI源码里的——比如某些状态提示、确认对话框的标题、进度条旁边的说明文字。改递归扫描一下源码里所有字符串字面量往往比改资源文件更能覆盖全面。我见过不少朋友改了半天资源文件界面上还是老样子最后发现要改的那句话写死在C代码里这类问题排查起来很费时间。所以拿到一个修改文字的需求我的第一反应一定是先归类这句话是资源文件控制的还是源代码写死的还是终端输出流打到屏幕上的三条路走不同的改法。这也是上一篇和这一篇的衔接点——上一篇解决资源文件这条线这一篇我们重点攻字体渲染和动态显示这条线。2. 中文字体与自定义字库从改字到换字库2.1 为什么官方字库打不了中文Recovery的minui在编译时会生成一组静态字体数据本质上就是一个巨大的C数组数组里按字符编码依次存放每个字的点阵信息。官方默认的字体覆盖范围非常保守连中文的边都不沾。你就算把资源文件里的字符串全换成中文字体表里没有这些字符渲染层画出来的只能是空白或乱码。要支持中文核心工作是替换整个字体数据。这一步绕不开也不建议绕。常见做法是引入一套完整的点阵字库。中文点阵字库最经典的是16x16规格也就是每个汉字用16行、每行16个像素点来表示共32字节。早年DOS时代的HZK16文件就是这个格式。Recovery场景下用16x16字库有个现实好处内存占用小整个常用汉字字库也就几百KB级别对Recovery这种精简环境比较友好。如果你对显示效果有更高要求可以用24x24甚至32x32的字库笔画细节会好很多但内存和绘制开销会明显上升部分低端设备的Recovery可能扛不住。我实际测试下来绝大多数设备的Recovery用16x16字库渲染中文清晰度完全够用毕竟Recovery界面本来信息密度就低字体稍微大一点反而更好读。2.2 从TTF字体到Recovery可用的点阵数据拿到一个TTF字体文件后不能直接扔给Recovery用必须转成点阵数组。转换流程大致分三步栅格化、取模、生成C头文件。栅格化就是把矢量字体的轮廓在指定尺寸下渲染成黑白位图。取模是把位图按行按字节打包成点阵数据。生成C头文件则是把打包结果输出成Recovery源码能引用的静态数组。实际操作中我习惯用脚本批量处理把常用汉字从GB2312或者GBK码表里逐个取出并转换。这里的关键是码表覆盖要够用不能只转几个汉字就收工不然界面上某个冷僻字又变空白。一般建议至少覆盖3500个常用汉字稳妥起见直接覆盖GB2312全部6763个汉字。转换时有一个参数要特别注意抗锯齿。系统桌面上看惯了灰阶抗锯齿字体但Recovery的点阵字库是纯黑白的不能带灰度。带了灰度意味着每个像素不再是非黑即白需要额外的灰度层级渲染支持大部分Recovery的minui根本不支持。所以转换时务必关闭抗锯齿字体边缘会有一些锯齿感这是点阵字库的正常特性不用纠结。2.3 把新字库挂进渲染链路生成好的C头文件替换掉源码中原来的字体数据文件然后在构建配置里把它编进去。这里有个细节minui的字体结构通常包含字体宽度、高度和字符数据表替换时这三者必须保持一致尤其是字段顺序。我见过有人把数据替换对了但忘了更新字体尺寸的宏定义结果渲染出来的每个字都错位错得离谱像被切了一半。替换字体后还需要测试几个点西文字符和中文字符混排时的对齐是否正常、英文数字的显示是否还清晰、标点符号是否落在了预期位置。因为中文是全角字和半角英文混排时行宽计算容易出问题表现为句子中间突然多出半个字位的空隙。这个现象不影响阅读但追求完美的话可以在渲染层针对全角/半角切换做一下额外偏移处理。如果你用的是第三方触摸Recovery字体替换路径会略有不同但底层逻辑是类似的找到它内置的字体文件或字体数组把中文点阵合并进去或者整体替换。第三方Recovery往往已经把中文字体问题解决了所以如果你是在第三方Recovery基础上改文案字体这步可以直接跳过但如果你维护的是官方Recovery这一关必须过。3. 把静态提示改成动态状态文本3.1 动态文字到底能干什么静态替换玩熟了之后很多人自然会想能不能让Recovery界面上显示一些实时变化的内容最常见的需求有三类显示当前操作的耗时、显示正在安装的包名或脚本名、显示剩余电量或日志的最后几行。这些需求本身不难但难点在于Recovery的UI结构和普通程序不一样。Recovery没有后台线程池没有消息队列一切操作都在一个相对封闭的流程里跑。要显示动态文字你得搞清楚它的UI刷新时机在正确的节点插入自己的更新逻辑。3.2 UI线程与操作回调在哪里塞逻辑官方Recovery的UI核心有一个事件循环负责轮询按键、刷新画面、执行菜单回调。你看到的每一个界面状态切换本质上都是这个循环驱动的。想显示动态信息思路通常是在自己的操作流程中每一步进度更新时调用UI的更新接口把新的文本内容传进去让UI引擎在下一帧刷新时画出来。比如刷入ZIP包时想显示正在写入分区...后面跟一个倒计时或文件名就需要在安装脚本执行的循环里获取当前安装项的信息拼接成字符串然后调用UI的刷新函数。安装过程本身是阻塞的所以要注意刷新调用必须放在主流程中而且更新后要主动触发一次画面重绘不然文字会一直停在第一次画的旧状态。我踩过一个典型的坑更新文字之后没有触发重绘导致屏幕上显示的还是旧内容排查了很久才发现问题是刷新函数没被调用而不是字符串拼接错了。在Recovery这种环境里画了不等于刷了刷了不等于你看见了这两层关系一定要理清。3.3 进度条背后的文字联动Recovery界面上常见的进度条旁边通常有一条说明文字。默认情况下这条文字是固定的比如正在安装更新…。如果想让这条文字跟着进度变化比如每写一个分区就显示分区名就需要把进度更新和文本更新绑定在同一个回调链上。我自己常用的做法是在进度更新的回调里把当前分区名格式化成一行文本同时调用两个接口——一个更新进度条位置一个更新文字内容。两个操作放在同一处可以保证视觉上进度条和文字的刷新是同步的。如果分开放经常会出现进度条已经走完文字还停在上一行的尴尬情况。这里还要注意内存复用。Recovery环境内存紧张频繁创建新字符串是大忌。建议提前分配好一块固定大小的字符缓冲区每次更新时往里面填充内容而不是动态拼接。多数Recovery内核里malloc频繁调用没问题但在极端情况下会碰到内存碎片稳妥起见用静态缓冲区最放心。3.4 花屏与闪烁的真相动态刷新文字时最怕两个现象花屏和闪烁。花屏的根子通常是绘制区域没有完全清空新文字画上去时旧文字的残影还在一层叠一层就成了花屏。解决办法很简单每次更新文字前先把目标区域的矩形填充成背景色再画新内容。闪烁则和刷新频率有关。Recovery的framebuffer刷新是直接整屏刷新的如果你一秒钟更新几十次文字人眼看到的就会是明显的闪动。解决思路是降低刷新频率或者只在内容真正变化时才触发重绘。比如给文本设一个简单的比较逻辑内容跟上一次相同就跳过重绘。我见过有人在Recovery里放了一个实时时钟结果整个界面闪得没法看后来改成每分钟刷新一次才算正常。4. 多语言提示与语言切换一套Recovery多套文案4.1 官方资源目录与硬编码字符串的分裂Recovery确实支持多语言资源目录比如res-zh-rCN、res-pt-rBR这种带地区码的目录。但这些资源目录只覆盖了一部分文案前端UI里仍有大量硬编码字符串不会跟着语言资源走。这就导致一个很搞笑的情况菜单和按钮变成中文了某些提示框里还是英文。想真正实现多语言必须两条线一起改。资源目录这边做常规翻译硬编码字符串那边就得在源码里按语言条件判断返回不同文本。工作量比想象中大但这是躲不掉的。我的建议是优先覆盖用户看得到的高频文案——主菜单、安装确认、擦除确认、重启选项这几个地方低频的调试日志可以放一放。4.2 按需切换语言的实现方式Recovery环境里没有运行中的Android系统帮你决定语言它只能依赖少数几个信息来源。一个常见机制是读取一个配置文件这个文件可能是刷机工具写入的也可能是Recovery启动时从缓存分区读取的。文件内容通常就是一个语言标记比如zh_CN或en_US。Recovery启动早期会尝试读取这个标记用它初始化全局语言选项之后所有界面文本都据此选择。如果读取不到标记就会回退到编译时指定的默认语言。这解释了为什么很多定制Recovery默认就是中文不是因为它自动识别了用户系统语言而是它在编译时直接把默认语言设成了中文。想测试多语言效果不需要重新编译整个Recovery。我把语言标志文件放到相应的位置手动改内容然后重启进Recovery就能快速看到不同语言下的界面效果。这个调试方法非常省时间强烈推荐。4.3 语言和字体必须配套多语言切换里最容易翻车的是字体配套。Recovery只有一套点阵字库切换语言只换了文字串字库没换某个语言特有的字符就会变成空白方块。最常见的就是切到中文时发现大部分汉字变豆腐块原因是字库还是官方那套只有西文字符的默认字库。所以做多语言支持的完整流程是字库先行翻译跟上。先确认目标语言的全部字符都在字库里覆盖了再做文案翻译。顺序反过来的话你会在调试时看到一堆方块字还以为是翻译文件格式错了定位半天才明白是字库问题。这个教训我印象太深了。5. 屏幕适配与文本溢出改完文案后最常见的显示事故5.1 坐标基准与UI布局的计算Recovery的UI布局在很多实现里是用写死的坐标或者简单的比例计算完成的。屏幕宽高不同同一个提示框的位置可能完全跑偏。修改文案时尤其要注意新文案比原文长或短可能导致文本绘制区域超出预定的框体范围甚至覆盖旁边的按钮。更隐蔽的问题是Recovery界面里有些元素是屏幕坐标硬编码的不随分辨率缩放。你在开发机上用的测试分辨率可能和设备实际分辨率不一致结果测试时一切正常一到目标设备上文字就跑到屏幕外去了。我的习惯是拿到设备的分辨率后先画一张简单的网格图把Recovery里各UI元素的坐标范围标出来再对照自己的文案长度做调整。这样能一眼看出哪里会溢出。5.2 超长文案的换行与滚动策略Recovery的文字绘制接口大多数不支持自动换行字符串长了它就往右画画到屏幕外也不报错非常坑。所以长文案必须自己处理换行。一般做法是把长文本按空格或标点切分测量每一段的像素宽度如果加上已有内容超出屏幕宽度就插入换行符。这里有个测量宽度的麻烦事点阵字体下每个全角字符宽度是固定的但半角字符和空格宽度不同。如果你不仔细算混排文本很容易出现某行太满、某行太空的情况。有一个讨巧的办法全角字符统一按一个字宽算半角字符按半个字宽算四舍五入取整。这样误差很小视觉上基本看不出问题。滚动显示是另一个方案适合那种不换行但确实太长的文本。实现方式也很原始在固定的显示窗口内每隔一段时间把文字起点往上平移几像素实现视觉上的滚动效果。注意滚动速度要合适太快看不清太慢等得着急。而且Recovery里很少用滚动因为要处理定时器和重绘的配合代码复杂度会上升不少。5.3 不同分辨率下的等比缩放误区有人想偷懒根据目标分辨率对基准分辨率把所有坐标做等比缩放不就行了。实际测试发现这招经常失灵。原因很简单Recovery界面元素不是纯图形它里面有大量文字文字的点阵字号是固定的不能跟着分辨率等比缩放。你按分辨率把布局放大了但字体没变大结果文字在整体比例里显得变小了布局也比例失衡。更稳妥的做法是布局坐标按实际像素设计文字字号独立选择不要试图做全局等比缩放。不同分辨率之间宁可让某个元素的位置在不同设备上有细微差异也不要强行构建一个虚假的完美等比体系。Recovery的界面使用场景本来就简单用户只需要看得清楚、点得准不追求像素级一致。6. 最后一道坎修改后的Recovery为什么刷不进去6.1 分区镜像的完整性校验改完字体、改完文案、编译打包高高兴兴去刷机结果刷机工具报错或者设备直接拒刷。多数情况下是镜像文件的完整性校验没过。Recovery镜像通常包含表头信息、内核、ramdisk等部分。很多设备的引导加载程序在刷入Recovery时会校验镜像的头部结构和签名信息。你只是改了ramdisk里一个文件表头的部分校验值可能就得跟着更新如果忽略了引导程序直接判定镜像损坏拒绝写入。处理办法说穿了也不复杂用官方提供的解包工具先解包原厂Recovery镜像修改完成后再用配套的打包工具重新打包工具会自动更新表头信息。这里最忌讳的是手动拼接镜像稍微一个字节算错就废了。6.2 签名校验与自签名的配合另一道常见的坎是签名校验。很多设备要求Recovery镜像由特定密钥签名否则不认。如果你没有官方签名密钥就得把设备引导程序的签名校验关掉或者用自签名密钥重新签名并让引导程序信任你的密钥。具体操作要看设备情况但思路是一致的你可以用源码里自带的签名工具给修改后的镜像签名。如果引导程序的校验比较严格还要一并处理vbmeta或者其他防回滚机制这个就要按特定设备来具体对待了。不同设备的处理方式天差地别没法给一个通用命令建议以设备的官方文档为准。6.3 我修改Recovery文字后的最终检查单每次改完我都会按这个清单走一遍能省下大量返工时间检查改动是否涉及需要同步更新的校验信息表头哈希、签名哈希、防回滚计数检查ramdisk里的文件权限和SELinux上下文是否被意外改动检查新增的字体数据数组是否占用了过多空间导致镜像超出分区大小限制在多个分辨率设备上各测一遍文本布局确认没有溢出保留改动前后的镜像哈希值方便随时对比排查问题其中文件权限这条极易被忽视。解包ramdisk后如果某个目录的属主或权限位发生了变化即使文字改对了设备也可能因为权限问题进入异常状态。我习惯解包后先记录所有关键文件的权限值改完再核对一遍基本不会出错。7. 写在最后的实操心得Recovery里的文字修改表面上是个翻译活儿实际上考验的是对底层渲染机制、资源加载、分区打包校验这一整条链路的理解。我见过有人只用十六进制编辑器直接改镜像里的字符串改显示文本是能凑效但一旦涉及换字体、动态信息就完全没法做。给你一个最直接的建议进入Recovery开发前先花半天时间把minui的渲染流程读一遍把字体数据的结构彻底搞明白。这半天花的绝对值后面你能少踩百分之八十的坑。另外做任何修改前先给原厂Recovery镜像做一个哈希备份。万一改坏了随时能回到初始状态。这个习惯我从第一次刷砖之后就再也没丢过。最后再提醒一句不同设备之间的差异远比想象中大我在某台设备上验证没问题的方案换到隔壁设备就翻了车。所以每台设备都要独立走一遍完整的验证流程别指望一份编译产物通吃所有机器。