解决漫步者H180Plus(TypeC)耳机连接三星手机后每次播放音频出现滴滴声

项目代码:https://github.com/ewt45/edifier-h180plus-typec-samsung

可以在 release 下载编译后的 apk

我的博客 https://ewt45.github.io/blogs/2025/09/20-edifier-h180plus-samsung/

前言

想买个typec的有线耳机,结果坑一堆。

漫步者H180Plus(TypeC),手机为三星s23 oneui6.1 。插上后表现为:开始播放音频时会有滴滴两声。同一个app暂停再播放,不会再次滴滴,换一个应用则会重新滴滴一次。

搜索发现网上好多人都遇到了这个问题。workaround是下载 edifier connect 这个app (google play 上中文名叫漫步者蓝牙连接软件,不是那个EDIFIER ConneX), 然后打开保持后台运行。每次插入耳机时,出现弹窗提示app要连接耳机,点击确认即可。

求助,耳机发声前会有滴滴声

不清楚原理,反正保持app在后台运行就不会出现滴滴声。也没有什么其他的好方法,那就自己写一个app简化一下流程吧。目标:应用体积尽可能小,每次插入耳机时无需手动确认。

参考

分析 Edifier Connect

参考 IntegratedConnector.connect()

先用 UsbFindConnector 找到漫步者设备

然后 UsbPermissionConnector 检查是否有USB设备权限

然后 UsbConnector 与设备连接

最后用 Sender 发送了一些数据。

可以通过查看logcat看到具体发送的内容是什么

logcat 复制代码
16:57:35.060 dzq-integrated                                   V  send:[aa ec d8 00 00 6e}]main
16:57:35.073                                                  V  recv:[bb ec d8 00 13 00 00 01 00 00 01 00 00 00 01 00 01 00 01 00 00 00 00 00 97]

顺带一提很多日志都是中文,这app还是国人写的=-=

可以从日志中看到,向耳机发送数据后,耳机又返回了数据,至于这个返回数据有什么用就不知道了,实际也不用知道,因为后面发现只要与设备连接,不发送这串数据也没有滴滴声。

编写 app

USB设备连接

USB设备的连接断开监听,发送数据等在USB 主机概览中都介绍的很详细。

USB连接有两种,USB配件和USB主机,看起来我们需要看的USB主机的文档。

连接监听可以写在清单中,为 activity 添加 intent-filter 标签,并附加 meta-data 指定要筛选的设备。

断开监听不能写在清单中,只能在运行时动态注册广播接收器。

连接后,先获取USB权限(通过 intent-filter 获取到的自动拥有权限)然后通过 usbManager.openDevice() 连接设备,接下来就可以与设备进行通信了。

关闭连接就无法屏蔽滴滴声了,所以不需要关闭连接的代码。

所需权限

录音权限

注意到,连接时的弹窗,有一句"此应用未获取录音权限"(下图左)。而授予录音权限后,这句消失,并且多了一个选项"连接 xxx 设备 后一律打开 xxx app"(下图右)。如果勾选,之后插入usb设备就不会显示弹窗,而是自动打开这个app.

通知权限

为保证服务能持续运行,需要发送一个前台通知,因此需要通知权限。

关闭电池优化

目标sdk比较高的话,不允许后台运行的app启动前台服务,除非关闭了电池优化。另外也可以防止在运行中被杀死。总之开启了比较保险。

另外,长时间不使用可能导致权限被收回。最好在应用设置界面 - 权限,把对应选项关掉。

前台服务

插入耳机后,服务启动与持续运行的逻辑是这样的:

  1. 创建一个过渡 Activity, 在清单中为其添加 intent-filter 接收 USB 设备的连接监听。
  2. 耳机插入时,过渡 Activity 被调起,在 onCreate 中执行 startForegroundService 启动前台服务,然后直接 finish 结束自身。注意自身 intent 包含了 UsbDevice 的信息,所以需要将自身 intent 存入启动 Service 的 intent 的 extra 中。
  3. Service
    • 在 onCreate 中注册广播接收器,监听设备的断开。
    • 在 onStartCommand 中 取出 intent, 获取 UsbDevice 并尝试连接。如果执行成功,执行 startForeground 发送前台通知。
  4. 耳机接入期间,服务一直运行(理想情况下)。
  5. 如果接收到了设备断开的广播,首先确认是否是目标设备,然后 stopService 结束服务。

