设备:小米 13(HyperOS),KernelSU root
目标:把搜索框的百度换成必应;把网页搜索从写死的 Mi 浏览器改跳到 Via
结果:引擎切换 + 跳转改造均生效,附带一键切换脚本
一、搜索引擎配置藏在哪
小米搜索(com.android.quicksearchbox)的引擎逻辑集中在 com.android.quicksearchbox.xiaomi.searchengine 包,配置有三级数据源:
- APK 内置默认:
assets/websearch-default/searchengineNew.json
- 本地缓存(云端下发落盘):
/data/user/0/<pkg>/files/data/websearch/websearch-<区域>-<hash>/searchengineNew.json
- 云端接口:
POST https://api.browser.miui.com/global/search/api/searchEngine/forGlobalSearch(默认 30 分钟~1 小时刷新一次)
核心 JSON 结构:
{
"data": {
"updateIntervalMinutes": 30,
"defaultSearchEngineMap": {
"globalSearchSearchBox": "baidu",
"globalSearchHotList": "baidu"
},
"searchEngineSceneMap": {
"globalSearchSearchBox": {
"searchEngines": [
{
"searchEngineName": "baidu",
"searchUrl": "https://m.baidu.com/s?...&word={searchTerms}...",
"title_zh_CN": "百度"
}
],
"homepageSearchEngineCount": 3,
"resetSearchEngineData": false
}
}
}
}
两个场景:搜索框(globalSearchSearchBox)和热搜榜(globalSearchHotList)。{searchTerms} 是关键词占位符。
用户的选择保存在 SharedPreferences searchEngine:
| Key |
含义 |
current_engine |
当前引擎名(默认 baidu) |
engine_click_count |
是否手动切换过 |
client_scene_info |
场景 → {sceneId, currentEngine, hasChanged} |
二、把百度换成必应
思路:不动引擎名(current_engine 保持 baidu,避免服务端逻辑漂移),只把本地缓存 JSON 中 baidu 条目的 searchUrl / 标题换成必应:
searchUrl: https://cn.bing.com/search?q={searchTerms}
- 标题:必应 / 必應 / Bing
- 图标:本地 PNG(Glide 不支持 ICO/SVG,注意选 PNG 源)
防覆盖是关键:配置目录和文件都加 chattr +i(immutable)。云端刷新写文件失败时不会清本地文件,也不会切换 hash 文件名。
chattr +i searchengineNew.json
chattr +i websearch-zh-CN-<hash>/
三、点击建议”没反应”的真相
改完必应后测试:点击搜索建议词毫无反应。排查发现网页搜索跳转在 V2/n1.java 里硬编码了 Mi 浏览器:
intent.setClassName("com.android.browser",
"com.android.browser.fullsearch.FullSearchActivity");
而这台手机上 com.android.browser 根本没安装——startActivity 抛的异常被 catch 静默吞掉。也就是说:任何引擎都会无反应,跟换必应无关。
四、改 smali:跳转 Via
不装 Mi 浏览器、也不做桥接 APK,直接改 QSB 的 dex:把 V2/n1.c() 方法整体替换为”对 Via 发标准 VIEW”:
.method public static c(Landroid/content/Context;Landroid/content/Intent;Ljava/lang/String;Ljava/lang/String;Ljava/util/HashMap;)V
.locals 3
if-eqz p2, :cond_0
:try_start_0
invoke-static {p2}, Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;
move-result-object v0
new-instance v1, Landroid/content/Intent;
const-string v2, "android.intent.action.VIEW"
invoke-direct {v1, v2, v0}, Landroid/content/Intent;-><init>(Ljava/lang/String;Landroid/net/Uri;)V
const-string v0, "mark.via"
invoke-virtual {v1, v0}, Landroid/content/Intent;->setPackage(Ljava/lang/String;)Landroid/content/Intent;
const/high16 v0, 0x10000000
invoke-virtual {v1, v0}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;
invoke-virtual {p0, v1}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
:cond_0
return-void
:catch_0
move-exception v0
return-void
.end method
c() 的第二个参数恒为 {searchTerms} 替换后的完整 URL(仅被 K() 内部两处调用),替换安全。
工具链与部署(全程 root):
# Termux
pkg install apktool python
apktool d -f --no-res base.apk -o qsb_d # 只解 smali
# Python 正则整块替换 V2/n1.smali 的 c() 方法
apktool b qsb_d -o qsb_new.apk # 只重编 smali,资源原样
# 部署:覆盖 base.apk + 删 oat 缓存
cp qsb_new.apk /data/app/<pkg路径>/base.apk
chown system:system base.apk && chmod 644 && restorecon
rm -rf /data/app/<pkg路径>/oat
am force-stop com.android.quicksearchbox
几个关键点:
- base.apk 无 fs-verity(
lsattr 无 V 标志),可直接替换文件
- 签名校验只在安装时发生;重启后 PMS 读 packages.xml 缓存,不重校验
- 替换 dex 必须删
oat/(art/odex/vdex),否则 checksum 不匹配;首次启动 JIT 稍慢
- 备份原 APK 和 websearch 目录到
/data/local/tmp/,出问题覆盖回去 + 删 oat 即回滚
五、一键脚本
把整个流程沉淀成脚本:engine(换引擎)、browser(换跳转目标)、restore(回滚)、status(体检)。换引擎时自动 chattr 解锁/加锁、修 SELinux context;换浏览器时基于原版备份幂等重建 APK。支持 via/chrome/edge,自定义引擎 URL 也行。
qsb-tool.sh status # 当前引擎/跳转目标/保护状态
qsb-tool.sh engine bing # 换成必应
qsb-tool.sh browser via # 跳 Via
qsb-tool.sh restore all # 一键回滚
qsb-tool.sh refresh-backup # OTA 升级后刷新 APK 备份
一键脚本下载:qsb-tool.sh
OTA 后一键恢复:ota-recover.sh——应用商店/系统更新后跑一次即可(自动检测:资源完整+已patch→只刷引擎;原版→重打补丁;资源缺陷(小米更新包 bug)→自动降级分区版再打补丁)。
依赖:root(KernelSU/Magisk)+ Termux 的 python3、apktool(仅 browser 命令需要)。放到任意目录,bash qsb-tool.sh help 查看用法。
五·五、LSPosed 模块方案(进阶,推荐长期使用)
改 dex 的痛点:OTA 升级后失效、云端换 hash 绕过锁。如果你装了 LSPosed(Zygisk 版),可以直接 hook,不改任何文件、永久免疫 OTA:
- Hook 点 1:
com.android.quicksearchbox.xiaomi.searchengine.f.h(Context)(返回 searchUrl)→ 强制替换为 Bing URL
- Hook 点 2:
V2.n1.c(Context, Intent, String, String, HashMap) 后置 → 拦截 Mi 浏览器跳转,重定向到已安装的浏览器(默认 mark.via)
用法:下载 qsb-hook.apk 安装 → LSPosed Manager 里启用 → 作用域勾选 com.android.quicksearchbox → 重启 zygote(软重启或重启手机)生效。
⚠️ 注意:混淆类名 V2/n1 随 QSB 版本变化,本模块仅验证于 QSB 13.1.0.08242(HyperOS 4 / Android 17)。其它版本若 hook 失败,LSPosed 日志会提示 hook ... FAIL,不影响原功能。
LSPosed 模块下载:qsb-hook.apk
六、经验总结
- 改系统应用优先改数据,其次改 dex,最后才重打包:配置 JSON + chattr 保护就解决了 90% 的问题。
- 无反应 ≠ 没调用:先看代码里异常是不是被吞了,再看目标组件是否存在。
- sed 处理 XML 转义串是坑(
& 是特殊符号),字符串替换一律用 Python。
- OTA 是最大敌人:升级会覆盖
/data/app 的修改,脚本里 refresh-backup + 重放流程即可恢复。