Android App 里实现开机动画替换

Android App里实现开机动画替换

1. 传统方式:adb remount + adb push

先说电脑连接开发板时最常见的做法。假设开机动画文件名是 bootanimation.zip,目标路径是 /system/media/bootanimation.zip

bash 复制代码
adb root
adb remount
adb push bootanimation.zip /system/media/bootanimation.zip
adb shell chmod 644 /system/media/bootanimation.zip
adb shell chown root:root /system/media/bootanimation.zip
adb reboot

这几步分别完成:

text 复制代码
adb root       → 让 adbd 尝试以 root 身份运行
adb remount    → 把系统分区重新挂载为可写
adb push       → 把新的 bootanimation.zip 推到设备
chmod/chown    → 修正文件权限和属主
adb reboot     → 重启后观察开机动画

这套方式要求开发板允许 adb root/adb remount。普通量产版 user build 往往不允许直接修改 /system;开发板通常需要是 eng、userdebug、已关闭验证的内部版本,或者已经配置了相应的 root/写入能力。

另外,开机动画路径不是所有 ROM 都一样。示例使用 /system/media/bootanimation.zip,实际项目要先确认设备的启动动画加载路径,也可能位于 /product/media 或其他分区。

2. 为什么 App 里不能直接执行 adb remount

adb remount 不是开发板上的 Shell 内置命令。它是电脑端 adb Host 程序的子命令:

text 复制代码
电脑上的 adb 客户端
        ↓
通过 ADB 传输层向 adbd 打开 "remount:" 服务
        ↓
开发板上的 adbd 处理 remount 请求
        ↓
执行系统 remount 逻辑

所以在 App 里执行下面这种代码没有意义:

kotlin 复制代码
Runtime.getRuntime().exec("adb remount")

App 进程里通常没有 adb 可执行文件;即使把可执行文件打包进来,adb remount 也需要先连接一个 ADB transport,不能把它当作普通 Shell 命令直接在目标设备上执行。

3. 通过 ADB 源码找到 remount:

继续看 ADB 的服务定义,可以看到 remount: 是 adbd 提供的控制服务:

text 复制代码
adb remount
        ↓
OPEN "remount:"
        ↓
adbd 接收服务请求
        ↓
执行 remount

这也是整个实现的关键:

kotlin 复制代码
connection.open("remount:")

它不是 shell:remount,也不是 shell:mount -o remount,rw /systemremount:shell: 属于 ADB 协议中的不同服务入口。

4. App 内的前置条件

这里使用:

kotlin 复制代码
val socket = Socket("127.0.0.1", 5555)

127.0.0.1 表示 App 和 adbd 在同一块开发板上。开发板必须已经让 adbd 监听 TCP 5555,例如开发板端具备 root 能力时可以执行:

kotlin 复制代码
Runtime.getRuntime().exec(new String[]{"su", "-c", "setprop service.adb.tcp.port 5555; stop adbd; start adbd"})

控制端 App 还需要网络权限:

xml 复制代码
<uses-permission android:name="android.permission.INTERNET" />

Socket、密钥生成和 ADB 操作不能放在主线程,实际项目要放到后台线程或协程中。下面只贴 ADB 核心代码,不再封装类和辅助函数。

5. App 内最终实现

bootanimation 是已经准备好的本地文件路径,例如 App 从 assets 复制到自己的缓存目录后得到的绝对路径。adblib 可以直接作为依赖使用,也可以像示例工程一样把源码放进 App:

kotlin 复制代码
implementation("com.tananaev:adblib:1.3")

核心代码如下:

kotlin 复制代码
import android.util.Base64
import com.tananaev.adblib.AdbConnection
import com.tananaev.adblib.AdbCrypto
import java.net.Socket

val socket = Socket("127.0.0.1", 5555)
val crypto = AdbCrypto.generateAdbKeyPair { data -> Base64.encodeToString(data, Base64.NO_WRAP) }
val connection = AdbConnection.create(socket, crypto)
connection.connect()
connection.open("remount:")
connection.open("shell:cp $bootanimation /system/media/bootanimation.zip")
connection.open("shell:chmod 644 /system/media/bootanimation.zip")
connection.open("shell:chown root:root /system/media/bootanimation.zip")

