什么是Broadcast?
广播是程序组件之间的传输消息的机制,广播的内容是一个intent,在其中携带数据,他没有用户界面。
同一APP中不同组件传输,不同APP组件间传输消息。
默认情况下广播接收者运行在UI线程中。
广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁 ;广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框;也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉 ;耗时的较长的工作最好放在服务中完成。
广播接收器
- 标准广播:异步执行,发出后接收者几乎同一时刻收到广播。
- 有序广播:同步执行,同一时间只有一个接收者能收到,这个接收者可以选则停止,执行完逻辑后继续传递,还可修改广播内容。
- 粘性广播:粘性广播发送后在没有找到任何接收方的情况下会一直等待,接收者重建会接收到广播。(确保重要的状态改变后的信息被持久保存,并且能随时广播给新的广播接收器)
- 系统广播:有系统程序发出的广播,是标准广播。主要涉及到手机的基本操作
标准广播:标准广播又区分为两种:动态注册 和 静态注册
静态广播注册
MyStaticReceiver这个类里面很简单,只做了打印收到的信息:
package com.hope.demo.broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import com.hxc.supreme.utils.ToastUtil;
public class MyStaticReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("MyStaticReceiver", "onReceive: "+intent.getStringExtra("info"));
}
}
Manifest中的代码块:
<receiver
android:name=".broadcast.MyStaticReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="hopu"/>
</intent-filter>
</receiver>
然后就是主界面一个点击按钮,布局文件很简单:
public class BroadcastActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tvStaticBroadcast;
private TextView tvDynamicBroadcast;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast);
tvStaticBroadcast = findViewById(R.id.tv_static_broadcast);
tvDynamicBroadcast = findViewById(R.id.tv_dynamic_broadcast);
tvStaticBroadcast.setOnClickListener(this);
tvDynamicBroadcast.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_static_broadcast:
sendBroadcast();
break;
case R.id.tv_dynamic_broadcast:
break;
}
}
private void sendBroadcast() {
Intent intent = new Intent();
intent.setAction("hopu");
intent.putExtra("info", "这是一条静态广播");
sendBroadcast(intent);
}
}
然后看一下logcat中的日志输出:
MyStaticReceiver: onReceive: 这是一条静态广播
然而,在Android8.0及以上的系统中,收不到静态广播!Google了一下发现,谷歌在8.0后为了提高效率,删除了静态注册,防止关闭App后广播还在, 造成内存泄漏, 现在静态注册的广播需要指定包名!!!
意思就是在8.0以后Google官方推荐用动态注册替换静态注册,那我们就指定一下包名看看是否管用:
代码只改动了发送广播的方法:
private void sendBroadcast() {
Intent intent = new Intent();
intent.setPackage(getPackageName());//Android8.0以上需指定包名
intent.setAction("hopu");
intent.putExtra("info", "这是一条静态广播");
sendBroadcast(intent);
}
看下logcat的输出日志:
MyStaticReceiver: onReceive: 这是一条静态广播
事实是证明可以的,但是Google推荐使用动态注册,那我么接下来就看一下动态注册的使用方式。
动态广播注册
public class BroadcastActivity extends AppCompatActivity implements View.OnClickListener {
private TextView tvStaticBroadcast;
private TextView tvDynamicBroadcast;
private DynamicReceiver dynamicReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast);
tvStaticBroadcast = findViewById(R.id.tv_static_broadcast);
tvDynamicBroadcast = findViewById(R.id.tv_dynamic_broadcast);
tvStaticBroadcast.setOnClickListener(this);
tvDynamicBroadcast.setOnClickListener(this);
dynamicReceiver = new DynamicReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("husanity");
registerReceiver(dynamicReceiver, intentFilter);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.tv_static_broadcast:
sendBroadcast();
break;
case R.id.tv_dynamic_broadcast:
sendDynamicBroadcast();
break;
}
}
private void sendBroadcast() {
Intent intent = new Intent();
intent.setPackage(getPackageName());//Android8.0以上需指定包名
intent.setAction("hopu");
intent.putExtra("info", "这是一条静态广播");
sendBroadcast(intent);
}
private void sendDynamicBroadcast() {
Intent intent = new Intent();
intent.setAction("hopu");
intent.putExtra("extra", "这是一条动态广播");
sendBroadcast(intent);
}
class DynamicReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("BroadcastActivity", "onReceive: " + intent.getStringExtra("extra"));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//onDestroy中需要反注册,用来优化内存空间避免内存泄漏
unregisterReceiver(dynamicReceiver);
}
}
看下logcat打印日志:
BroadcastActivity: onReceive: 这是一条动态广播
具体案例
动态注册广播接收者,接收网络发生变法的广播
public class HomeActivity extends AppCompatActivity {
private NetWorkReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
//创建一个意图过滤器
IntentFilter filter = new IntentFilter();
//接收网络改变的action
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
receiver = new NetWorkReceiver();
//注册广播
this.registerReceiver(this.receiver, filter);
}
/*
* 取消注册
* */
@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(this.receiver);
}
public class NetWorkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//如果当前有默认的网络就返回NetworkInfo 否则就返回 null
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
//因为可能是null 所以要先判断是否为空
if (networkInfo != null && networkInfo.isAvailable()) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
Toast.makeText(context, "Wifi", Toast.LENGTH_SHORT).show();
} else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
Toast.makeText(context, "流量", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(context, "世界上最遥远的距离就是没有网", Toast.LENGTH_SHORT).show();
}
}
}
}
静态注册广播接收者,实现开机启动
<!--注册我们的开机广播-->
<receiver
android:name=".MyBootComplatedReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
public class MyBootComplatedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent gotoIntent = new Intent(context, RootComplatedActivity.class);
context.startActivity(gotoIntent);
}
}
有序广播
通过Context.sendOrderedBroadcast来发送,所有的receiver依次执行。BroadcastReceiver可以使用setResult系列函数来结果传给下一个BroadcastReceiver,通过getResult系列函数来取得上个BroadcastReceiver返回的结果,并可以abort系列函数来让系统丢弃该广播,使用该广播不再传送到别的BroadcastReceiver。可以通过在intent-filter中设置android:priority属性来设置receiver的优先级,优先级相同的receiver其执行顺序不确定。
举例:
短信拦截:你可以设置自己的app的广播接收器的级别高于系统原来的级别,并且收到后中断往下传播,这样的话收件箱里面就不会收到短信了。
BroadCastReceiver中有那些应用场景?
APP内部消息通信。
不同APP之间的消息通信。
接收系统发出的设备信息。
广播优先级对无序广播生效吗?
优先级对无序广播生效
动态注册谁的优先级更高?
一自己设置优先级,二谁先注册谁高。
如何判断当前广播接收者收到的广播是有序还是无序?
在onReceive中调用isOrderedBroadcast()
BroadcastReceiver中为什么不能执行耗时操作?
- BroadcastReceiver一般处于主线程中,时间超过10s会报ANR错误。
- BroadcastReceiver启动较快,若进程中只有一个接收者并开了耗时的子线程,onReceive执行完后广播接收者被销毁后系统会认为是空进程,系统会优先杀死。
- 如果广播接收器是启用的独立线程(在Manifest中设置了receiver的process)并且在其中开启子线程执行耗时任务,也会很容易在内存不足时优先被回收,子线程中的任务没法完成,如2。
局部广播
- 局部广播的发送者和接受者都同属于一个
APP
- 相比于全局广播具有以下优点:
- 其他的
APP
不会受到局部广播,不用担心数据泄露的问题。- 其他
APP
不可能向当前的APP
发送局部广播,不用担心有安全漏洞被其他APP
利用。- 局部广播比通过系统传递的全局广播的传递效率更高。
Android v4
包中提供了LocalBroadcastManager
类,用于统一处理 APP 局部广播,使用方式与全局广播几乎相同,只是调用注册 / 取消注册广播接收器和发送广播偶读方法时,需要通过LocalBroadcastManager
类的getInstance()
方法获取的实例调用。
前台广播后台广播?
前台广播对应前台队列,后台广播对应后台队列。可以通过设置Intent.FLAG_RECEIVER_FOREGROUND属性来将广播定义为前台广播,如果未定义,默认使用后台广播。前台广播超时时间是10s,后台是60s。
静态广播和动态广播
- 静态广播:只要
app
还在运行,那么会一直收到广播消息- 动态广播:当注册的组件销毁了那么就会接收不到广播。