Delphi Android应用:CallStateChanged过程未触发的解决方法咨询
问题描述
我基于Embarcadero的移动设备拨号器教程开发应用,但CallStateChanged过程始终未触发。已在Android 10和12设备测试,所有权限均已授予,相关代码如下:
unit uMain; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Permissions, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Media, FMX.StdCtrls, FMX.Objects, System.Actions, FMX.ActnList, FMX.Controls.Presentation, FMX.Effects, FMX.Filter.Effects, FMX.PhoneDialer, FMX.Platform, FMX.Edit, FMX.ListBox, FMX.Layouts,System.IOUTils, androidapi.Jni.Telephony; const AUDIO_FILENAME = 'test.mp3'; type TAudioRecPlayForm = class(TForm) btnStartRec: TButton; btnStopRec: TButton; btnStartPlay: TButton; btnStopPlay: TButton; ToolBar1: TToolBar; Label1: TLabel; ToolBar2: TToolBar; MediaPlayer: TMediaPlayer; ActionList: TActionList; actStartRecording: TAction; actStopRecording: TAction; actPlay: TAction; actStop: TAction; edtTelephoneNumber: TEdit; btnMakeCall: TButton; lblCallState: TLabel; Button1: TButton; procedure btnMakeCallClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure actStartRecordingExecute(Sender: TObject); procedure actStopRecordingExecute(Sender: TObject); procedure actStopExecute(Sender: TObject); procedure actPlayExecute(Sender: TObject); procedure ActionListUpdate(Action: TBasicAction; var Handled: Boolean); procedure SetArsOnCallStateChanged(const ACallID: String; const ACallState: TCallState); procedure Button1Click(Sender: TObject); private FMicrophone: TAudioCaptureDevice; PhoneDialerService: IFMXPhoneDialerService; FCallPhonePermission: string; FRecordCallPermission: string; FStatePhonePermission: string; procedure DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc); procedure MakePhoneCallPermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); function HasMicrophone: Boolean; function IsMicrophoneRecording: Boolean; procedure RequestPermissionsResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); public { Public declarations } Mic: TAudioCaptureDevice; end; var AudioRecPlayForm: TAudioRecPlayForm; Dial: TPhoneDialerService; implementation uses Androidapi.Helpers, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os, FMX.DialogService; {$R *.fmx} function GetAudioFileName(const AFileName: string): string; begin Result := TPath.GetTempPath + '/' + AFileName; end; procedure TAudioRecPlayForm.ActionListUpdate(Action: TBasicAction; var Handled: Boolean); begin actStartRecording.Enabled := not IsMicrophoneRecording and HasMicrophone; actStopRecording.Enabled := IsMicrophoneRecording; actStop.Enabled := Assigned(MediaPlayer.Media) and (MediaPlayer.State = TMediaState.Playing); actPlay.Enabled := FileExists(GetAudioFileName(AUDIO_FILENAME)) and (MediaPlayer.State <> TMediaState.Playing); end; procedure TAudioRecPlayForm.actPlayExecute(Sender: TObject); begin if IsMicrophoneRecording then actStopRecording.Execute; MediaPlayer.FileName := GetAudioFileName(AUDIO_FILENAME); MediaPlayer.Play; end; procedure TAudioRecPlayForm.actStartRecordingExecute(Sender: TObject); const PermissionRecordAudio = 'android.permission.RECORD_AUDIO'; begin actStop.Execute; { get the microphone device } FMicrophone := TCaptureDeviceManager.Current.DefaultAudioCaptureDevice; if HasMicrophone then begin { and attempt to record to 'test.wav' file } FMicrophone.FileName := GetAudioFileName(AUDIO_FILENAME); PermissionsService.RequestPermissions([PermissionRecordAudio], RequestPermissionsResult, DisplayRationale); end else TDialogService.ShowMessage('No microphone is available.'); end; procedure TAudioRecPlayForm.DisplayRationale(Sender: TObject; const APermissions: TArray<string>; const APostRationaleProc: TProc); begin TDialogService.ShowMessage('Нам нужно получить разрешение на запись звука с помощью вашего микрофона', procedure(const AResult: TModalResult) begin APostRationaleProc; end) end; procedure TAudioRecPlayForm.RequestPermissionsResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); begin if (Length(AGrantResults) = 1) then begin case AGrantResults[0] of TPermissionStatus.Granted: try FMicrophone.StartCapture; except TDialogService.ShowMessage('Ваш девайс не поддерживается'); end; TPermissionStatus.Denied: TDialogService.ShowMessage('Нельзя записывать звук без соответствующего разрешения!'); TPermissionStatus.PermanentlyDenied: TDialogService.ShowMessage('Если вы решите, что хотите использовать функцию аудиозаписи в этом приложении, пожалуйста, зайдите в настройки приложения и включите разрешение микрофона!'); end; end else TDialogService.ShowMessage('Что-то пошло не так с проверкой разрешений'); end; procedure TAudioRecPlayForm.actStopExecute(Sender: TObject); begin MediaPlayer.Stop; end; procedure TAudioRecPlayForm.actStopRecordingExecute(Sender: TObject); begin { stop capturing audio from the microphone } if IsMicrophoneRecording then try FMicrophone.StopCapture; except TDialogService.ShowMessage('Предоставьте разрешение!'); end; end; procedure TAudioRecPlayForm.FormCreate(Sender: TObject); var ImprPhoneState:string; begin FMicrophone := TCaptureDeviceManager.Current.DefaultAudioCaptureDevice; FCallPhonePermission := JStringToString(TJManifest_permission.JavaClass.CALL_PHONE); { test whether the PhoneDialer services are supported } TPlatformServices.Current.SupportsPlatformService(IFMXPhoneDialerService, PhoneDialerService); if Assigned(PhoneDialerService) then PhoneDialerService.OnCallStateChanged:=SetArsOnCallStateChanged else ShowMessage('Телефону пизда'); end; function TAudioRecPlayForm.HasMicrophone: Boolean; begin Result := Assigned(FMicrophone); end; function TAudioRecPlayForm.IsMicrophoneRecording: Boolean; begin Result := HasMicrophone and (FMicrophone.State = TCaptureDeviceState.Capturing); end; procedure TAudioRecPlayForm.btnMakeCallClick(Sender: TObject); begin if PhoneDialerService <> nil then begin if edtTelephoneNumber.Text <> '' then PermissionsService.RequestPermissions([FCallPhonePermission], MakePhoneCallPermissionRequestResult, DisplayRationale) else begin TDialogService.ShowMessage('Пожалуйста, наберите номер'); edtTelephoneNumber.SetFocus; end; end else TDialogService.ShowMessage('Служба PhoneDialer не поддерживается'); end; procedure TAudioRecPlayForm.Button1Click(Sender: TObject); begin PhoneDialerService.OnCallStateChanged:= SetArsOnCallStateChanged ; end; procedure TAudioRecPlayForm.MakePhoneCallPermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); begin if (Length(AGrantResults) = 1) and (AGrantResults[0] = TPermissionStatus.Granted) then PhoneDialerService.Call(edtTelephoneNumber.Text) else TDialogService.ShowMessage('Невозможно сделать телефонный звонок, потому что не было получено необходимое разрешение'); end; procedure TAudioRecPlayForm.SetArsOnCallStateChanged(const ACallID: String; const ACallState: TCallState); Begin case ACallState of TCallState(0): ShowMessage('Call Connected'); TCallState(1): ShowMessage('Call Connected'); TCallState(2): ShowMessage('Call Incoming'); TCallState(3): ShowMessage('Call Dialing'); TCallState(4): ShowMessage('Call Disconnected'); end; End; end.
解决方案
核心问题分析
- 权限不足:Android 10+监听通话状态需要
READ_PHONE_STATE权限,当前仅申请了CALL_PHONE,缺少必要权限导致无法获取状态回调。 - 枚举值硬编码错误:直接使用
TCallState(0)这类硬编码数值容易和实际枚举值不匹配,导致状态判断失效。 - 服务实例管理不当:全局变量
Dial属于冗余定义,可能导致接口实例被意外释放。
具体修复步骤
1. 添加权限申请
在FormCreate中添加READ_PHONE_STATE权限的初始化:
procedure TAudioRecPlayForm.FormCreate(Sender: TObject); begin FMicrophone := TCaptureDeviceManager.Current.DefaultAudioCaptureDevice; FCallPhonePermission := JStringToString(TJManifest_permission.JavaClass.CALL_PHONE); // 添加通话状态权限 FStatePhonePermission := JStringToString(TJManifest_permission.JavaClass.READ_PHONE_STATE); TPlatformServices.Current.SupportsPlatformService(IFMXPhoneDialerService, PhoneDialerService); if Assigned(PhoneDialerService) then PhoneDialerService.OnCallStateChanged := SetArsOnCallStateChanged else ShowMessage('Телефону пизда'); end;
在btnMakeCallClick中同时请求两个权限:
procedure TAudioRecPlayForm.btnMakeCallClick(Sender: TObject); begin if PhoneDialerService <> nil then begin if edtTelephoneNumber.Text <> '' then PermissionsService.RequestPermissions([FCallPhonePermission, FStatePhonePermission], MakePhoneCallPermissionRequestResult, DisplayRationale) else begin TDialogService.ShowMessage('Пожалуйста, наберите номер'); edtTelephoneNumber.SetFocus; end; end else TDialogService.ShowMessage('Служба PhoneDialer не поддерживается'); end;
2. 修正通话状态判断逻辑
使用TCallState的官方枚举常量替代硬编码数值:
procedure TAudioRecPlayForm.SetArsOnCallStateChanged(const ACallID: String; const ACallState: TCallState); Begin case ACallState of TCallState.Connected: ShowMessage('通话已连接'); TCallState.Connecting: ShowMessage('正在连接通话'); TCallState.Incoming: ShowMessage('来电'); TCallState.Dialing: ShowMessage('正在拨号'); TCallState.Disconnected: ShowMessage('通话已断开'); end; End;
3. 移除冗余全局变量
删除全局变量Dial: TPhoneDialerService;,因为IFMXPhoneDialerService由平台服务管理,无需手动实例化。
4. 确认AndroidManifest配置
确保AndroidManifest.xml中已声明以下权限:
<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
额外注意事项
- Android 10及以上版本中,应用在后台时可能无法收到通话状态回调,需确保应用处于前台或拥有前台服务权限。
- 测试时需使用真实设备,模拟器可能无法完全模拟通话状态变化。
内容的提问来源于stack exchange,提问作者Saler
相关产品推荐
相关产品推荐

