折腾了一整天,我逆向了创维电视的信号源切换逻辑
我有一个毛病,看见能自动化的事情就手痒。
家里电视接了三个 HDMI 设备,PS5 在 HDMI1,XBOX 在 HDMI2,HDMI3 接专门打游戏的台式机。每次切换都得翻遥控器,用了半年,忍了半年。今天终于坐不住了,打算写个脚本,一行命令切换信号源,以后再做进 Home Assistant。
这件事我预期两小时搞定。
然后就耗了一整天。

先说 ADB 是什么
怕有人不了解,简单说一句:ADB 是安卓官方的调试工具,可以通过网络或 USB 连接安卓设备,发命令、装 APK、查日志。大多数安卓电视都支持,开启方式一般是:设置 → 关于 → 版本号 连点七下开开发者模式,再把网络调试打开。以下以我自己的电视(创维酷开)为例:
连接:
adb connect 192.168.0.109:5555
# connected to 192.168.0.109:5555
这步很顺,两分钟搞定。然后开始踩坑。

第一坑:标准 keycode 全部无效
安卓系统定义了一套标准的电视输入 keycode,HDMI1 对应 243,HDMI2 是 244,HDMI3 是 245:
adb shell input keyevent 243
adb shell input keyevent 244
adb shell input keyevent 245
电视是一动不动。
我以为是 keycode 数值问题,去查了 KeyEvent 的完整列表,KEYCODE_TV_INPUT_HDMI_1 到 KEYCODE_TV_INPUT_HDMI_4 的值分别是 243-246,没错。但就是没用。
后来才明白:这套标准是谷歌定的,各厂商爱实现不实现,创维根本没接这个。
第二坑:广播发出去石沉大海
换思路,翻了一下 dumpsys package com.skyworth.tv,看到一堆 intent action,其中有个 com.skyworth.broadcast.source.select 看着很有希望。
然后试:
adb shell am broadcast -a com.skyworth.broadcast.source.select --es source_type HDMI2
# Broadcast completed: result=0
result=0 是默认返回值,意思是没有任何接收者处理这个广播。
我又试了几种参数格式:--es source HDMI2、--ei source_type 2、加包名指定接收者……全是 result=0。这个广播常量虽然存在于代码里,但根本没有注册静态接收器来处理它,纯粹是个内部定义的符号。
这时候我意识到,靠猜是走不通的,必须去看代码。
开始逆向:把 APK 拉下来
创维电视的 TV UI 程序包是 com.skyworth.tv,APK 路径在系统分区:
adb pull //system/ccos/app/SkyTVUI/SkyTVUI.apk ./
# 16.7 MB/s (16485269 bytes in 0.940s)
拉下来,建虚拟环境,装 androguard:
python -m venv .venv
.venv/Scripts/pip install androguard
androguard 可以把 dex 字节码反编译成可读的伪 Java 代码,用过 jadx 的应该很熟悉这类工具。开始分析。
找到 intent 入口
APK 里代码有混淆,类名都是单字母或短字母组合,但方法逻辑保留着。我先找 SkyTVMainActivity,它是电视 UI 的主 Activity,所有信号源相关操作都应该从这里进来。
onCreate 和 onNewIntent 都调用了同一个私有方法 h(Intent),h 里的核心是:
this.f = new StartHelper();
this.f.D(p3); // 解析 intent
this.f.I(p3); // 处理启动类型
所以真正干活的是 StartHelper 这个类。
StartHelper:四层解析链
StartHelper.D() 里构造了一个包含四个函数的数组,依次尝试解析 intent,第一个非 null 的结果就是最终的信号源:

逐个看:
解析器1:parseIfSourceExits
private SkyTVSource C(Intent p4) {
if (!TextUtils.equals(p4.getStringExtra("startType"), "switchSource")) {
return null;
}
SkyTVSource v = new SkyTVSource();
v.sourceName = p4.getStringExtra("sourceName");
v.sourceIndex = p4.getIntExtra("sourceIndex", -1);
return v;
}
这个是最直接的:startType = "switchSource" 触发,然后读 sourceName 和 sourceIndex。
解析器2:parseIfChannelInfoExits 读 channelId 或 channelName,用于频道切换,和信号源无关,跳过。
解析器3:parseIfBundleSourceChannelExits 读名为 source_channel 的 Bundle extra,里面包 sourceName、sourceIndex、displayname、signal 等。
解析器4:parseIfBundleSourceExits 读名为 source 的 Bundle extra,同样是一个 Bundle,包 name、index、displayname 等。
为什么之前的命令成功了又没成功
这里有个很微妙的点,值得单独说。
最初我发了 --es source HDMI1,切到了 PS5 那个口。当时以为命令有效,于是继续试 --es source HDMI2,结果又切回了 HDMI1,以为是参数格式不对。
实际上发生了什么:
--es source HDMI1里,source是一个字符串 extra但解析器4的
parseIfBundleSourceExits读的是p5.getBundleExtra("source"),是 Bundle,不是 String四个解析器全部返回 null,
SkyTVSource为空Activity 收到 intent 但没有解析到有效信号源,于是沿用了上次记忆的信号源——恰好是 HDMI1
后来发 --es source HDMI2 时,Activity 已经在前台运行,intent 通过 onNewIntent 送进来,同样没命中任何解析器,同样沿用上次的 HDMI1。
所以那次成功是个假成功,撞上了巧合。

sourceName 和 sourceIndex 的值
弄清楚解析链之后,还需要确认两个值。
sourceName 不是 HDMI1、HDMI2 这种,而是信号源类型。通过 androguard 提取 SkyTVSource 类的静态字段:

区分具体哪个 HDMI 口靠 sourceIndex,从 0 开始,HDMI1 = 0,HDMI2 = 1,HDMI3 = 2。
最终命令
# HDMI1
adb shell am start -n com.skyworth.tv/.activity.SkyTVMainActivity
--es startType switchSource
--es sourceName HDMI
--ei sourceIndex 0
# HDMI2
adb shell am start -n com.skyworth.tv/.activity.SkyTVMainActivity
--es startType switchSource
--es sourceName HDMI
--ei sourceIndex 1
# HDMI3
adb shell am start -n com.skyworth.tv/.activity.SkyTVMainActivity
--es startType switchSource
--es sourceName HDMI
--ei sourceIndex 2
发出去,立刻切换。三个口全部验证通过。
总结
如果你的电视也是创维/酷开系,这套命令大概率直接能用,底层都是同一套 SkyTVUI。
如果是其他品牌,方法论是一样的:
adb pull把 TV UI 的 APK 拉下来用 androguard 或 jadx 反编译
找主 Activity 的
onNewIntent/onCreate顺着 intent 解析链找信号源参数
本质上这类国产电视大多是安卓套壳,intent 机制是通用的,厂商只是在上面包了一层私有协议。知道怎么找,就不难。就算有哪里不懂的问题发给 GPT 处理,效率比以前高多了。

校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案
校验提示文案