由于我将 app 的目标 SDK 设置为了安卓 16, 所以对于前台服务的启动要求比较严格。

  • 清单中 service 标签必须要指定 foregroundServiceType 属性。这里我设置为 android:foregroundServiceType="connectedDevice"

  • 清单中必须声明前台服务权限,以及对应具体的前台服务类型权限

    xml 复制代码
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
  • 在 activity 中启动前台服务时,执行 startForegroundService 而非 startService。然后 Service 必须在短时间内执行 startForeground 显示通知,否则系统会抛出异常 ForegroundServiceDidNotStartInTimeException

  • startForeground 中必须指定 foregroundServiceType,需要与清单中声明的对应,否则会报错。我这里用 ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE

  • 发送通知需要先创建通知渠道。这部分我交给ai写的,结果不知道为什么明明是前台服务但通知也能被划掉。

  • 目标sdk 安卓13开始,需要在清单中声明通知权限 <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

过渡 Activity 的其他注意事项

  • 它的作用是调起 Service 然后结束运行,将其主题修改为透明,这样在视觉上不会打扰用户(实际上会占用0.几秒屏幕,所以还是会小小打扰一下)。
  • 在清单中为其声明 android:excludeFromRecents="true" 和 android:noHistory="true",这样不会出现在最近应用中。
  • 还需要声明 android:taskAffinity="" 否则和主 activity 一个 affinity, 启动并退出后,如果主 activity 存在就会被带起来。

其他

缩小 apk 构建体积,在 gradle 中添加 isMinifyEnabled = trueisShrinkResources = true 就行了。构建出 apk 体积 1M.

中途发现多次拔插会导致系统卡死,几十秒后自动重启(也可手动强制重启)。改了一大堆东西,也不知道哪些有效了。反正最后排查出来一个确定的是,service.onStartCommand 中如果 startForeground 无条件执行,插上就会卡死;如果仅在没有问题(不 stopSelf)的情况下 startForeground 就一切正常。

关闭电池优化,有个需要声明权限才能发送的 Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, 我改用不需要权限的 Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS 了。不过去应用设置 - 电池 改成 "不受限制" 也是一样的。

注意第一个 action 需要附带包名作为 uri data, 第二个 action 附带了包名反而无法启动。通过 MT 的 activity 记录 发现第二个 action 调起的 activity 为 com.android.settings.Settings$HighPowerApplicationsActivity,手动通过 ComponentName 启动这个 activity 也行。

安卓13的通知权限,可以通过 Manifest.permission.POST_NOTIFICATIONS 申请权限,然后直接出现底部弹窗,不需要跳转到设置页面。

最终效果

耳机插上后自动显示通知,播放音频没有滴滴声。拔出后通知关闭。

待完善

  • 由于默认都有USB通信权限,就没写申请USB权限的代码了。另一部分原因就是之前系统卡死也与申请权限有些神秘的联系。

  • 通知抽屉里的通知图标,主体图片很小,然后周围一大圈白色背景,改了半天没改明白。Shizuku就都是蓝色的,以后有时间可以研究研究怎么改图标背景色。

相关推荐
十幺卜入18 小时前
Unity3d C# 基于安卓真机调试日志抓取拓展包(Android Logcat)
android·c#·unity 安卓调试·unity 安卓模拟·unity排查问题
frontend_frank19 小时前
脱离 Electron autoUpdater:uni-app跨端更新:Windows+Android统一实现方案
android·前端·javascript·electron·uni-app
柳鲲鹏19 小时前
OpenCV视频实时跟踪目标,多种算法,python版
opencv·算法·音视频
薛晓刚19 小时前
MySQL的replace使用分析
android·adb
Facechat19 小时前
视频混剪-撤销/重做系统
音视频
心动啊12119 小时前
简单了解下音频和VAD
音视频
DengDongQi19 小时前
Jetpack Compose 滚轮选择器
android
stevenzqzq19 小时前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停20 小时前
MySQL事务
android·数据库·mysql
朝花不迟暮20 小时前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio