什么是广播
广播是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();
}