Pro Android学习笔记(一二十):Telephony API(2):接收短信

文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处http://blog.csdn.net/flowingflying以及作者@恺风Wei。

很多APP需要短信发送一个验证码,APP可以截取短信中的验证码,我们来学习一下如何在应用中通过BroastReceiver截获短信。

接收短信的机制

接收短信,需要有接收短信的授权:

<uses-permission android:name="android.permission.RECEIVE_SMS" />

当系统接收到短信时,会通过action为"android.provider.Telephony.SMS_RECEIVED"的广播,我们在程序中实现广播接收器,监听这个广播,就可以获得短信。

短信广播接收器

小例子代码如下:

public class RecvSMSMonitor extends BroadcastReceiver{
    public static final String SMSCODE="android.provider.Telephony.SMS_RECEIVED";

    @Override
    public void onReceive(Context context, Intent intent) { 
        if(intent == null || intent.getAction() == null  || !SMSCODE.contentEquals(intent.getAction()))
            return; 
        
        //长短信会被分拆送来,因此可能会有多个PDU。Protocol Description Unit是短信的工业标准。 
        Object[] pduArray = (Object[])intent.getExtras().get("pdus");
        
        Log.i("WEI","pdu number=" + pduArray.length);
        String message = "";
        String from = null;

        for(int i = 0 ; i < pduArray.length; i ++){
            SmsMessage sms = SmsMessage.createFromPdu((byte[])pduArray[i]);
 //将PDU转换为SmsMessage格式
            Log.i("WEi","From : " + sms.getDisplayOriginatingAddress()); 
            Log.i("WEI","SMS : " + sms.getDisplayMessageBody());

            from = sms.getDisplayOriginatingAddress();
            message += sms.getDisplayMessageBody();
        }
       
        Toast.makeText(context, "收到" + from + "短信:" + message, Toast.LENGTH_LONG).show();         
    } 
}    

短信广播接收器的注册和注销

广播器有两种方式可以向系统登记,在AndroidManifest.xml中进行说明,例如:

<receiver android:name="cn.wei.flowingflying.testtelephony.RecvSMSMonitor"> 
         <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/> 
         </intent-filter> 
</receiver>

但是对于短信监听,更多的情况是只在应用的某个阶段,例如某个activity开启,有或者用户进行了某些操作之后才进行监听,不是一直监听。这种情况应该在代码中进行接收器的注册和注销。代码片段如下:

private RecvSMSMonitor monitor = null;

private void registerMonitor(){ 
    if(monitor == null){
        monitor = new RecvSMSMonitor();
        IntentFilter interFilter = new IntentFilter();
        interFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
        interFilter.setPriority(1000);
        registerReceiver(monitor, interFilter);   
    }
}

private void unregisterMonitor(){
    if(monitor != null){
        unregisterReceiver(monitor);
        monitor = null;
    }
}

在模拟器中,我们可以通过DDMS进行来模拟接收短信,小例子的运行情况如下:

Pro Android学习笔记(一二十):Telephony API(2):接收短信_第1张图片

Priority的问题

注意到在receiver的声明中有一个priority,这是何用?Android的广播有两种,sendBroadcast()和sendOrderedBroadcast(),后者是有序广播,会根据优先级别,依次传递给接收者。对于有序广播,要确保接受的优先级别,为自己设置一个高优先级别。先接收到的接收器,可以通过abortBroadcast(),禁止广播继续传递。

我们在receiver中增加了abortBroadcast()的语句,并将优先级别设置为最高的Integer.MAX_VALUE,在Android4.2的模拟器和Android4.4的模拟器的测试结果又所不同。在4.2的模拟器中,应用拦截的短信,不会出现在系统短信应用中,而在4.4的模拟器中,短信仍会出现在系统短信应用中。这只能说明在4.2版本及之前,短信是通过有序广播发送出去的。

