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

Android服务中通过手机号查联系人姓名(Delphi Tokyo+FMX):wSubCursor.getCount返回0求解

解决Delphi Tokyo FMX Android服务中联系人查询getCount返回0的问题

我之前在做FMX Android开发时也碰到过几乎一模一样的问题,明明通讯录里存着目标手机号,但查询返回的Cursor的getCount()始终是0。结合Delphi Tokyo的特性和Android的权限、查询规则,给你梳理几个最可能的原因和对应的解决办法:

1. 权限未正确授予(最常见原因)

Android 6.0(API 23)及以上版本需要动态申请权限,哪怕你已经在AndroidManifest.xml里声明了READ_CONTACTS权限,后台服务也可能无法直接获取到权限——因为服务的权限上下文和前台Activity是分离的。

解决步骤:

  • 首先在服务启动前,确保主Activity已经申请并获取到READ_CONTACTS权限。可以用以下代码检查权限状态:
uses
  Androidapi.Helpers, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os;

function HasReadContactsPermission: Boolean;
var
  Perm: JString;
begin
  Perm := StringToJString('android.permission.READ_CONTACTS');
  Result := TAndroidHelper.Context.checkSelfPermission(Perm) = TJPackageManager.JavaClass.PERMISSION_GRANTED;
end;
  • 如果权限未授予,需要在前台Activity中触发权限申请(服务无法直接弹出权限对话框):
procedure RequestContactsPermission;
var
  Perms: TJavaObjectArray<TJString>;
begin
  Perms := TJObjectArray.Create(TJString.ClassType, 1);
  Perms.Items[0] := StringToJString('android.permission.READ_CONTACTS');
  TAndroidHelper.Activity.requestPermissions(Perms, 1001); // 1001是自定义请求码
end;
  • 注意:Android 10+及更高版本中,后台服务访问通讯录可能需要额外配置,比如将服务设置为前台服务(显示通知)来提升权限优先级。

2. 手机号格式不匹配

通讯录中的手机号通常会带有格式符(比如空格、括号、区号前缀+86等),而你查询的是纯数字字符串,导致数据库匹配失败。

解决办法:

先对输入的手机号做清洗,去掉所有非数字字符,再用LIKE模糊查询匹配通讯录中的号码:

uses
  System.RegularExpressions;

function CleanPhoneNumber(const APhone: string): string;
begin
  // 移除所有非数字字符
  Result := TRegEx.Replace(APhone, '[^0-9]', '');
end;

// 构建查询语句
var
  LCleanedPhone: string;
  LWhereClause: JString;
  LParams: TJObjectArray;
begin
  LCleanedPhone := CleanPhoneNumber('你的目标手机号');
  // 使用LIKE匹配,兼容带前缀/格式的号码
  LWhereClause := StringToJString(
    TJContactsContract_CommonDataKinds_Phone.JavaClass.NUMBER + ' LIKE ?'
  );
  // 构建参数数组
  LParams := TJObjectArray.Create(TJString.ClassType, 1);
  LParams.Items[0] := StringToJString('%' + LCleanedPhone + '%');

  // 执行查询
  wSubCursor := TAndroidHelper.ContentResolver.query(
    TJContactsContract_CommonDataKinds_Phone.JavaClass.CONTENT_URI, // 正确的联系人手机号URI
    nil, // 查询所有字段,也可以指定需要的字段提升性能
    LWhereClause,
    LParams,
    nil
  );
end;

3. 错误使用URI或查询字段

如果你的查询URI用错了(比如直接用ContactsContract.CONTENT_URI而不是手机号专用的URI),或者没有正确指定查询字段,也会导致查询结果为空。

关键注意点:

  • 必须使用TJContactsContract_CommonDataKinds_Phone.JavaClass.CONTENT_URI作为查询的URI,这是Android系统专门存储联系人手机号的数据源。
  • 如果你明确指定查询字段,要确保包含NUMBER和DISPLAY_NAME字段,示例如下:
var
  LProjection: TJObjectArray;
begin
  LProjection := TJObjectArray.Create(TJString.ClassType, 2);
  LProjection.Items[0] := StringToJString(TJContactsContract_CommonDataKinds_Phone.JavaClass.DISPLAY_NAME);
  LProjection.Items[1] := StringToJString(TJContactsContract_CommonDataKinds_Phone.JavaClass.NUMBER);

  wSubCursor := TAndroidHelper.ContentResolver.query(
    TJContactsContract_CommonDataKinds_Phone.JavaClass.CONTENT_URI,
    LProjection,
    LWhereClause,
    LParams,
    nil
  );
end;

4. Cursor的正确使用与资源释放

一定要先判断Cursor是否不为null,再调用getCount()——如果查询失败,query()方法可能直接返回null,而不是count=0。同时用完Cursor后必须关闭,避免内存泄漏和后续查询异常。

正确的Cursor处理流程:

if Assigned(wSubCursor) then
begin
  try
    if wSubCursor.getCount > 0 then
    begin
      wSubCursor.moveToFirst;
      // 获取联系人姓名
      var LContactName := JStringToString(
        wSubCursor.getString(
          wSubCursor.getColumnIndex(TJContactsContract_CommonDataKinds_Phone.JavaClass.DISPLAY_NAME)
        )
      );
      // 处理姓名...
    end;
  finally
    wSubCursor.close; // 必须关闭Cursor
  end;
end;

5. Android版本兼容性问题

Delphi Tokyo对Android 8.0+的后台服务支持有一些细节需要注意:

  • Android 8.0+限制了后台服务的运行时间,如果你是在后台服务中执行查询,建议将服务转为前台服务(显示一个持续的通知),避免被系统杀死。
  • Android 11+对通讯录访问有更严格的隐私限制,需要在AndroidManifest.xml中添加android:permission="android.permission.READ_CONTACTS",并且确保服务的启动方式是绑定服务(而非启动服务),以继承主应用的权限上下文。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:23:00