这段代码对应的实际链路是:

text 复制代码
Socket 连接本机 adbd
    → ADB RSA 握手
    → 打开 remount: 控制服务
    → 用 shell:cp 替换动画文件
    → 用 shell:chmod 修正权限
    → 用 shell:chown 修正属主

6. 为什么没有继续使用 adb push

严格来说,adb push 走的是 ADB 的 sync: 文件同步协议,不是 shell: 命令。当前示例选择 shell:cp,是因为动画文件已经位于开发板本地:

text 复制代码
App 本地文件
    → shell:cp
    → /system/media/bootanimation.zip

这样不需要在 App 内再次实现 sync: 协议。换句话说:

text 复制代码
adb remount → connection.open("remount:")
adb push    → 本例用 shell:cp 替代

如果源文件路径对 adbd 不可读,例如 App 私有目录权限是 0700,而 adbd 不是 root,那么 shell:cp 会失败。此时要先把文件放到 /data/local/tmpadbd 可读位置,或者在 adblib 上实现 sync: 的发送流程。

7. 开机动画文件本身的要求

bootanimation.zip 不是普通图片压缩包,通常至少要包含:

text 复制代码
desc.txt
part0/
part1/
...

desc.txt 定义分辨率、帧率和播放分段,图片目录名和分段顺序由文件内容决定。替换前要确认目标设备的分辨率、像素格式和动画目录结构,否则即使文件复制成功,开机时也可能黑屏、卡住或回退到默认动画。

8. 实际项目容易踩的坑

  1. remount: 只是请求开始,不代表一定成功。user build、开启 dm-verity、动态分区或厂商安全策略都可能让 remount 失败。
  2. 示例代码没有读取和关闭 connection.open() 返回的 AdbStream,适合验证链路;正式实现应该保存返回值、读取错误信息并及时关闭连接。
  3. AdbCrypto.generateAdbKeyPair() 每次都会生成新密钥。若开发板要求 RSA 授权,正式版本应该保存并复用密钥对。
  4. shell:cp 使用的是开发板上的 Shell 权限,源文件和目标文件都必须对该权限可见。
  5. 开机动画目标路径要根据 ROM 确认,不要只在一台设备上硬编码后直接推广。
  6. 修改 /system 前要保留原始 bootanimation.zip,写入失败时可以恢复;复制完成后最好校验文件大小和权限。
  7. 修改系统文件属于开发板系统能力,不要把 root adbd、remount: 和任意 Shell 执行能力暴露给普通用户。

官方依据

相关推荐
消失的旧时光-19432 小时前
Android 系统层扫盲 05:Android 开机后发生了什么?从 Bootloader 到 Launcher
android·zygote·fork·aosp·cow
hunterandroid3 小时前
Android 线上卡顿治理:从布局层级到主线程负载的全链路排查
android
恋猫de小郭3 小时前
Dart Skills CLI 1.0 :AI 时代的 Dart 交付支持
android·前端·flutter
2501_916007474 小时前
使用Apple Dashboards显示和自定iOS应用性能指标与数据可视化指南
android·ios·小程序·https·uni-app·iphone·webview
蜡台4 小时前
# 已解决|MySQL 8\.4\.11 密码正确仍报错1045 \(28000\) Access denied 终极修复方案
android·mysql·adb
JMchen1234 小时前
2026年六款主流AI编程工具深度实测:Cursor、Copilot、Claude Code等对比与选型思考
android·kotlin·copilot·ai编程·开发工具·cursor·claude code
mmsx5 小时前
osmdroid 地图实战 05|让地图动起来:三个实时能力 + 六条工程排雷清单
android
码农coding5 小时前
android12 SystemUI组件之StatusBar启动
android
hai_android5 小时前
Kotlin 协程上下文:从 plus 到 CombinedContext,彻底搞懂左偏结构
android·kotlin