13.1.3 短信的发送和接收

除了处理通话,在Android中,开发者还可以控制短信的发送和接收,比如,使用android.telephony.SmsManager对象,可以发送短信或彩信:


//构造回调事件,短信发送结束后,会发出对应的Intent请求

Intent intent=new Intent("com.sample.sms_sent");

intent.putExtra("sms_id",GenerateSmsId());

PendingIntent pendingIntent=PendingIntent.getBroadcast(

context,0,intent,0);

//发送文本短信

SmsManager smsManager=SmsManager.getDefault();

smsManager.sendTextMessage(

phone_number, null, text_content, pendingIntent, null);


其中,pengdingIntent对象是一个异步的Intent请求(在下一节中会更详细地进行介绍),当短信发送完成后,会触发预设在其中的广播事件;通过监听该广播事件,可以获知短信发送的结果:


//在对应的触发器组件中,监听并处理短信发送的结果

@Override

protected void onHandleIntent(Intent intent){

if("com.sample.sms_sent".equals(intent.getAction())){

//读取出预设的短信ID和短信发送的结果

long sms_id=intent.getLongExtra("sms_id",0);

int result=intent.getIntExtra("result",0);

//处理不同的短信发送结果

if(result==Activity.RESULT_OK){

//发送成功

}else{

//发送失败

1

}

}

}


通过该方式发送短信,无法与系统的短信数据库整合起来,也就是说通过这种方式发送出去的短信,无法在其他短信应用中查看,也无法通过其他短信应用来管理其状态。在很多场景下,开发者如果不是期望在后台发送短信,可以通过发送Intent请求,调用第三方短信应用的界面组件来实现短信的发送。在这种方式下,用户可以对准备发送的短信进行确认和修改,并自行进行发送:


//构造短信发送的Intent对象

//如果使用Action为Intent.ACTION_SENDTO的方式,可以包含目标地址

//如果使用Action为Intent.ACTION_SEND的方式,可以预设短信内容等

final Uri sms=Uri.parse("smsto:1234567");

Intent intent=new Intent(Intent.ACTION_SENDTO, sms);

//调用短信发送组件,如果成功会调出编辑发送短信的界面组件

//其中会填充好需要发送的地址、预设的短信文本等内容

startActivity(intent);


调用第三方短信界面组件来发送短信,不仅能节约开发编辑短信界面的成本,还可以避免申请短信发送权限,只要场景合适,应该优先使用该方式。

但基于第三方组件发送短信的方式,需要用户再次进行确认,并且无法控制最终的短信发送结果,在有些场景下并不适合。这时候,就需要考虑将短信插入到系统短信数据库的待发送队列来进行短信发送。通过这样的方式,将短信的发送整合到系统短信数据库中,可以通过其他短信应用查看,也可以实现后台的发送和控制。

系统短信数据库被封装成了一个数据源组件,其地址为content://sms,通过该数据源可以对系统中的短信进行增删改查操作。当有新短信插入到数据源的待发送队列中时,系统会自动触发短信发送的流程,该短信的发送状态随时可以在短信数据源中进行查询:


//构造需要发送的短信,在对应的数据列上插入对应的值

ContentValues values=new ContentValues();

values.put("address",TARGET_ADDRESS);//短信地址

values.put("body",SMS_CONTENT);//短信内容

values.put("type",6);//短信的类型,6表示待发送短信

values.put("date",CurrentDate());//短信发送时间

//将短信插入数据源

Uri insertUri=getContentResolver().insert("content://sms",values);

//从插入的地址信息中解析出短信id,通过这个id值,随时可以在数据源中查看短信的状态、内容等信息

long sms_id=Long.valueOf(insertUri.getLastPathSegment());


基于该方式进行的短信发送,可能存在一定的兼容性隐患。短信数据源属于系统隐藏的数据源组件,其接口和数据库接口的稳定性都不给予保证。一旦在未来某个版本中,短信数据源的地址或数据库结构进行了变更,基于该方式进行的短信发送就可能会失效。

除了可以发送短信,Android还开放了接口供开发者处理新接收到的短信。当系统从通信底层获取到新的短信信息后,会发出Action为android.provider.Telephony.SMS_RECEIVED的广播事件,通过监听该事件可以读取新收到的短信内容:


//在注册了android.provider.Telephony.SMS_RECEIVED的触发器组件中,监听并处理新短信

@Override

protected void onHandleIntent(Intent intent){

//从名为pdus的Extra域中,读取新短信的内容

Object[]pdus=(Object[])bundle.get("pdus");

//把所有数据块中的内容抽取出来,拼接成完整的短信内容

String sms_content;

SmsMessage[]messages=new SmsMessage[pdus.length];

for(int i=0;i<messages.length;i++){

messages[i]=SmsMessage.createFromPdu((byte[])pdus[i]);

sms_content+=messages[i].getDisplayMessageBody();

}

//其余信息,均可以从第一个短信数据块中抽取出来

SmsMessage first_message=messages[0];

String address=first_message.getDisplayOriginatingAddress();

int status=first_message.getStatus();

}


在实际开发中,需要特别留意该触发器组件的优先级。因为该广播事件是一个有序的事件广播,系统会按照触发器组件的优先级,从高到低依次进行通知。优先级高的触发器组件可以通过调用BroadcastReceiver.abortBroadcast终止该事件的传递,使得低优先级的触发器组件无法获知新短信的到来(大量的短信安全应用、第三方短信应用,都会选择终止新短信的传播,实现所谓的安全或者避免过多的新短信弹出窗口)。因此,如果收取新短信通知对于所开发的应用来说非常关键,那么就需要尽量提高触发器组件的优先级,从而尽早地获取新短信的事件通知,避免通知被截获导致的异常。

注意 通话和短信是移动设备最基础的功能之一。在底层,Android通过RIL将与设备相关的通话实现有效地独立出来,使设备厂商可以独立地升级通话模块。而在上层,Android为开发者提供了android.telephony,可以实现通话状态监听、通话信息获取、短信收发等功能。

此外,利用Intent对象调用第三方通话和短信组件,来实现拨号、短信发送等,是最经济快捷的手段。