什么是广播
广播是Android SDK的四大组件中唯一需要别动接收数据的组件。也就是说对于Activity、ContentProvider和Service都可以主动调用,并获取返回数据。而负责接收Broadcast数据的接收器却永远不知道什么时候可以接收到广播。从这种表现形式上看,很像面向对象中的事件(Event),对于事件(onClick、onKeydown)来说,从来不会预知用户什么时候触发他们,只能默默的等待不可预知的事件发生。因此,广播也可以被成为全局事件。
接收系统广播
短信拦截(静态注册)
1 编写广播接收器类,继承自android.content.BroadcastReceiver类
ShortMessageReceiver.java
package com.turing.base.activity.broadcastDemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.telephony.SmsMessage; import android.widget.Toast; import com.apkfuns.logutils.LogUtils; import java.util.Set; public class ShortMessageReceiver extends BroadcastReceiver { private Handler handler ; public ShortMessageReceiver() { } public ShortMessageReceiver(Handler handler) { this.handler = handler ; } @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); if (bundle != null) { Set<String> keys = bundle.keySet(); //查看收的广播包含哪些数据 for (String key : keys) { LogUtils.e("bundele中的数据" + key); } // 获取收到的短信 Object[] objArray = (Object[]) bundle.get("pdus"); String message = parseMessageFromRawData(objArray); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); Message msg = new Message(); msg.what = 1 ; msg.obj = message; handler.sendMessage(msg); /** // 定义封装短信内容的SmsMessage对象数组 SmsMessage[] message = new SmsMessage[objArray.length]; // 循环处理收到的所有短信 for (int i = 0; i < objArray.length; i++) { // 将每条短信数据转换成SendMessage对象 message[i] = SmsMessage.createFromPdu((byte[]) objArray[i]); // 获取发送短信的电话号码和短信内容 String messageInfo = "手机号:" + message[i].getOriginatingAddress() + "\n"; messageInfo += "短信内容:" + message[i].getDisplayMessageBody(); //做个简单的展示 Toast.makeText(context, messageInfo, Toast.LENGTH_SHORT).show(); } **/ } } public String parseMessageFromRawData(Object[] pdus) { if (pdus == null) return null; try { StringBuilder message = new StringBuilder(); for (Object pdu : pdus) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdu); if (smsMessage == null) continue; message.append("源号码:" + smsMessage.getOriginatingAddress() + ",内容:" +smsMessage.getDisplayMessageBody()); } return message.toString(); } catch (Exception e) { LogUtils.e( "SMSBroadcastReceiver read sms failed", e); } catch (OutOfMemoryError oom) { LogUtils.e( "SMSBroadcastReceiver caused OOM =_=!", oom); //为了避免后续操作出现问题,gc一下 System.gc(); System.gc(); } return null; } }
清单文件配置Receiver
<receiver android:name=".activity.broadcastDemo.ShortMessageReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
清单文件配置权限
<uses-permission android:name="android.permission.RECEIVE_SMS" />
注意事项
- 如果不知道广播中包含哪些数据,可以从Bundle.keySet()方法中获取这些数据的key,将其输出到Logcat中查看,如上述代码所示
- 由于接受的短信内容是以字节数组的形式保存的,为了方便使用这些数据,需要使用SmsMessage.createFromPdu方法将这些字节数据组成的数据转换为SmsMessage对象
- SmsMessage建议使用android.telephony.SmsMessage中的。
- 由于接收器可能接收多条短信,因此通过pdus返回了一个短信数组。
- 必须要指定<action android:name="android.provider.Telephony.SMS_RECEIVED" /> 我们编写的短信接收器才可以接收系统的短信广播,切记
- 配置权限android.permission.RECEIVE_SMS
- 即使注册广播接收器的程序关闭,接收器仍然会接收到广播,除非从模拟器或者手机中卸载程序或者注销接收器,否则无法阻止接收器接收广播
用代码注册广播接收器
如果在清单文件中配置广播接收器,程序安装后就会自动注册广播接收器,如果想在适当的时候注册广播接收器,在使用完成之后将其注销就需要使用Java代码来操作了。
注册和取消方法
注册广播接收器的方法是 registerReceiver,注销的方法是unregisterReceiver,定义如下:
public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) public void unregisterReceiver(BroadcastReceiver receiver)
其中receiver表示广播接收器对象,
filter参数相当于设置intent-filter标签中的内容。
Code
package com.turing.base.activity.broadcastDemo; import android.app.Activity; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.turing.base.R; public class SmsMessageAct extends Activity implements View.OnClickListener { private EditText et_phone, et_msg; private Button btn_registerReceiver, btn_unRegisterReceiver; private ShortMessageReceiver shortMessageReceiver; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: String message = (String) msg.obj; String pre = "源号码:"; String suf = ",内容:"; et_phone.setText(message.substring((message.indexOf(pre) + pre.length()), message.indexOf(suf))); et_msg.setText(message.substring(message.indexOf(suf) + suf.length(), message.length())); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sms_message); initView(); initEvents(); // 创建广播接收器的对象 shortMessageReceiver = new ShortMessageReceiver(handler); } private void initEvents() { btn_registerReceiver.setOnClickListener(this); btn_unRegisterReceiver.setOnClickListener(this); } private void initView() { et_phone = (EditText) findViewById(R.id.id_et_phone); et_msg = (EditText) findViewById(R.id.id_et_msg); btn_registerReceiver = (Button) findViewById(R.id.id_btn_registerReceiver); btn_unRegisterReceiver = (Button) findViewById(R.id.id_btn_unRegisterReceiver); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.id_btn_registerReceiver: registerReceiver(shortMessageReceiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED")); Toast.makeText(this, "动态注册广播接收器成功", Toast.LENGTH_SHORT).show(); break; case R.id.id_btn_unRegisterReceiver: unregisterReceiver(shortMessageReceiver); Toast.makeText(this, "动态注销短信广播接收器over", Toast.LENGTH_SHORT).show(); break; default: break; } } @Override protected void onPause() { super.onPause(); if (shortMessageReceiver != null) { unregisterReceiver(shortMessageReceiver); Toast.makeText(this, "Activity onPause ,注销短信广播接收器over", Toast.LENGTH_SHORT).show(); } } }
广播的优先级
android:priority
通过intent-filter标签的android:priority属性可以设置接收器的调用优先级,该属性值属于一个整数,数值越大,优先级越高。
Code
<receiver android:name=".activity.service.StartupReceiver"> <intent-filter android:priority="100"> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
如果不设置优先级别,对于同一个应用程序中的广播接收器会按照在Manifest清单文件中定义的顺序调用。
广播的优先级只是对同步处理方式起作用,如果在接收器中使用了异步处理方式,则调用的顺序除了和优先级有关,还和Android系统的线程调用有关。
来去电拦截
广播动作
监听电话状态以用于拦截来去电,来电(监听电话状态)和去电的广播动作如下:
- 来电:android.intent.action.PHONE_STATE
- 去电:android.intent.action.NEW_OUTGOING_CALL
来电可以分解为3个状态:未接电话时的响铃,接听电话 和挂断电话(可能是对方挂断,也可能是自己挂断)
监听这三个状态的代码如下(使用静态方式注册的广播):
CallInReceiver:
package com.turing.base.activity.broadcastDemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.PopupWindow; import android.widget.TextView; import android.widget.Toast; import com.turing.base.R; public class CallInReceiver extends BroadcastReceiver { private static Object obj; public CallInReceiver() { } @Override public void onReceive(Context context, Intent intent) { // 获取电话管理服务,以便获取电话的状态 TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); switch (telephonyManager.getCallState()) { case TelephonyManager.CALL_STATE_RINGING: // 响铃 String incomingNumber = intent.getStringExtra("incoming_number"); Toast.makeText(context, "电话响铃中......", Toast.LENGTH_SHORT).show(); //showPopupWindowToast(context,incomingNumber); break; case TelephonyManager.CALL_STATE_OFFHOOK: //接听电话 Toast.makeText(context, "电话已接通......", Toast.LENGTH_SHORT).show(); break; case TelephonyManager.CALL_STATE_IDLE:// 挂断电话 Toast.makeText(context, "挂断电话......", Toast.LENGTH_SHORT).show(); //closeToast(); default: break; } } /** * 使用反射,此Toast不会关闭 * * @param context * @param msg */ // public static void showToast(Context context, String msg) { // Toast toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); // toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0); // try { // Field field = toast.getClass().getDeclaredField("mTN"); // field.setAccessible(true); // obj = field.get(toast); // Method method = obj.getClass().getDeclaredMethod("show", null); // method.invoke(obj, null); // } catch (Exception e) { // } // // } /** * 通过此方法关闭那个不可关闭的Toast */ // public static void closeToast() { // if (obj != null) { // try { // Method method = obj.getClass().getDeclaredMethod("hide", null); // method.invoke(obj, null); // } catch (Exception e) { // } // // } // } public static void showPopupWindowToast(Context context, String incomingNumber) { LayoutInflater inflater = LayoutInflater.from(context); View view = inflater.inflate(R.layout.activity_popupwd_toast, null); TextView textView = (TextView)view.findViewById(R.id.tvMsg); textView.setText("电话号码:" + incomingNumber); final PopupWindow popupWindow = new PopupWindow(view,500 ,100); popupWindow.setTouchable(false); popupWindow.showAtLocation(view, Gravity.CENTER_HORIZONTAL,20 ,0); // 设置定时器,5秒后自动关闭 android.os.Handler handler = new android.os.Handler(); handler.postDelayed(new Runnable() { @Override public void run() { popupWindow.dismiss(); } } , 5*1000); } }
CallOutReceiver
package com.turing.base.activity.broadcastDemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class CallOutReceiver extends BroadcastReceiver { public CallOutReceiver() { } @Override public void onReceive(Context context, Intent intent) { // 获取去电号码 String outComingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); // showToast Toast.makeText(context, "去电......" + outComingNumber, Toast.LENGTH_SHORT).show(); //CallInReceiver.showPopupWindowToast(context, outComingNumber); } }
<!-- 来电 --> <receiver android:name=".activity.broadcastDemo.CallInReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> </receiver> <!-- 去电 --> <receiver android:name=".activity.broadcastDemo.CallOutReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> </intent-filter> </receiver>
设置权限
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
截获屏幕休眠与唤醒
按手机上的挂断按钮后,手机会进入休眠状态(屏幕变黑),当再此按下手机的任意键后,屏幕会唤醒(屏幕变量)。这两个动作可以通过如下两个动作连接
广播动作
- 休眠状态 Intent.ACTION_SCREEN_OFF
- 唤醒状态 Intent.ACTION_SCREEN_ON
private void screenOnOff() { ScreenOnOffReceiver screenOnOffReceiver = new ScreenOnOffReceiver(); IntentFilter intentFilter = new IntentFilter(); // 设置屏幕唤醒广播的动作 intentFilter.addAction(Intent.ACTION_SCREEN_ON); // 设置屏幕休眠广播的动作 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); // 注册 registerReceiver(screenOnOffReceiver,intentFilter); }
package com.turing.base.activity.broadcastDemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class ScreenOnOffReceiver extends BroadcastReceiver { public ScreenOnOffReceiver() { } @Override public void onReceive(Context context, Intent intent) { // 接收屏幕唤醒状态的广播 if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { Log.d("screen", "ok"); } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { Log.d("screen", "off"); } } }
注意事项:
屏幕唤醒和休眠的广播,只能通过代码的以动态的方式注册,如果在清单文件中配置,则不起作用。
开机自动运行
广播动作
android.intent.action.BOOT_COMPLETED
Code
package com.turing.base.activity.service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.apkfuns.logutils.LogUtils; /** * 只要完成两项工作: 启动服务 和 显示一个Activity提示服务启动成功(主题设置为Dialog的形式) */ public class StartupReceiver extends BroadcastReceiver { public StartupReceiver() { } @Override public void onReceive(Context context, Intent intent) { LogUtils.e("StartupReceiver onReceive"); // 如果是开机启动的Action if(intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){ // 启动Activity Intent activityIntent = new Intent(context,BootCompletedMessageAct.class); // 想要在Service中启动Activity,必须设置如下标志 activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activityIntent); //启动服务 Intent serviceIntent = new Intent(context,StartupService.class); context.startService(serviceIntent); } } }
<!-- 开机广播 --> <receiver android:name=".activity.service.StartupReceiver"> <intent-filter > <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
显示手机电池的当前电量
查看电池的电量也需要接收一个系统广播,本demo是通过registerReceiver方法进行注册的。
广播动作
Intent.ACTION_BATTERY_CHANGED
Code
package com.turing.base.activity.broadcastDemo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import com.turing.base.R; public class BatteryInfoAct extends AppCompatActivity { private TextView tv_batteryInfo; private BroadcastReceiver batteryChangedReceiver = 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); // 将当前电量换算成百分比的形式 tv_batteryInfo.setText("电池用量:" + (level * 100 / scale) + "%"); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_battery_info); tv_batteryInfo = (TextView) findViewById(R.id.id_tv_battery); // 注册广播 registerReceiver(batteryChangedReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } @Override protected void onPause() { super.onPause(); if (batteryChangedReceiver != null) { unregisterReceiver(batteryChangedReceiver); } } }
发送广播
sendBoradcast
可以通过sendBoradcast的方式发送广播
方法定义如下:
public void sendBroadcast(Intent intent)
下面的代码发送了一个广播,并添加了广播数据和category
// 指定广播动作 Intent brdcstIntent= new Intent("com.turing.demo.sendbrdcst.MYBROADCAST"); // 添加category brdcstIntent.addCategory("xxx.xxx.xxx"); // 设置广播数据 brdcstIntent.putExtra("name","XXXXX"); // 发送广播 sendBoradcast(brdcstIntent);
Code
两个工程,proj_send_broadcast, proj_custom_receiver
proj_send_broadcast
public class Main extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onClick_Send_Broadcast(View view) { Intent broadcastIntent = new Intent("mobile.android.ch10.MYBROADCAST"); broadcastIntent.addCategory("mobile.android.ch10.mycategory"); broadcastIntent.putExtra("name", "broadcast_data"); sendBroadcast(broadcastIntent); Toast.makeText(this, "广播发送成功.", Toast.LENGTH_LONG).show(); } }
proj_custom_receiver
CustomReceiver
import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class CustomReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if ("mobile.android.ch10.MYBROADCAST".equals(intent.getAction())) { String name = intent.getStringExtra("name"); Toast.makeText(context, name, Toast.LENGTH_LONG).show(); } } }
清单文件
<receiver android:name=".CustomReceiver"> <intent-filter> <action android:name="mobile.android.ch10.MYBROADCAST" /> <category android:name="mobile.android.ch10.mycategory" /> </intent-filter> </receiver>
验证广播接收器是否注册
private void validateReceiver(String actionName) { // 获取PackageManager PackageManager packageManager = getPackageManager(); // 指定要查询广播的动作 Intent intent = new Intent(actionName); // 返回已查到的广播接收器集合,如果没有符合条件的广播,List长度为0 List<ResolveInfo> resolveInfos = packageManager.queryBroadcastReceivers(intent,PackageManager.GET_INTENT_FILTERS); // 显示查询到的广播的数量 Toast.makeText(this,"已发现" + resolveInfos.size() + "个接收去电广播的接收器" ,Toast.LENGTH_SHORT).show(); StringBuilder sb = new StringBuilder(); if(resolveInfos.size() > 0 ){ for (ResolveInfo resolveInfo : resolveInfos){ sb.append(resolveInfo.toString()); } } Toast.makeText(this,sb.toString() ,Toast.LENGTH_SHORT).show(); }