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

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.
解决方案

核心问题分析

  1. 权限不足:Android 10+监听通话状态需要READ_PHONE_STATE权限,当前仅申请了CALL_PHONE,缺少必要权限导致无法获取状态回调。
  2. 枚举值硬编码错误:直接使用TCallState(0)这类硬编码数值容易和实际枚举值不匹配,导致状态判断失效。
  3. 服务实例管理不当:全局变量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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 00:52:03