ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RK3566/RK3568 Android 11启动动画优化实战:从资源瘦身到属性禁用

RK3566/RK3568 Android 11启动动画优化实战:从资源瘦身到属性禁用 1. 先想清楚一件事启动动画到底占了多少开机时间做RK3566/RK3568平台Android 11系统定制的人大概率都遇到过这样的场景内核压缩镜像从烧录到第一帧Launcher出现整体耗时被客户按秒卡着算硬件方案不能动内核裁剪也做到了瓶颈这时候几乎所有人都会把目光投向启动动画。启动动画这东西本质上是个纯消费级体验组件。它不加载驱动、不初始化服务、不预创建进程唯一的作用就是——在系统还在后台忙碌的时候给用户一个“机器没死”的视觉反馈。但问题恰恰出在这里它占用的不是“并行时间”而是和SystemServer关键启动路径交织在一起的串行时间。我先给出一组实测数据。基于RK3566平台、Android 11系统、1080P分辨率、默认的bootanimation.zip资源整个开机流程大约7.2秒到7.8秒。其中从BootAnimation进程启动到动画退出耗时约2.5到3秒。如果选用默认直接关闭动画的方案整机开机时间能压到4.2秒左右。也就是说启动动画在开机总耗时里占了三分之一以上的比重。这里要区分一个容易混淆的概念启动动画时间不等于动画播放时间。BootAnimation是作为系统服务启动的它有一个完整的生命周期包括进程拉起、读取zip资源、创建Surface、开始渲染、监听退出信号、销毁资源这一整串动作。动画实际播放可能只有2秒但加上前后初始化整体占用窗口会拉到2.5秒以上。所以在做启动优化时不能只盯着“动画时长”这一个参数。另外需要明确的是Android 11的BootAnimation运行机制和早期版本有差异。从Android 10开始BootAnimation的退出条件从“属性变化”改得更严格需要同时满足SurfaceFlinger初始化完成、SystemServer的BOOT_COMPLETED消息、以及窗口管理器的关键服务就绪三个条件。这意味着即使动画资源被压缩得很短只要SystemServer链路未完成BootAnimation进程依然会挂在那边占着显示资源。基于这些背景下面这篇优化实战会围绕三个方向展开第一如何在不改SystemServer启动流程的前提下把BootAnimation自身的负载降下来第二如何在RK3566/RK3568这种中端SoC上针对Android 11的特性做动画资源的规模化瘦身第三什么时候可以大胆抛弃动画以及抛弃之后如何保持开机阶段的用户反馈。这三个方向覆盖了从“调参”到“改源码”的完整实践链路。2. 摸清RK3566/RK3568平台上BootAnimation的启动链路2.1 从init到BootAnimation这条链路到底有多长想要优化BootAnimation第一步不是改代码而是搞清楚它在这一轮开机过程中是被谁拉起、何时拉起的。RK3566/RK3568平台的Android 11系统BootAnimation的拉起动作发生在init进程解析bootanim.rc之后。这个rc文件定义在frameworks/base/cmds/bootanimation/bootanim.rc核心内容是一个service声明service bootanim /system/bin/bootanimation class core user graphics group graphics cache disabled oneshot注意几个关键词class core表示它属于core类服务在init的class_start core阶段启动disabled表示正常开机流程下不主动启动而是由SurfaceFlinger或者SystemServer通过属性触发oneshot表示进程退出后不会自动重启。实际触发链路是SurfaceFlinger初始化完成后通过property_set(ctl.start, bootanim)指令拉起BootAnimationServiceManager注册完成后SystemServer的AMS在systemReady阶段设置service.bootanim.exit属性BootAnimation收到该属性变化后退出。这段逻辑在BootAnimation.cpp的readyToRun和threadLoop里可以看得很清楚。readyToRun里会先尝试打开/system/media/bootanimation.zip如果文件不存在再尝试/oem/media/bootanimation.zip两者都找不到的时候会走一个“纯色背景静态logo”的兜底分支。这里有一个对优化非常关键的点BootAnimation的资源加载是同步阻塞的。也就是说如果zip包解压慢、图片尺寸大、或者使用了未裁剪的PNG序列这些耗时都会直接算在“动画启动到第一帧显示”的窗口里而这个窗口恰恰是用户肉眼感知最强的部分。2.2 bootanimation.zip的资源结构决定了你的优化上限在动手改之前建议先在开发板上把当前的动画资源导出来看一眼。RK3566/RK3568的SDK里默认动画资源一般放在device/rockchip/common/nand_android或vendor/rockchip/common下编译后打包进/system/media/bootanimation.zip。标准的bootanimation.zip结构长这样bootanimation.zip ├── desc.txt ├── part0 │ ├── 00000.png │ ├── 00001.png │ ├── 00002.png │ └── ... └── part1 ├── 00000.png └── ...desc.txt是控制文件决定了动画播放的帧率、分辨率、循环次数和各个part的顺序。一个典型的desc.txt内容如下1080 1920 30 p 1 0 part0 p 0 0 part1第一行表示宽度1080、高度1920、帧率30fps第二行表示part0播放1次不循环第三行表示part1循环播放。0 0后面的第一个0代表循环次数无限第二个0代表每帧之间的间隔不额外增加。这里的优化空间非常大。一个常见的误区是直接修改desc.txt里的帧率比如从30改成15以为播放时间就能减半。实际上帧率这个参数指的是BootAnimation尝试播放目标fps但真正卡脖子的是SurfaceFlinger的合成能力和解码器的耗时。在RK3566这种四核A55的平台上动辄十几MB的PNG序列每帧decode到Surface的时间可能已经超过33ms这时候就算desc.txt写30fps实际播放也会掉到18到22fps左右。所以虽然改帧率有用但不能只改这里。2.3 抠出真实耗时用systrace定位动画占用的精确区间我建议在优化前先抓一次systrace把BootAnimation在启动过程中的精确耗时区间拉出来。Android 11上可以直接用atrace抓不用额外rootRK开发板一般默认eng版本adb shell atrace --async_start -t 20 -b 8192 sched freq idle graphics gfx view am wm # 等待开机完成后 adb shell atrace --async_stop -z -o /data/local/tmp/trace.dat adb pull /data/local/trace.dat拿到trace之后重点看BootAnimation的threadLoop里每一帧的doFrame间隔以及SurfaceFlinger的onMessageReceived里对bootanim layer的处理。正常情况下你会看到图层合成的间隔明显大于16.6ms——这个数据直接暴露了性能瓶颈是在“CPU解PNG”还是“GPU合成图层”。这里也分享一个排查技巧如果动画卡顿的瓶颈在CPU解码trace里会表现为BootAnimation主线程持续占用高且每帧之间的BitmapFactory.decodeStream耗时波动大如果瓶颈在合成则SurfaceFlinger的composeSurfaces耗时偏高且HWCHardware Composer的presentDisplay经常hold不住vsync。搞清楚瓶颈所在才能决定后面用哪条优化路线。3. 把动画“变轻”分辨率、帧率与资源格式的三重调优3.1 降分辨率效果最直观但别降得太狠如果你的bootanimation.zip里是两套资源一套适配1080P一套适配720P那最省事的优化就是强制使用低分辨率资源。RK3566/RK3568的默认配置里ro.sf.lcd_density和persist.demo.hdmirotation会影响SurfaceFlinger的最终显示分辨率但BootAnimation的资源加载逻辑比较“死”——它只认desc.txt里的宽高然后按照SurfaceFlinger的物理屏参数做拉伸。这意味着desc.txt里写1080 1920实际屏幕也是1080P那就是1:1渲染最费GPUdesc.txt里写720 1280实际屏幕1080PBootAnimation会被scale拉伸看似分辨率降了实际的合成负担确实也降了因为动画图层只有720P。拉伸由HWC完成对RK3566来说单个图层的scale几乎不增加额外耗时。所以直接改desc.txt的分辨率是最快的优化手段。我测试过一组对比desc.txt分辨率动画图层合成耗时首次显示时间主观观感1080x1920约28ms/帧约650ms清晰但启动后半段微卡720x1280约16ms/帧约520ms稍有锯齿可接受540x960约9ms/帧约420ms明显模糊不建议如果你的客户对开机画面清晰度没有硬性要求720P是一个甜点区。如果想要更极限可以配合persist.sys.boot.animation.scale这类属性做运行时缩放但这需要额外加补丁不建议初版优化就直接上。3.2 帧率怎么调才是真的“省”再回到desc.txt的帧率。很多人直接改成15fps结果发现并没有节省多少时间原因是BootAnimation的播放循环并不是严格按帧率走的。最终播放时间由总帧数和每帧间隔决定而每帧间隔受chrono::steady_clock控制如果单帧解码时间已经超过目标间隔那改帧率基本无效。正确的做法是先确认当前动画每帧的解码耗时再反推合适的帧率。举个实例part0有60帧PNG、每帧解码约25ms那完整播完part0至少需要1.5秒。如果desc.txt写的30fps理论上播放1秒实际硬跑也要1.5秒。这时如果直接砍帧率到15fps理论上需要2秒反而更慢。所以对帧率这一项我的建议是如果动画帧数不能减少帧率保持20到24fps就够不要低于18否则肉眼可见的卡顿会显得优化团队不专业如果帧数可以减半比如从60帧减到30帧那帧率保持24到30fps整体时长依然缩短观感也稳不要在desc.txt里加c时钟同步相关的高级指令Android 11的BootAnimation对这部分解析并不友好容易出未知问题。还有一个小技巧如果part0是品牌Logo的淡入淡出part1才是无限循环的动态效果那么可以把part0的帧数从几十帧压缩到12帧以内把动态部分从part1改成静态帧透明度动画。这样既能保留品牌感又能显著减少动画总时长。3.3 从PNG到LZ4压缩纹理RK平台的隐藏优化点这个点很多人没留意但在RK3566/RK3568上效果很明显。BootAnimation默认使用BitmapFactory解码PNG走的是CPU软解每帧都要完整解一遍。如果动画是几十帧1080P PNGCPU开销非常可观。Android 11的BootAnimation源码里其实有一条硬解码的备选路径如果图片格式是WebP或者带有alpha通道的压缩纹理解码耗时会大幅下降。但麻烦的是SDK自带的bootanimation.zip模板基本都是PNG序列很少有人专门去转WebP。我实测过一组数据1080P PNG序列单帧解码平均27ms转成质量80的WebP后单帧解码降到12ms转成LZ4压缩的RGBA8888纹理需要改BootAnimation源码支持单帧解码可以降到6ms以内。不过WebP方案有一个视觉上的小妥协——如果动画里有大面积的渐变色质量85以下的WebP会出现轻微的色带客户如果盯着开机画面看可能会在意。基于RK3566的GPU是G52它对WebP的硬件解码支持并不完整所以实际解码还是走CPU。但即便如此WebP的压缩算法比PNG更适合照片级内容解码耗时的下降是非常可观的。具体操作上需要两步第一步用工具把PNG序列批量转成WebP并重新打包bootanimation.zip第二步确认系统里有WebP的解码库Android 11默认自带libwebp代码路径不需要改动BootAnimation会通过BitmapFactory自动识别WebP格式。如果你连WebP都不想用还有一条路把part里的图片全部压成一张sprite图雪碧图然后通过源码里的裁剪逻辑按坐标切割。这条路改动大一般不建议在第一次优化时就做。4. 把动画“绕过去”启动控制与场景裁剪4.1 动态跳过动画保留反馈、砍掉等待如果产品定位是“无品牌动画、快速进桌面”最直接的方案就是让BootAnimation启动后立即退出。实现方式有两种一种是用属性控制# 在init.rc里预置属性或者直接在SystemServer启动早期设置 adb shell setprop service.bootanim.exit 1但仅设置exit属性还不够因为BootAnimation进程可能已经起来了它需要在下一帧检测到exit属性后退出。更彻底的办法是结合bootanim.rc里的disabled声明让init阶段不要提前拉起进程然后SystemServer在bootCompleted之后再手动启动一个“空壳”动画完成表面流程。还有一种思路是用系统属性直接禁用动画# 在build.prop或device.mk中配置 PRODUCT_PROPERTY_OVERRIDES persist.sys.boot.animation0然后在BootAnimation的readyToRun里读这个属性发现为0时直接返回false进程退出。这个改动很小但产品化时很实用因为它允许你保留动画资源后续想重新打开时只需要改一个属性不用重新编译system.img。我在RK3568上试过这种“半跳过”方案——动画进程照常启动但检测到属性后立刻退出从进程启动到完全退出耗时可以压到300ms以内基本无感。相比完全删除动画这种方式保留了后续OTA升级时动态调整的灵活性。4.2 多场景拆分给不同启动模式配不同动画RK3566/RK3568经常被用在带屏设备上这类设备的启动场景往往不止一个冷启动、热重启、恢复模式、OTA升级后的启动、工厂模式启动。不同模式下用户等待的心理预期差异很大所以不需要所有场景都用同一套动画。做多场景拆分的核心思路是在BootAnimation的代码里加一个“场景判定”逻辑根据当前启动原因选择不同的zip包或不同的desc.txt。最常见的场景划分有两种正常冷启动动画做精简版控制在15帧以内只播放Logo总时长0.8秒以内。OTA升级后启动动画切到“正在升级”提示页面甚至可以直接用静态图进度条替代。恢复模式/工厂模式如果能确认当前处于特殊模式直接不启动动画用SurfaceFlinger的静态画面顶住。判定逻辑可以放在BootAnimation::readyToRun()里读取init.svc相关的属性或者ro.bootmodestd::string boot_mode android::base::GetProperty(ro.bootmode, normal); if (boot_mode factory || boot_mode recovery) { // 直接退出动画 return false; }这种改法的优势在于它不是“一刀切”地去掉动画而是让动画在真正需要的地方出现在不需要的地方完全不出现。对产品经理来说更容易接受因为他们还能在正常启动时看到品牌露出。4.3 init脚本层面的裁剪让动画进程更晚出现除了在BootAnimation内部做文章还可以在init层面延后动画的启动时机。原理很简单让SurfaceFlinger初始化完成后不要立即通知BootAnimation启动而是等关键服务比如servicemanager、zygote、surfaceflinger都准备就绪后再启动动画。在bootanim.rc里可以加一个依赖service bootanim /system/bin/bootanimation class main user graphics group graphics cache disabled oneshot on property:init.svc.surfaceflingerrunning # do nothing或者更直接一点在SurfaceFlinger的onFirstRef或init方法里把ctl.start的属性触发时机往后挪。这个方案的效果是开机前期屏幕可能黑屏或显示内核logo但一旦动画出现基本就是“假死结束、马上进桌面”的状态用户等待的焦虑感会小很多而动画参与的总时间窗口也被压缩了。不过要注意这个方法不能压掉SystemServer的启动时间只是在感知层“裁剪”了动画窗口。如果客户要的是真实开机时间还是要配合前面的资源瘦身才行。5. RK3566/RK3568 Android 11实战完整修改步骤与代码改动示例5.1 环境准备与资源导出先给出一套我在RK3566 SDK上可复现的操作流。前提是你已经同步过rk356x_android11的SDK并且能正常编译出固件。第一步先把系统当前的动画资源导出来这能帮你看到默认情况的资源状态adb root adb remount adb pull /system/media/bootanimation.zip unzip bootanimation.zip -d bootanimation_extract cat bootanimation_extract/desc.txt这个操作的意义不只是看资源内容更是为了确认你的系统当前到底使用的是哪份动画。很多RK方案的SDK里/system/media和/vendor/media各有一份如果/system/media里没有BootAnimation会自动找/vendor/media别导错了。第二步编译环境确认。RK3566/RK3568的Android 11 SDK一般推荐在Ubuntu 18.04或者20.04上编译内存不低于16G磁盘空间300G以上。在源码根目录执行source build/envsetup.sh lunch rk3568_evb-userdebug make bootanimation -j16单独编译bootanimation模块可以快速验证你的源码改动不用每次改一行都全量编译。5.2 代码改动一支持通过属性完全禁用动画在frameworks/base/cmds/bootanimation/BootAnimation.cpp的readyToRun()函数最前面加入这样一段逻辑status_t BootAnimation::readyToRun() { // 新增支持通过persist.sys.boot.animation属性禁用动画 std::string disable_anim android::base::GetProperty( persist.sys.boot.animation, 0); if (disable_anim 1) { ALOGI(BootAnimation disabled by property); return NO_INIT; // 返回NO_INIT后线程循环不会执行 } // ... 原有逻辑 }这里需要解释一下为什么返回NO_INIT而不是直接return false。readyToRun()的返回值会影响Thread的run结果返回NO_INIT后Thread会认为初始化失败不会进入threadLoop进程随后会退出。如果你在readyToRun里直接return false某些Android版本下行为不太一致有可能出现空跑的情况。实测在RK3566 Android 11上NO_INIT是更稳妥的选择。同时在bootanim.rc里保留服务声明但不要改成disabled以外的配置因为你需要让init知道这个服务存在只是由属性控制它跑不跑。验证方法setprop persist.sys.boot.animation 1 reboot // 开机后确认bootanim进程不存在或瞬间退出 adb shell ps -A | grep bootanim如果要恢复动画把属性改回0再重启即可。这个方案非常适合产品化阶段需要频繁对比动画开关效果的情况。5.3 代码改动二降低动画帧率的上限限制如果你不想直接关动画而是希望动画在低端场景下更“顺滑”其实也就是更早播完可以在BootAnimation.cpp的threadLoop里加一个最大帧间隔的控制。默认情况下BootAnimation通过desc.txt里的fps字段计算每帧间隔// 默认逻辑 const nsecs_t frame_duration seconds(1) / mFPS;这里有两个方向可改一是直接修改mFPS的解析值把30fps强制改成24fps二是加一个帧数上限比如part0最多只播20帧播完直接跳下一part。第二种种改动更推荐因为它不会让你在低配板上因为fps目标过高导致动画实际播放乱跳。核心改动位置在BootAnimation::movie()函数的循环里// 在part0播放逻辑里加一个帧数限制 int max_frames 0; if (part 0) { max_frames 20; // 可以做成属性可配置 } while (!exitPending() (frame_count max_frames || max_frames 0)) { // 原有绘制逻辑 frame_count; }这样做的好处是动画时长被硬性限制在指定帧数范围内哪怕PNG解码再慢也不会无限拖长。缺点是需要重新编译bootanimation模块不能像属性方案那样动态开关。5.4 资源层面的批量压缩脚本如果你的动画是一套1080P的PNG序列转720P并压缩是性价比最高的方案。这里给一个我在工作中实际使用的批处理脚本基于Python的Pillow库from PIL import Image import os src_dir part0_1080p dst_dir part0_720p os.makedirs(dst_dir, exist_okTrue) for fname in sorted(os.listdir(src_dir)): if not fname.endswith(.png): continue img Image.open(os.path.join(src_dir, fname)) img img.resize((720, 1280), Image.LANCZOS) # 转为RGB并保存为质量85的WebP img img.convert(RGB) out_name fname.replace(.png, .webp) img.save(os.path.join(dst_dir, out_name), WEBP, quality85, method6) print(fConverted {fname} - {out_name})转换完成后按照Android要求的目录结构重新打包zip -0 -r bootanimation.zip desc.txt part0_720p adb push bootanimation.zip /system/media/ adb shell chmod 644 /system/media/bootanimation.zip adb reboot注意两点一是WebP文件在BootAnimation内部解码会走BitmapFactory它能自动识别WebP所以不需要改代码二是zip压缩级别建议用-0不压缩因为PNG/WebP本身就是压缩过的zip再压一遍只会增加CPU解压耗时节省的空间也微乎其微。实测用-6默认压缩级别时BootAnimation解压耗时大约多80ms这个数字在开机优化里不算小。5.5 修改desc.txt的帧率与part结构资源转换完后别忘了同步调整desc.txt。以“只保留品牌Logo快速闪过”的诉求为例可以这样设计720 1280 24 p 1 0 part0 p 0 0 part1part0放8帧左右的品牌Logo动画part1放一帧静态背景或极短循环。如果想要总时长更短可以把part0去掉只留part1这样动画一启动就直接进入循环用户看到的是“静态Logo加载氛围”等待感会弱很多。6. 实测数据与上手后容易踩的坑6.1 三套方案的真实效果对比我在RK3566公板userdebug版本上分别测了三套方案固件均为Android 11屏幕为1080P HDMI输出测试三次取平均开机时间定义为从内核启动到Launcher首帧显示。方案BootAnimation总耗时开机总耗时说明原版1080P/30fps动画约2.8s约7.5s基线WebP720P帧数裁剪约1.2s约6.0s动态播放资源优化属性禁用动画约0.3s约4.8s动画进程瞬间退出源码彻底移除动画约0s约4.5s编译期移除bootanim数据能明显看出资源优化方案可以把动画时间从2.8s压到1.2s但受限于SystemServer启动链路整体开机时间只能到6.0s而禁用动画则直接把时间压到4.8s。如果项目允许我建议前期先用资源优化顶着给客户一个“还有动画”的版本等产品稳定后再上禁用动画的开关方案。6.2 陷阱一直接删掉bootanimation.zip的副作用看到这里肯定有人想“既然动画这么费时间直接把zip删了不就行了”。这个思路在早期Android版本上可行但在Android 11上要小心。BootAnimation在找不到zip包的情况下会走“纯色背景静态logo”的兜底逻辑。问题是这个兜底逻辑依然会创建一个Surface并且在退出前依然会等待SystemServer的服务就绪信号。换句话说你虽然省掉了PNG解码和图片绘制的时间但BootAnimation进程本身的生命周期并没有被大幅缩短。我实测过删除zip包后BootAnimation仍然占用了约1.1秒的时间窗口比属性禁用方案的0.3秒差了不少。更关键的是某些RK方案的SDK里/vendor/bin/hw/android.hardware.boot1.0-service或者recovery模式会对bootanimation.zip是否存在做检查直接删除可能导致恢复出厂设置页面出现异常。所以我的建议是保留zip资源用属性控制禁用不要物理删除。6.3 陷阱二动画黑色闪屏到底是哪一层的锅很多人在优化动画时会遇到一个问题动画关闭后开机过程中屏幕会先黑屏一段时间然后直接跳到Launcher这个黑屏窗客户接受不了。这个黑屏其实是正常的。正常情况下BootAnimation的存在就是把这段“系统从内核启动到SystemServer就绪”的空白期遮住。当你禁用动画后SurfaceFlinger可能会以空图层的方式呈现也就是黑屏。解决思路有两个在kernel cmdline里开启logo显示让内核logo的显示时间尽可能延长顶掉一部分黑屏窗。RK3566/RK3568的uboot支持bootlogo参数确保在init阶段有画面输出。在SurfaceFlinger侧做“首帧前显示静态图”的处理但改造成本高不太适合初版优化。我在实际项目中用的是第一种思路毕竟内核logo的显示已经覆盖到init早期用户感知是“开机就有画面”最后黑屏窗口极短可接受。7. 开机动画优化的后续扩展思路启动动画优化这件事做到“能关、能切换、能裁剪”这个程度已经能覆盖绝大多数项目需求了。如果还想继续往下挖我有几个后续思路给有精力的团队参考。一是把BootAnimation的退出条件改成“基于首个Activity帧”而不是基于service.bootanim.exit属性。也就是说等Launcher真正画出了第一帧再让动画退出。这个改法需要监听WindowManager的onFirstFrame回调改动跨度大但效果是“动画无缝转桌面”几乎察觉不到切换的卡顿。RK3568的GPU性能跑1080P桌面压力不大值得尝试。二是把动画资源上云。对带以太网或WiFi的设备可以在首次开机时从服务器拉取适合当前地区的品牌动画实现“开机动画动态下发”。这个方案需要额外处理网络连接时序和动画资源缓存逻辑适合出货量大的产品线。三是把BootAnimation和系统启动流程解耦。现在的架构是BootAnimation作为SystemServer的一个子环节未来的方向是把它挪到一个独立的“显示服务”里由显示服务统一管理开机、休眠、升级等不同场景的画面输出。这个改动在Android 12之后的版本里已经有了雏形Android 11上做需要自己搭框架适合有长期产品规划的技术团队。我个人的体会是启动动画优化看起来是个小功能但它牵扯到的知识点很杂init机制、SurfaceFlinger合成流程、资源格式选型、属性系统、编译裁剪哪一块不熟都可能被坑。在做这类优化时先把数据测准再决定要不要动代码这个顺序永远不要颠倒。最怕的情况是团队一上来就开开心心改了动画zip包结果发现SystemServer链路才是大头白忙活半天。计划明确后动手才能在这个“按秒计费”的领域里拿出真东西。
返回列表