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
相关产品推荐
相关产品推荐

