《Android深入透析》之广播(Broadcast)
1. 概述
在android中,Broadcast作为四大组件之一,被广泛的应用在android程序之间的数据传递。举一个大家都比较熟悉的例子来说明。在车上的时候大家都有收听广播的习惯,广播电台通过发送不同频率的信号,然后大家通过将各自频率调成和电台相同的频率,就可以接受到广播内容了。在android中的广播其实是和这个是一样的效果的。
2. 广播机制
android中广播机制体现在方方面面,各种广播在android系统中运行,当系统、应用程序运行时便会向android注册各种广播,例如当开机完成后,系统会发送一条广播,接受到这条广播就能及时的作出提示和保存数据的动作;当你安装卸载应用程序的时候系统都会发送广播,来监听这条广播,就可以作出相应的动作,同样的广播还有很多,比如电量多少,短信,来电等等。
3. 发送和接收广播
3.1 广播定义
关于广播的定义,分为两种,动态注册和静态注册。
动态注册的广播接收器永远要快于静态注册的广播接收器,不管静态注册的广播接收器的优先级是否高于动态注册的广播接收器的优先级。
动态注册的广播不是常驻型的,他是跟随着activity的生命周期的。一般在开发中都会在onDestroy()方法中移除广播。静态注册的广播是常驻型的广播,也就是说当应用程序关闭的时候,如果有信息广播来,程序也会被系统调用。在同一优先级下,谁启动的快,谁将先收到广播。
3.2 静态广播
静态广播指的就是在AndroidManifest.xml配置文件中定义,
<receiver android:name=".DemoReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.broadcast1"/> </intent-filter> </receiver>
android:name=".DemoReceiver"指的是静态广播的类名称,
android:exported="false"设置的属性,如果为true的话则可以允许被其他应用接受,如果是false话,只能被本应用内的接收器接收
<action android:name="com.example.broadcast1"/>指的是定义的广播的动作标准,就相当于广播的频率,只有当接收器指定相同的动作标准的时候,才可以接收到发送的广播内容
3.3 动态广播
动态广播需要通过代码的方式注册,注册广播接收器需要registerReceiver方法,注销广播接收器需要使用unregisterReceiver方法:
注册:
IntentFilter filter = new IntentFilter(); filter.addAction("com.example.broadcast2"); registerReceiver(broadcastReceiver, filter);
解除:
unregisterReceiver(broadcastReceiver);
其中broadcastReceiver 为广播接收器对象
3.4 广播接收器
3.4.1 发送广播
在说广播接收器之前,首先说一下是怎么发送广播的。无论是动态注册的还是静态注册的广播接收器,他的发送方法都是通过sendBroadcast方法来实现的,例如下代码:
Intent intent = new Intent(); intent.setAction("com.example.broadcast2"); intent.putExtra("name", "动态注册广播"); sendBroadcast(intent);
sendBroadcast方法中的intent指定广播接收器的动作,将要传递给广播接收器的内容放入到intent中
3.4.2广播接收
不管是动态广播还是静态广播,如果想接收到广播,都需要定义广播接收器,例如如下代码:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String name = intent.getExtras().getString("name"); Toast.makeText(mContext, name, Toast.LENGTH_SHORT).show(); } };
在广播接收器中最重要的就是这个onReceive方法,其中在onReceive的intent参数中可以携带发送广播时所发送出来的参数,同这种intent.getExtras().getString("name")方法,通过intent的可以传递各种类型的参数,包括对象.
3.4.3优先级
在注册广播的时候,可以设定广播的优先级,设置的数越大的越会在前面执行,这里指的是同一级的广播,比如几个静态注册的广播或者动态注册的广播,例如如下代码,
<receiver android:name=".DemoReceiver1" android:exported="false"> <intent-filter android:priority="500"> <action android:name="com.example.broadcast1"/> </intent-filter> </receiver> <receiver android:name=".DemoReceiver2" android:exported="false"> <intent-filter android:priority="1000"> <action android:name="com.example.broadcast1"/> </intent-filter> </receiver>
上面有两个静态注册的广播,第一个的优先级设置的500,第二个的优先级设置的1000,这样设置优先级为1000的就会先执行,然后才会在优先级为500的接收器里收到信息,如果在优先级为1000的接收器中设置abortBroadcast() 方法,那么在同级别的优先级里广播将被终止,不在往下传递
4. 系统广播
广播接收器有一个很大的用途就是接收系统广播,根据系统广播可以做很多事情,下面就为大家介绍几种在开发过程中常见的系统广播
4.1开机启动
在介绍开机启动广播之前,要先跟大家解释一个问题,从android3.0系统之后,所有的android系统的机器在安装完APP之后,必须手动启动一次之后才可以接收到开机启动的广播,不过google官方还是给出了解释,如果你可以将自己的APP变成系统权限的话,那么就可以再任何时候接到到系统的开机启动广播的。那么如何将APP如何获取系统权限哪,简单的讲就是把app放入到system/app目录下,如下图:
将app放入到system/app目录下,系统会将该应用当做系统应用,获取系统的最高权限,并且在未启动过应用的前提下,就可以获取到任何的系统广播。以下是接受到系统开机的广播:
@Override public void onReceive(Context context, Intent intent) { Intent intent = new Intent(context,MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }
◊注意:
在广播接收器中通过intent打开activity,必须要设置intent的flags为Intent.FLAG_ACTIVITY_NEW_TASK。否则就会抛出android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity的异常,请读者切记
需要在订单文件中定义静态接收器:
<receiver android:name=".BootReceiver" android:enabled="true"> <intent-filter> <!-- 开机启动 --> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
在系统权限中需要加入下面的权限:
<!-- 开机自启动权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 4.2网络状态变更
在开发的过程中都会遇到关于网络的切换,比如wifi切换到3G,3G切换到无网络,那么我们如何来得之当前的网络状态哪,下面通过一段代码来给大家说明一下:
public class BootReceiver extends BroadcastReceiver{ private ConnectivityManager connectivityManager; private NetworkInfo info; @Override public void onReceive(Context context, Intent intent) { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { Log.d("mark", "网络状态已经改变"); connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); info = connectivityManager.getActiveNetworkInfo(); if(info != null && info.isAvailable()) { String name = info.getTypeName(); Log.d("mark", "当前网络名称:" + name); } else { Log.d("mark", "没有可用网络"); } } } }
ConnectivityManager.CONNECTIVITY_ACTION当网络状态发生变化的时候,intent中的action就会收到当前状态的监听,然后获取到ConnectivityManager对象,从ConnectivityManager对象中获取NetworkInfo对象,NetworkInfo中可以获取到当前的网络状态是什么形式
public class BootReceiver extends BroadcastReceiver{ private ConnectivityManager connectivityManager; private NetworkInfo info; @Override public void onReceive(Context context, Intent intent) { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { Log.d("mark", "网络状态已经改变"); connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); info = connectivityManager.getActiveNetworkInfo(); if(info != null && info.isAvailable()) { String name = info.getTypeName(); Log.d("mark", "当前网络名称:" + name); } else { Log.d("mark", "没有可用网络"); } } } }
需要在清单文件中添加权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
4.3电量监听
如何才能知道现在手机的电量是多少了哪?怎么才能避免突然没电的尴尬?我们可以通过一个系统的广播来查看电池的电量,下面我们将通过一个动态注册的方式来给大家写一个例子:
private BroadcastReceiver mBatteryChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { //level表示当前电量的值 int level = intent.getIntExtra("level", 0); //scale表示电量的总刻度 int scale = intent.getIntExtra("scale", 100); //将当前的电量换算成百分比的形式 tvBattery.setText("电池用量:"+(level*100/scale)+"%"); } } };
通过当前电量和总电量的计算,可以得到目前手机电池剩余电量的一个百分比
下面是对定义的监听器的注册:
private OnClickListener registerClickListener = new OnClickListener() { @Override public void onClick(View v) { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); registerReceiver(mBatteryChangeReceiver, filter); } };
最后当不再使用的时候,记得在onDestroy()方法中执行解除监听的方法
4.4短信拦截
短信拦截广播,顾名思义,就是当手机收到短信的时候可以拦截电话号码和短信内容,看下面的代码实例:
public class ShortMessageReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { //通过广播的方式获取短信的内容 Bundle bundle = intent.getExtras(); if (bundle!=null) { Set<String> keys = bundle.keySet(); //查看广播中包含的数据项 for (String key:keys) { Log.i("广播中的数据项:", key); } //短信数据的获取都用通过一个叫做pdus的key获取 Object[] arrayObjects = (Object[])bundle.get("pdus"); //短信的数据内容都是封装在SmsMessage对象中 SmsMessage[] msg = new SmsMessage[arrayObjects.length]; //循环处理收到的所有短信 for (int i = 0; i < arrayObjects.length; i++) { //将每条短信数据赋值给SmsMessage对象 msg[i] = SmsMessage.createFromPdu((byte[])arrayObjects[i]); //获得发送短信的电话号码和短信内容 String s = "手机号:"+msg[i].getOriginatingAddress()+"\n"; s+= "短信内容:"+msg[i].getDisplayMessageBody(); //通过土司的方式把电话号码和内容显示出来 Toast.makeText(context, s, Toast.LENGTH_LONG).show(); } abortBroadcast(); } } }
◊ 通过intent获取短信内容项,短信的内容项存储在Bundle中
◊ Bundle中存储了一个set集合,可以通过Log的方式循环查看短信中的每一个数据项是什么
◊ 收到的短信内容是以字节数组的形式保存的,为了便于使用这些数据,所以要将这些字节数组通过
SmsMessage.createFromPdu((byte[])arrayObjects[i]);的方法转换成SmsMessage对象。
◊ 为什么会用SmsMessage数组的形式存储短信对象哪,因为短信息本身是有字数限制的,一般都是70个汉字左右,如果发送的短信字数过多,运营商就会把你发送的短信进行拆分来发送,比如说你发送了一天100个汉字的短信,运营商就会把短信拆分成1条70字和一条30字的短信,为了保证收到的短信的完整性,所以就需要以SmsMessage数组的形式来存储接受短信内容。
广播接收器需要在AndroidManifest.xml注册,代码如下:
<receiver android:name=".ShortMessageReceiver" android:enabled="true"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
其中android:name="android.provider.Telephony.SMS_RECEIVED"是接收到短信的广播动作,必须指定该动作,否则ShortMessageReceiver接收不到系统的短信广播
由于android的安全机制,必须在AndroidManifest.xml中指定权限,程序才能正常的接收短信广播。
<uses-permission android:name="android.permission.RECEIVE_SMS" />
- 5. 小结
本章节主要讲解了android中的广播接收器,介绍到这里相信大家都对广播就了一个大概的了解,广播在android开发中应用很广泛,也很方便,包括他可以传递数据给全局的各个类或者接收到的一些系统的广播之类的机制。广播相当于一个全局的事件,不管是否有接收器他都会根据条件去发送,去触发,所以在性能方面来讲,如果大量使用的话,可能会对应用造成性能损耗,所以大家在使用的时候,要经过深思熟虑之后在动手