You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Delphi FMX实现Android Service播放声音解决后台无提示音问题

Android 后台Service播放提示音失效解决方案

问题根因

  1. Android 8.0及以上系统对普通后台Service做了严格的运行限制,无前台优先级的后台服务会在短时间内被系统回收,无法正常执行音频输出逻辑
  2. FMX的TMediaPlayer依赖UI线程的Application上下文,Service中无对应上下文所以无法调用
  3. 当前ToneGenerator实现存在两个问题:一是STREAM_SYSTEM音频流在多数用户设备上默认处于静音状态,二是未持有音频焦点,播放会被系统限制

修复步骤

  • 第一步:配置必要权限
    在AndroidManifest.xml中添加如下权限声明:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Android 6.0及以上版本需要在应用启动时动态申请上述运行时权限。

  • 第二步:将普通Service改造为前台Service
    在Service的OnStartCommand事件中添加前台服务启动逻辑,绑定一个常驻通知(Android 8.0以上要求前台服务必须绑定通知渠道):
procedure TDMSounds.ServiceStartCommand(Sender: TObject; Intent: JIntent; Flags, StartId: Integer; var Result: Integer);
var
  Notification: JNotification;
  Channel: JNotificationChannel;
  NotificationMgr: JNotificationManager;
begin
  // Android 8.0及以上创建通知渠道
  if TJBuild_VERSION.JavaClass.SDK_INT >= 26 then
  begin
    Channel := TJNotificationChannel.JavaClass.init(
      StringToJString('sound_service_channel'),
      StringToJString('后台提示音服务'),
      TJNotificationManager.JavaClass.IMPORTANCE_LOW
    );
    NotificationMgr := TJNotificationManager.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.NOTIFICATION_SERVICE));
    NotificationMgr.createNotificationChannel(Channel);
  end;

  // 构建通知
  Notification := TJNotification_Builder.JavaClass.init(TAndroidHelper.Context, StringToJString('sound_service_channel'))
    .setContentTitle(StringToJString('数据采集服务运行中'))
    .setContentText(StringToJString('接收数据时将播放提示音'))
    .setSmallIcon(TJR_drawable.JavaClass.ic_dialog_info) // 替换为你应用的图标资源
    .build();

  // 启动前台服务,ID为自定义的非0整数
  startForeground(1001, Notification);
  Result := TJService.JavaClass.START_STICKY;
end;
  • 第三步:替换音频播放逻辑,使用Android原生MediaPlayer实现
procedure TDMSounds.PlaySound(aFile: String);
var
  MediaPlayer: JMediaPlayer;
  AudioPath: JString;
begin
  MediaPlayer := TJMediaPlayer.JavaClass.init;
  try
    // 设置音频流类型为闹钟流,不会受系统静音影响,也可改为STREAM_MUSIC
    MediaPlayer.setAudioStreamType(TJAudioManager.JavaClass.STREAM_ALARM);
    // 拼装音频文件路径,可根据你实际的文件存放位置调整
    AudioPath := StringToJString(System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetDocumentsPath, aFile));
    MediaPlayer.setDataSource(AudioPath);
    MediaPlayer.prepare;
    MediaPlayer.start;
    // 播放完成后释放资源
    MediaPlayer.setOnCompletionListener(TJMediaPlayer_OnCompletionListener.Create(
      procedure(MP: JMediaPlayer)
      begin
        MP.release;
      end
    ));
  except
    on E: Exception do
    begin
      if Assigned(MediaPlayer) then MediaPlayer.release;
    end;
  end;
end;

可选替代方案

如果不需要自定义音效,仅需要蜂鸣提示,也可以直接调用Android系统的蜂鸣接口,无需打包音频文件:

procedure TDMSounds.PlayBeep;
var
  AudioManager: JAudioManager;
  Volume: Integer;
begin
  AudioManager := TJAudioManager.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.AUDIO_SERVICE));
  Volume := AudioManager.getStreamVolume(TJAudioManager.JavaClass.STREAM_ALARM);
  if Volume > 0 then
  begin
    AudioManager.playSoundEffect(TJAudioManager.JavaClass.FX_KEYPRESS_STANDARD, Volume);
  end;
end;

内容的提问来源于stack exchange,提问作者Ian Fear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 21:09:03