基于Delphi FMX实现Android Service播放声音解决后台无提示音问题
Android 后台Service播放提示音失效解决方案
问题根因
- Android 8.0及以上系统对普通后台Service做了严格的运行限制,无前台优先级的后台服务会在短时间内被系统回收,无法正常执行音频输出逻辑
- FMX的
TMediaPlayer依赖UI线程的Application上下文,Service中无对应上下文所以无法调用 - 当前
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
相关产品推荐
相关产品推荐

