Delphi Firemonkey Android8下短信发送状态检测及系统短信应用显示问题
问题原因说明
从Android 4.4开始,只有系统默认短信应用调用SmsManager发送的短信,才会被系统自动写入短信公共Provider(content://sms/*路径)。非默认短信的第三方应用直接调用API发送的短信,系统不会自动存储,所以你既没法查到记录,系统默认短信应用也看不到。
问题1解决方案:让应用发出的短信显示在默认短信应用中
有两种实现方案,可根据业务需求选择:
方案1:手动将短信记录插入系统短信Provider(支持后台自动发送)
是不需要用户交互、实现后台自动发短信的首选方案,操作步骤如下:
- 首先申请必要权限:除了已有的
SEND_SMS权限,还需要动态申请READ_SMS和WRITE_SMS权限(Android 6.0及以上不能只在AndroidManifest.xml里声明,必须运行时弹窗申请用户授权) - 发送短信后立即向系统短信Provider插入记录,插入示例逻辑(Delphi语法):
var values: JContentValues; begin values := TJContentValues.JavaClass.init; values.put(StringToJString('address'), StringToJString(targetstr)); // 收信号码 values.put(StringToJString('body'), StringToJString(bodystr)); // 短信内容 values.put(StringToJString('type'), Integer.valueOf(2)); // 2对应TYPE_SENT已发送类型,后续发送失败可以更新为3(TYPE_FAILED) values.put(StringToJString('status'), Integer.valueOf(0)); // 0对应STATUS_PENDING待确认发送结果 values.put(StringToJString('date'), Integer.valueOf(JavaSystem.currentTimeMillis)); // 发送时间戳 TAndroidHelper.Activity.getContentResolver.insert(StrToJURI('content://sms/sent'), values); end;
- 后续确认短信发送失败后,更新对应记录的
type为3,或者直接移动到content://sms/failed路径下即可。
方案2:调用系统短信应用发送(无需申请短信权限,需要用户交互)
如果业务允许用户手动确认发送,可以直接调起系统默认短信应用发送,短信会自动由系统应用存入公共Provider,无需手动操作:
var intent: JIntent; uri: Jnet_Uri; begin uri := TJnet_Uri.JavaClass.parse(StringToJString('smsto:' + targetstr)); intent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_SENDTO, uri); intent.putExtra(StringToJString('sms_body'), StringToJString(bodystr)); TAndroidHelper.Activity.startActivity(intent); end;
缺点是会跳转到系统短信的发送界面,需要用户手动点击发送按钮,不能实现后台自动发送和自动重发逻辑。
问题2解决方案:正确判断短信发送和送达状态
你之前调用sendMultipartTextMessage时后两个参数传了nil,这两个参数就是用来监听发送状态和送达状态的PendingIntent,按以下步骤实现即可:
- 注册两个广播接收器,分别监听短信发送结果和对方送达回执:
- 发送结果广播:可以判断短信是否成功从本机发出(信号差、欠费等原因发不出会返回失败)
- 送达回执广播:可以判断对方是否成功收到短信(需要运营商支持,国内大部分运营商默认支持)
- 发送短信时构造对应的
PendingIntent数组,传入sendMultipartTextMessage方法:
// 示例:构造发送状态的PendingIntent,每个拆分后的短信段对应一个 var sentIntents: TJArrayList; sentPI: JPendingIntent; i: Integer; begin sentIntents := TJArrayList.JavaClass.init; for i := 0 to smsarray.size - 1 do begin intent := TJIntent.JavaClass.init(StringToJString('com.你的包名.SMS_SENT')); // 传入短信标识,方便收到广播时匹配对应的短信 intent.putExtra(StringToJString('sms_id'), 你生成的短信唯一ID); sentPI := TJPendingIntent.JavaClass.getBroadcast(TAndroidHelper.Activity, i, intent, 0); sentIntents.add(sentPI); end; // 送达回执的PendingIntent构造逻辑同上,action自定义为比如com.你的包名.SMS_DELIVERED // 调用发送接口时传入 smsManager.sendmultiparttextMessage(smsTo, nil, smsarray, sentIntents, 送达PendingIntent数组); end;
- 在广播接收器的
onReceive方法里处理结果:
- 发送结果广播:
getResultCode返回Activity.RESULT_OK就是发送成功,返回SmsManager.RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_NO_SERVICE等常量就是发送失败,你可以根据错误类型触发自动重发逻辑。 - 送达回执广播:
getResultCode返回Activity.RESULT_OK就是对方已成功接收。
注意事项
- Android 8.0及以上对静态注册的隐式广播有限制,建议动态注册广播接收器,避免收不到回调。
- 如果使用手动插入短信记录的方案,收到发送状态回调后记得同步更新系统Provider里的短信状态,这样默认短信应用也能显示正确的发送结果。
内容的提问来源于stack exchange,提问作者jwan
相关产品推荐
相关产品推荐

