9.1 问题
应用程序需要响应接收到的短信,也叫文本信息。
9.2 解决方案
(API Level 1)
注册一个BroadcastReceiver来监听收到的信息,并在onReceive()中处理它们。当收到一个短信时。操作系统会发送一个action值为android.provide.Telephony.SMS_RECEIVED一条短信时,操作系统会发送一个action值为android.provider.Telephony.SMS_RECEIVED的广播Intent。应用程序则可以注册一个BroadcastReceiver以过滤这个Intent并处理收到的数据。
注意:
接收这个广播同样并不会阻止系统其他的应用程序接收它。默认的消息处理应用程序还会接收这条短信并显示。
9.3 实现机制
在上一个示例中,我们定义了一个BroadcastReceiver并作为Activity的内部私有成员变量。在本例中,最好单独定义接收器并在AndroidManifest.xml中是一部标签注册它。这样即使应用程序处于非激活状态,接收器也可以处理收到的事件。以下两段代码清单显示了一个接收器示例,它监控所有收到的短信并将感兴趣的短信显示在一个Toast上。
接收短信的BroadcastReceiver
public class SmsReceiver extends BroadcastReceiver {
//想要监听的设备地址(电话号码、短代码等)
private static final String SENDER_ADDRESS = "<ENTER YOUR NUMBER HERE>";
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
Object[] messages = (Object[])bundle.get("pdus");
SmsMessage[] sms = new SmsMessage[messages.length];
//为每个收到的PDU创建短信
for(int n=0; n < messages.length; n++) {
sms[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
}
for(SmsMessage msg : sms) {
//检查短信是否来自我们已知的发送方
if(TextUtils.equals(msg.getOriginatingAddress(), SENDER_ADDRESS)) {
//让其他应用处理此信息
abortBroadcast();
//显示我们自己的通知
Toast.makeText(context,
"Received message from the mothership: "
+ msg.getMessageBody(),
Toast.LENGTH_SHORT).show();
}
}
}
}
AndroidManifest.xml 的部分内容
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidrecipes.sms"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<!-- Required in 4.4+ to read SMS provider -->
<uses-permission android:name="android.permission.READ_SMS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.androidrecipes.sms.SmsActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="com.androidrecipes.sms.SmsReceiver"
android:exported="true">
<!--添加优先级以捕获有序的广播 -->
<intent-filter android:priority="5">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
要点:
接收短信需要在清单中声明android.permission.RECEIVE_SMS权限。
接收的短信作为byte数组(Object数组),通过广播Intent的extras传递,每个byte数组代表一个短信PDU(Protocol Data Unit)。SmsMessage.createFromPdu()方法可以方便地从原始的PDU数据创建SmsMessage对象。完成设置工作之后,可以检查每条短信,判断是否要对其进行处理。在这个示例中,我们会将每条短信的发送地址与已知短代码进行比较,在收到符合要求的短信时通知用户。
框架触发的广播是有序的广播信息,这意味着每个注册的接收器将按顺序接收信息学,并且有机会修改广播,然后将其交给下一个接收器;或者取消广播,并且完全阻止任何较低优先级的接收器接收该信息。
在AndroidManifest.xml的条目中,我们添加了任意的优先级值,从而在核心系统Messages应用程序(该应用程序使用默认的优先级0)之上插入接收器。这就使我们的应用程序可以最先处理短信信息。
注意:
使用有序的广播时,在相同优先级上注册的接收器会“同时”接收Intent,从而这些接收器之间的顺序是不确定的。此外,一个接收器不能取消将广播传递给相同优先级的其他接收器。
接下来,一旦确认查看的消息来自于正在跟踪的发送方,即可调用abortBroadcast()来终止响应链。这就使我们正在处理的短信消息不会显示给用户和弄乱他们的收件箱。
要点:
没有外部部分可验证系统中还要其他哪些应用可注册以处理广播并具有非常高的优先级(至少优先级要高于您的应用程序)。您的应用程序受具有更高优先级的应用程序的支配,无法终止您要处理的消息的广播。
在这个示例中显示Toast时,你有可能希望在这个时候给用户提供用户更多有用的信息。短信中可能有应用程序的请求码,这样就可以启动合适的Acivity以将该信息在应用程序中显示给用户。
默认短信应用程序
从Android 4.4开始,使用短信的应用程序的行为已经改变。设备的Settings应用程序现在为用户提供Default SMS App(默认短信应用)选项,以供选择用户最希望用户短信的应用程序。在此框架级别中,扩展了围绕发送和接受信息的一些行为。
未选择为默认选项的应用程序仍然会使用本例范中描述的相同有序广播,发送传出的短信和监控传入的信息。然而,新框架中为默认短信应用添加了两个新的广播动作来接收信息:
- android.provider.telephony.SMS_DELIVER
- android.provider.telephony.WAP_PUSH_DELIVER
框架会使用这两个动作,独立于其他应用程序将传入的短信/MMS消息数据广播到默认短信应用。尽管原始的SMS_RECEIVED动作仍然是有序的广播,但终止广播不再用拦截传递给应用程序的某些信息的技术。但是,终止广播仍然会打断响应链,不再到达监控传入信息的其他第三方应用程序。
此外,标记为默认的短信应用程序负责通过android.provider.Telephony,将设备上接收的所有短信数据写入设备的内部内容提供程序(在API Level 19 中公开揭示的提供程序)。在系统中,只有此应用程序有权将数据写入短信提供程序,而无论应用程序是否请求获得android.permission.WRITE_SMS权限。
如果其他应用程序获得了android.permission.READ_SMS权限,它们仍然可以读取短信提供程序数据。