Android的BroadcastReceiver 广播 短信拦截

时间:2021-08-20 08:24:06

    如何去理解BroadcastReceiver(广播)?其实可以这样想,首先我们要有一个发送广播的“媒体”,在这个例子中,我们暂且用activity组件作为这个媒体,当然以后会用到service,或者随机启动方式来发送广播,这看业务需求来决定。在这个例子中,当点击按钮的时候,一条广播就发送了出去,同样用到了意图对象Intent。和启动activity和service一样,我们需要为意图对象设置“标记”和“包裹”,它就像个基站,向世界发送信号。而对于广播自身而言,他就像一个监听器,一个系统级别的监听器,当然他也有他所监听的内容,不是什么都监听的,那么如何确定广播到底在监听什么呢?

    上面提到,意图这个基站被激活后向外发送信号,这个信号是带着“标记(action)”的,比如,基站发送了一条标记为 setAction("wuchen.broadcastReceiver.MY_RECEIVER")的信号,而在世界中监听的广播君,如果想要监听到这条信号,就需要让世界知道自己的存在,并且告诉世界自己要监听的信号的标记是什么,都要记录在案。这就需要在Manifest清单文件中进行注册:

    <receiver android:name=".MyBroadcastReceiver" >
<intent-filter>
<action android:name="wuchen.broadcastReceiver.MY_RECEIVER" />
</intent-filter>
</receiver>

    一但注册完成功后,每当有基站发送的信号同广播君向世界注册的标记匹配时,广播君就会启动并截获信息。其实广播接听的这个过程,上面的比喻需要调整一下,更严谨的说法是广播并不是随时都保持在一个监听的状态,而是在接收到Intent发出的广播和自己本身注册的信息匹配时,才会被触发,而且由于自身属于系统级别监听和程序级别监听不同的是:程序级别监听在程序终止后就没有监听作用了,而系统级别监听在被激活后会有自己的进程,比如当一个程序打开,并激活了一个系统级别监听,当这个程序关闭后,系统级别监听并不会随着这个激活他的程序的关闭而停止处理业务的作用,还会继续运行,除非他自己本身处理完业务需求后生命周期结束。

    严格上讲,系统级别监听的broadcastReceiver激活后也只能最多运行10秒钟,如果10秒内还没有处理完业务,Android会认为该程序无响应,并弹出ANR(application No Response)对话框。对于这种”限制“,我们处理这种限制的方式,也是项目中用到的最多一种方法:启动service。最可靠的方式是在广播中启动一个service,让service在后台去处理数据,这样就算广播生命周期结束,service也不会被销毁,因为用startService这种方式启动的service,就算访问者(广播)销毁自己也不会销毁,而且本来在广播中处理数据的耗时操作,交给了service去做,同时避免了ANR问题的出现。用启动新线程的方式也不可靠,广播的生命周期,就是内部onReceive方法的周期,当onReceiver方法结束,也就意味着广播的结束。

    虽然我们可以在onReceiver内执行启动一个新线程执行耗时的业务,由于onReceiver方法周期很短暂,可能出现的情况是:子线程还没有结束广播就关闭了,广播一旦关闭,虽然子线程还可以运行,但系统会认为广播处于无组件活动的状态,在内存紧张的时候优先结束该进程,这样可能会导致子线程不能执行完任务。所以最好的方式是启动service。

    需要注意的是,世界中接收同一个标记信号的广播君不局限只能存在一个,也许广播君A和广播君B监听的是同一个信号这是决不冲突的。但究竟是广播君A先接收到信号还是广播君B先接收到,这就涉及到了有序广播,和有序广播相对应的是普通广播。

    普通广播:普通广播可以让所有的广播君一起接收到信号(逻辑上),如果有10个广播君,那么是个都可以接收到,无法停止信号的传播。

    有序广播:有序广播传播的方式如同传递,比如有10个广播君,A传到B,B再到C,以此类推,如果传到D后,D想终止信号的传播,那么D以后的广播君就接收不到信号了。虽然这10个广播君都在等待接收同一个信号,但在D这里,就有权利终止信号的传输。 有序广播还有一个特点,广播君可以将自己的内容传给下一位唯一需要确定的是,到底是广播君A先接收到还是广播君B先接收到,这就需要在广播君们在注册时,考虑到”优先级“的问题了。在配置文件的每个receiver中的intent-filter标签中添加一个属性android:priority="100",数字越高优先级越大。

    <receiver android:name=".MyBroadcastReceiver" >
