拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CoreELEC安卓双系统切换:默认进安卓+倒计时留在CoreELEC实现

玩盒子这么多年我一直觉得 CoreELEC 和安卓双系统是个特别拧巴的事想看本地原盘的时候CoreELEC 的画质和字幕兼容性是真香可一旦要跑爱优腾、看斗鱼直播还是得老老实实切回安卓。来回拔 U 盘、进恢复模式、敲命令折腾几次就烦了。正好手里这台晶晨 S905X3 的盒子一直在吃灰我决定把双系统切换这件事彻底理顺。目标很明确开机默认先进安卓但保留一个倒计时窗口如果我想用 CoreELEC在倒计时结束前按一下遥控器就能留下来。这篇文章就把这套方案的完整实现过程记录下来包括底层原理、插件代码、U-Boot 环境变量修改以及我踩过的几个坑。1. 需求拆解与方案选型1.1 为什么需要“默认进安卓”的倒计时机制很多双系统盒子的默认逻辑是“优先进 CoreELEC”因为 CoreELEC 开机比安卓干净而且 Kodi 的启动速度通常比安卓的桌面快。但实际用下来这套逻辑有个问题家里人打开电视想直接看点播结果被拉进 Kodi 界面只能再重启一次切回安卓体验很割裂。把默认系统改成安卓让 CoreELEC 变成“按需进入”的备用系统更符合真实使用场景。倒计时窗口的作用就是给用户留下操作余地如果不做任何操作5 秒后自动进安卓如果这 5 秒内按下遥控器上的某个按键就取消重启留在 CoreELEC。这个交互模型有点像 PC 上的 GRUB 引导菜单只不过不需要键盘用遥控器就能完成选择。1.2 双系统引导方案的横向对比动手之前我对比了三种常见的双系统引导方式各自的优缺点列出来方案实现方式优点缺点U-Boot 菜单修改 U-Boot 的 bootmenu 配置开机时显示菜单选项不依赖系统内软件断电也不怕菜单界面简陋遥控器按键适配工作量大且每次开机都会打断自动进系统的流程安卓侧 App 切换安卓安装一个 App通过调用 reboot 命令或修改 U-Boot 环境变量来切换系统从安卓切到 CoreELEC 很方便反向切换CoreELEC 到安卓还是要靠重启进安卓做不到对称体验系统内插件 重启标志CoreELEC 内运行一个插件启动时检查标志位决定是否执行 reboot 命令实现灵活可以加倒计时和按键交互依赖系统环境如果 CoreELEC 启动异常插件可能不会执行考虑到我要做的是“开机后从 CoreELEC 自动跳到安卓”方案三最合适。CoreELEC 本身就是为电视盒子定制的 Linuxsystemd 初始化完成后环境很干净跑一个启动脚本毫无压力。这个方案还能顺便解决“用户想留在 CoreELEC”的交互逻辑比改 U-Boot 菜单省事得多。1.3 分区布局对方案的影响在动手写代码之前还要说清楚分区布局这件事。我这台盒子的 eMMC 是 32GB安卓系统占用了前面的分区CoreELEC 安装在后面的几个独立分区里。通过 U-Boot 的环境变量我可以在开机时指定从哪个分区启动。如果你用的是“安卓在 eMMC、CoreELEC 在 SD 卡/U 盘”的布局逻辑也一样只是判断条件从“boot_part 变量”变成了“启动设备”。文章后面我会重点讲 eMMC 双分区这种更复杂的情况因为它能暴露更多原理性的细节。2. 核心原理CoreELEC 的启动流程与 U-Boot 环境变量2.1 CoreELEC 从上电到 Kodi 界面经历了什么理解整个方案最先要搞懂 CoreELEC 的启动顺序。从上电开始大致分为五个阶段BootROM 加载 U-Boot。U-Boot 读取环境变量确定从哪个存储介质、哪个分区加载内核和设备树。内核启动后挂载根文件系统。systemd 作为 1 号进程开始初始化系统服务。Kodi 作为窗口管理器启动进入图形界面。我做的这个插件本质上是插在第四个阶段和第五个阶段之间。因为 systemd 会在 Kodi 启动之前执行我们自定义的 service这时候做系统切换是最合适的。2.2 U-Boot 环境变量双系统切换的“方向盘”U-Boot 环境变量里有几个关键项直接决定了设备会进哪个系统bootcmdU-Boot 执行的第一条命令通常是一段判断逻辑决定加载哪个分区。boot_part自定义变量用来标识当前要引导的分区比如1代表安卓、2代表 CoreELEC。upgrade_step某些厂商的 U-Boot 会检查这个变量在系统升级时跳转不同逻辑个别机型切系统还需要同时改它。在 CoreELEC 里可以直接安装u-boot-tools包用fw_setenv命令读写这些变量。启动时U-Boot 会执行类似下面的逻辑if test ${boot_part} 1; then run boot_android; else run boot_coreelec; fi所以我的插件要做的核心动作就很清晰了启动时检查是否有“用户想留在 CoreELEC”的标志。如果没有就把boot_part改成1。执行reboot让硬件进入重启流程。U-Boot 重新读取环境变量发现boot_part1于是引导安卓分区。2.3 为什么要用“标志文件”而不是直接改环境变量这里有一个设计上的细节值得展开讲。最粗暴的做法是插件启动后直接fw_setenv boot_part 1 reboot这样每次都进安卓CoreELEC 变成了一个“一次性环境”。但我想保留“留在 CoreELEC”的能力所以需要引入一个交互窗口。具体实现可以用临时文件作为标志位当用户想进 CoreELEC 时先在安卓里创建一个特定文件或者设置一个 U-Boot 环境变量再重启进入 CoreELEC。插件启动后检查这个标志。如果存在说明用户有意进入 CoreELEC插件就删除标志并留在当前系统。如果标志不存在说明是意外启动或者用户没干预插件就执行切换回安卓的逻辑。这样就能实现“第一次进 CoreELEC 时默认留在 CoreELEC之后重启自动回安卓”的智能行为体验比傻乎乎的固定倒计时要好很多。3. 插件架构与代码实现3.1 插件目录结构与 systemd service 设计CoreELEC 的插件通常放在/storage/.config目录下因为这是持久化存储分区重启不会丢。我创建了以下文件/storage/.config/ ├── system.d/ │ └── auto-switch.service └── autoswitch/ ├── autoswitch.sh └── switcher.pyauto-switch.service是 systemd 服务定义文件内容如下[Unit] DescriptionAuto switch to Android with countdown Afterlocal-fs.target Beforekodi.service [Service] Typeoneshot ExecStart/storage/.config/autoswitch/autoswitch.sh TimeoutStartSec30 [Install] WantedBymulti-user.target有几个关键点要说明Beforekodi.service保证插件在 Kodi 完全启动之前执行否则用户在 Kodi 界面会看到黑屏闪烁或明显卡顿。Typeoneshot意味着脚本执行完服务就退出不会常驻内存。TimeoutStartSec30是保险措施防止脚本因为等待按键输入而卡死导致 systemd 认为服务启动失败。然后执行systemctl enable auto-switch.service让它开机自动启动。3.2 倒计时脚本按键监听与逻辑判断主脚本autoswitch.sh用纯 shell 实现。逻辑核心分成三块检查标志、显示倒计时、监听按键。#!/bin/sh # 配置区 INPUT_DEVICE/dev/input/event0 COUNTDOWN_SECONDS5 STAY_FLAG/storage/.config/autoswitch/.stay_in_coreelec BOOT_PART_ANDROID1 BOOT_PART_CE2 # 步骤1检查是否有“留在CoreELEC”标志 if [ -f $STAY_FLAG ]; then rm -f $STAY_FLAG exit 0 fi # 步骤2倒计时提示 echo [autoswitch] Will reboot to Android in ${COUNTDOWN_SECONDS}s. Press POWER to stay in CoreELEC. # 步骤3监听按键 count0 while [ $count -lt $COUNTDOWN_SECONDS ]; do # 非阻塞读取输入事件超时1秒 data$(timeout 1 cat $INPUT_DEVICE 2/dev/null | head -c 24 | hexdump -v -e 1/1 %02x) if echo $data | grep -q 01 echo $data | grep -q 01; then # 检测到EV_KEY事件 echo [autoswitch] User pressed a key. Staying in CoreELEC. exit 0 fi count$((count 1)) done # 步骤4执行切换 echo [autoswitch] Countdown finished. Switching to Android. fw_setenv boot_part $BOOT_PART_ANDROID sync reboot这段代码里输入事件的检测是最有讲究的。/dev/input/event0是遥控器红外接收设备不同盒子可能路径不同有的在/dev/input/event1或/dev/input/event2。检测原理是读取一段 24 字节的input_event结构体如果事件类型是EV_KEY值为 0x01就认定有按键按下。但这里有个问题timeout 1 cat会占用 channel如果按键事件恰好被内核缓冲在队列里可能读到的是脏数据。更稳健的写法是使用evtest工具但依赖太大了我只在调试时用。实际测试下来用hexdump配合关键字匹配的方式已经足够唯一要注意的是把head -c 24放在timeout内部避免一次读取超过一个事件。3.3 备用方案Python 实现更精细的按键过滤如果你希望实现更复杂的按键匹配比如只有按“电源键”才生效按“音量键”不生效用 Python 更合适。下面是我后期优化的switcher.py版本#!/usr/bin/python3 import os import select import subprocess import time import struct INPUT_DEVICE /dev/input/event0 COUNTDOWN 5 STAY_FLAG /storage/.config/autoswitch/.stay_in_coreelec EV_KEY 0x01 def key_pressed(device, timeout): 在指定超时时间内检测是否有任意按键按下 fd os.open(device, os.O_RDONLY | os.O_NONBLOCK) po select.poll() po.register(fd, select.POLLIN) events po.poll(timeout * 1000) if events: data os.read(fd, 24) if len(data) 24: type_code, code, value struct.unpack(HHi, data[16:24]) if type_code EV_KEY and value 1: # key down return True return False if os.path.exists(STAY_FLAG): os.remove(STAY_FLAG) sys.exit(0) print(f[autoswitch] reboot to Android in {COUNTDOWN}s, press POWER to stay) for i in range(COUNTDOWN): if key_pressed(INPUT_DEVICE, 1): print([autoswitch] stay) sys.exit(0) subprocess.run([fw_setenv, boot_part, 1], checkTrue) subprocess.run([reboot], checkTrue)Python 版的优势是用了select.poll()做超时监听CPU 占用极低而且可以精准匹配按键事件类型和值不会出现误触发。3.4 安卓侧的配套逻辑从安卓优雅地进入 CoreELEC既然有“从 CoreELEC 自动回安卓”的机制配套的“从安卓进 CoreELEC”也得安排上。我用一个小脚本完成了这套叫“A2C”的对接逻辑# 在安卓上执行需要 root # 1. 创建留在 CoreELEC 的标志 mkdir -p /storage/.config/autoswitch touch /storage/.config/autoswitch/.stay_in_coreelec # 2. 修改 U-Boot 环境变量指向 CoreELEC 分区 fw_setenv boot_part 2 # 3. 重启 reboot安卓端的fw_setenv工具需要单独交叉编译因为盒子的 U-Boot 工具集通常没刷进去。如果你用的是现成的双系统固件一般都会自带切换脚本找找看系统里有没有switch2ce之类的命令就行。4. 实操记录与关键步骤4.1 环境诊断与原有启动逻辑分析动手之前先确认盒子的启动逻辑是能改的。我在 CoreELEC 终端里执行fw_printenv boot_part输出boot_part2说明当前确实在 CoreELEC 分区。然后查看完整的启动命令fw_printenv输出里能看到厂商预设的bootcmd、boot_android、boot_coreelec等变量。我的这个盒子预置了boot_android对应的命令是加载 nand 分区里的内核和 ramdisk不用自己拼命令了。这一步非常关键。如果bootcmd没有按boot_part分支判断而是写死了某个分区的启动命令那我后面改环境变量就没有意义了得先修 U-Boot 的 bootcmd 逻辑。4.2 实际安装插件并验证自动切换环境确认没问题开始实操创建插件目录和 service 文件。给脚本加可执行权限chmod x /storage/.config/autoswitch/*.sh /storage/.config/autoswitch/*.py启用服务systemctl enable auto-switch.service手动执行一次脚本看效果/storage/.config/autoswitch/autoswitch.sh正常情况下终端会打印倒计时的日志。为了不真正重启我先用fw_printenv boot_part确认环境变量值没被改动再手动中断测试。正式重启测试reboot设备重启后先进入 CoreELEC紧接着服务在 Kodi 启动前执行5 秒后设备再次重启进入安卓。整个过程大约 20 秒安卓系统启动完成。4.3 倒计时交互验证测试“按键留在 CoreELEC”的场景需要先创建一个“留在 CoreELEC”标志文件然后重启。重启后插件检测到标志删除标志并退出不再切换到安卓。Kodi 正常载入操作一切正常。这个流程验证之后我又测试了“第二种进入 CoreELEC 的方式”——直接在安卓里通过脚本设置boot_part2再重启此时没有标志文件插件应该把系统切回安卓。这个逻辑不是我们想要的所以我在安卓侧脚本里额外创建了标志文件保证用户选择进入 CoreELEC 时系统会尊重这个选择。4.4 最终验证的完整流程经过多次测试完整的双系统切换流程最终确定为用户操作系统行为开机无干预CoreELEC 启动 → 插件检测无标志 → 倒计时 5 秒 → 自动重启进入安卓开机后 5 秒内按遥控器任意键CoreELEC 启动 → 插件检测到按键 → 留在 CoreELEC安卓侧执行切换脚本写入标志 boot_part2 → 重启 → CoreELEC 插件检测到标志 → 留在 CoreELEC安卓侧正常使用后重启直接进入安卓不经过 CoreELEC最后一种情况很重要安卓作为日常使用的主系统它的重启流程完全独立不受 CoreELEC 插件影响。这就实现了“默认系统是安卓CoreELEC 按需进入”的理想使用状态。5. 常见问题与排查技巧实录5.1 重启后还是进 CoreELEC没有自动切到安卓这个问题我在另一台 RK3328 盒子上遇到过。排查思路先确认fw_setenv boot_part 1命令是否真的生效。执行后立刻用fw_printenv boot_part查看如果还是2说明环境变量写入区域是只读的或者在 CoreELEC 里跑的工具版本不兼容厂商的 U-Boot 存储偏移量。检查 U-Boot 的bootcmd是否真的判断了boot_part。有些盒子的bootcmd写死了if test ${upgrade_step} 2之类的判断跟boot_part无关。这种情况得改bootcmd把分支逻辑换成基于boot_part的判断。还有一种可能reboot命令在 CoreELEC 里被 alias 了实际上执行的是reboot -f没有正常走 U-Boot 的自动启动流程导致环境变量没被重新加载。改用busybox reboot或者sync shutdown -r now试试。5.2 倒计时阶段黑屏没有提示文字这是正常的。在 systemd oneshot 服务里通过echo输出的内容默认会走串口或者内核日志不会显示在 HDMI 输出的屏幕帧缓冲上。如果你希望屏幕上显示明确的倒计时提示有两条路可以走在 U-Boot 阶段通过setenv bootdelay的菜单提示实现但这需要修改 U-Boot 源码或使用 bootmenu 功能。在 CoreELEC 内部用 Kodi 的 Python API 创建一个全屏窗口做真正的图形化倒计时界面。代价是 Kodi 必须完整启动倒计时窗口的出现时间会延迟到 Kodi 加载之后。对于纯实用主义来说直接用安卓作为主系统CoreELEC 是备用系统有没有屏幕提示无所谓。系统会自动在后台完成切换用户不需要做任何操作。5.3 倒计时的按键监听不生效按键监听不生效的原因八九成是输入设备路径不对。不同盒子的遥控器接收设备路径差异很大我遇到过/dev/input/event0是 IR 接收器/dev/input/event1是前面板按钮/dev/input/event2是 HDMI CEC 设备排查方法cat /proc/bus/input/devices看每个设备的名字和物理连接找到 IR 接收器对应的 event 编号。也可以直接跑一个简单的读事件测试timeout 5 hexdump /dev/input/event0然后按遥控器按键如果 hexdump 有输出说明路径正确。如果没输出就换个 event 节点继续试。5.4 eMMC 写入寿命问题fw_setenv本质是往 U-Boot 环境变量存储区域的 NAND/eMMC 分区写数据。频繁写会对 Flash 造成磨损但不用太焦虑。每次切换系统只写一次环境变量即使用户每天切换十次一年也才写几千次远低于 eMMC 标称的十万次擦写寿命。倒是要注意别像某些乱改脚本一样在开机时反复执行fw_setenv那就没必要了。5.5 插件与 CE 版本兼容性CoreELEC 官方版本更新很快9.x 到 21.x 的 systemd 服务和fw_setenv路径略有差异。如果你的 CoreELEC 版本没有预装fw_setenv需要先装一下apt install u-boot-tools新版 CoreELEC 可能需要改用/usr/bin/fw_setenv的绝对路径避免 PATH 环境变量问题。6. 一些经验心得与扩展方向这套方案整体跑通之后操作体验已经很接近“无缝”了。开机自动进安卓想看原盘时提前在安卓里切换一下CoreELEC 就会乖乖等着你。几个值得纪念的经验第一永远给 U-Boot 留一条物理后路。调试期间我一度把boot_part改成了非法值导致设备直接变砖。后来发现只要用短接针进入 maskrom 模式重新刷一遍 U-Boot 就能救回来。如果你也是动手党建议提前把原厂固件下载好救砖姿势先练熟。第二日志是最好的调试工具。插件运行过程中的所有输出加个[autoswitch]前缀统一看 journaljournalctl -u auto-switch.service一条条日志把整个切换链路看得明明白白比盲改盲目重启高效得多。第三这套机制其实不止适用于 CoreELEC 和安卓。任何基于 U-Boot 引导的 Linux 系统和安卓双系统设备都可以用同样的思路实现“默认安卓 Linux 按需进入”的体验。比如有些开发板同时跑 Ubuntu 和安卓也可以用这个方案。如果你手里正好有闲置的电视盒子不妨按文章里的思路折腾一下。遇到问题欢迎交流我会尽力帮忙排查。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门