翻看4.2的源代码com.android.internal.telephony.SMSDispatcher,我们看到了下面的源码

/**
* Grabs a wake lock and sends intent as an ordered broadcast.
* Used for setting a custom result receiver for CDMA SCPD.
*
* @param intent intent to broadcast
* @param permission Receivers are required to have this permission
* @param resultReceiver the result receiver to use
*/
public void dispatch(Intent intent, String permission, BroadcastReceiver resultReceiver) {
    // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
    // receivers time to take their own wake locks.
    mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
    mContext.sendOrderedBroadcast(intent, permission, resultReceiver,
            this, Activity.RESULT_OK, null, null);
}

但是在4.4的源码中,SMSDispatcher出现了很大的改写,我们没有看到相关的代码,所以我们不能确定4.4的广播机制,猜测仍是有序广播,因为4.4不允许abort广播,在Android Developers Blog中提到:

Beginning with Android 4.4,you should stop listening for the SMS_RECEIVED_ACTION broadcast, which you can do at runtime by checking the platform version then disabling your broadcast receiver for SMS_RECEIVED_ACTION with PackageManager.setComponentEnabledSetting().However, you can continue listening for that broadcast if your app needs only to read special SMS messages, such as to perform phone number verification. Note that—beginning with Android 4.4—any attempt by your app to abort the SMS_RECEIVED_ACTION broadcast will be ignored so all apps interested have the chance to receive it.

实体机运行的问题

在Android4.4之前,短信接收采用有序广播实际上带来很严重的安全隐患,应用可以拦截短信,而用户毫不知情。OEM通常在封装OS的时候会对此漏洞进行封堵。因此,我们在实体机上运行小例子,4.4之前不能监听到短信的概率会更高。我在两条手机上测试过,在4.4的手机上,可以监听成功,而在4.1.2的机器上无效。我们可以看看有哪些接收器在监听短信。

private void checkReceiver(){
    List<ResolveInfo> receivers = getPackageManager().queryBroadcastReceivers(  
                           new Intent("android.provider.Telephony.SMS_RECEIVED"),
                           0);
    for(int i = 0 ; i < receivers.size();i ++){
        ResolveInfo info = receivers.get(i); 
        Log.i("WEI", "No." + i + "--------------------");
        Log.i("WEI", "" + info.toString() + " 优先级" + info.priority);
    }
}

如果应用采用动态注册接收器方式,不会在列表中,当如果在Androidmanifest.xml中声明,则会出现在该列表中。运行的结果如下,有12个接收器在监听短信:

Pro Android学习笔记(一二十):Telephony API(2):接收短信_第2张图片

你认为你还有个人隐私吗?只是大家都在裸泳,你不那么显眼罢了。而且很多都设置了最该的优先级别2147483647(Integer.MAX_VAULE)。如果优先级别相同,是根据注册(或AndroidManifest.xml的声明先后顺序)时间的先后来确定顺序,我们的小程序不可能比系统程序先注册,早就被半道截胡了。截胡者可能性最大的就是系统的短信。我们可以在短信设置中,将系统优先的设置取消。在我的4.1.2的设备中,取消设置后,可以正常监听短信。

Pro Android学习笔记(一二十):Telephony API(2):接收短信_第3张图片

问题在于,我们不可能要求用户进行设置,那么在4.4之前的版本,我们如何确保应用能够获得短信的内容。有很多应用,例如微信,有自动获取短信验证码的功能。我们看看它的权限要求:

Pro Android学习笔记(一二十):Telephony API(2):接收短信_第4张图片

答案很清楚,用读短信来解决。监听短信数据库的变化,当收到短信数据库变化了的时候,去取得最新的那条短信即可,我们在下一篇笔记中介绍。

小例子代码在:Pro Android学习:telephony小例子

相关链接:我的Android开发相关文章

你可能感兴趣的:(Pro Android学习笔记(一二十):Telephony API(2):接收短信)