<intent-filter android:priority="11" >
<action android:name="wuchen.broadcastReceiver.MY_RECEIVER" />
</intent-filter>
</receiver> <receiver android:name=".MyBroadcastReceiver_B" >
<intent-filter android:priority="10" >
<action android:name="wuchen.broadcastReceiver.MY_RECEIVER" />
</intent-filter>
</receiver>

普通广播(Normal Broadcast):

一,优缺点:和有序广播的优缺点相反!

二,发送广播的方法:sendBroadcast()

 

有序广播(Ordered Broadcast):

一,优缺点

优点:1,按优先级的不同,优先Receiver可对数据进行处理,并传给下一个Receiver

             2,通过abortBroadcast可终止广播的传播  

缺点:效率低  

二,发送广播的方法:sendOrderedBroadcast()   

三,优先接收到Broadcast的Receiver可通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,下一个Receiver 通过 Bundle bundle=getResultExtras(true)方法获取上一个 Receiver传来的数据     

上面讲了很多关于广播的很多基础概念,那么BroadcastReceiver在实际的应用中到底会起到样的作用呢?

因为广播是一个系统级别的监听器,所以它可以将不同进程间通信的一个“纽带”。举个简单的例子,我们知道在我们启动service是如果用startService这种方式启动时,访问者和service之间是不能进行数据的“互动”的,但是有了BroadcastReceiver就一样可以进行互动,方法我大致说一下,在activity端创建一个broadcast,同样在service端也创建一个broadcast,相互监听对方的动作,这样就可以达到进程间通信的目的了。

短信拦截

BroadcastReceiver还可以监听系统进程,比如android的收短信,电量低,电量改变,系统启动,等……只要BroadcastReceiver监听了这些进程,就可以实现很多有趣的功能,比如,接收短信的这条广播是一条有序广播,所以我们可以监听这条信号,在传递给真正的接收程序时,我们将自定义的广播接收程序的优先级大于它,并且取消广播的传播,这样就可以实现拦截短信的功能了。

我们只需要一个BroadcastReceiver类,就可以实现这个功能,不需要activity,也不需要布局文件。当然如果需要更加的商业化,需要改进的地方还有很多。比如我们可以做一个界面让用户从联系人中指定一个号码进行短信拦截,也可以自定义。

public class SmsReceiver extends BroadcastReceiver {
// 当接收到短信时被触发
@Override
public void onReceive(Context context, Intent intent) {
// 如果是接收到短信
if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
// 取消广播(这行代码将会让系统收不到短信)
abortBroadcast();
StringBuilder sb = new StringBuilder();
// 接收由SMS传过来的数据
Bundle bundle = intent.getExtras();
// 判断是否有数据
if (bundle != null) {
// 通过pdus可以获得接收到的所有短信消息
Object[] pdus = (Object[]) bundle.get("pdus");
// 构建短信对象array,并依据收到的对象长度来创建array的大小
SmsMessage[] messages = new SmsMessage[pdus.length];
for (int i = 0; i < pdus.length; i++) {
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
}
// 将送来的短信合并自定义信息于StringBuilder当中
for (SmsMessage message : messages) {
sb.append("短信来源:");
// 获得接收短信的电话号码
sb.append(message.getDisplayOriginatingAddress());
sb.append("\n------短信内容------\n");
// 获得短信的内容
sb.append(message.getDisplayMessageBody());
}
}
Toast.makeText(context, sb.toString(), 5000).show();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wuchen.broadcastReceiver"
android:versionCode="1"
android:versionName="1.0" > <application
android:icon="@drawable/icon"
android:label="@string/app_name" > <receiver android:name=".SmsReceiver" > <intent-filter android:priority="800" > <action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
<!-- 授予程序接收短信的权限 --> <uses-permission android:name="android.permission.RECEIVE_SMS" /> </manifest>