目录
1. 广播机制简介
Broadcast:应用质检传输信息的机制
BroadcastReceiver:对发送出来的广播过滤并响应的组件,用来接收来自系统和应用中的广播。
用途:开机完成后、网络状态改变时、电池电量改变时,系统产生一条广播
why
广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。
减少工作量,作为应用开发者,只需掌握BroadcastReceiver
2. 接收系统广播
开机完成后、网络状态改变时、电池电量改变时,系统产生一条广播
2.1 动态注册监听网络变化(代码中注册)
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
}
}
}
Notes:
① 内部类 NetworkChangeReceiver 继承自BroadcastReceiver,重写 onReceive。每当网络发生变化,onReceive 就会执行。
② 创建 IntentFilter ,添加 action,当网络状态变化时,系统会发出一条值为 ...CONNECTIVITY_CHANGE 的广播。也就是说想监听什么广播,这里就添加相应的 action
③ registerReceiver 注册广播接收器,传入广播接收器(networkChangeReceiver)实例和意图过滤器(intentFilter)实例。
④ 动态注册一定要注销才行,onDestroy 方法中
准确告诉用户有网还是没网,修改广播接收器
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()){
Toast.makeText(context,"network is available",
Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context,"network is unavailable",
Toast.LENGTH_SHORT).show();
}
}
}
Notes:
① ConnectivityManager 是系统服务类,专门管理网络连接。拿到 NetworkInfo 实例判断是否有网
② 访问系统网络状态,AndroidManifest.xml 声明权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.2 静态注册实现开机启动
动态注册能自由注册注销,灵活性有优势,但是有一个缺点,必须在程序启动之后才能接收到广播。因为注册在 onCreate
1. 新建广播接收器
Exported:是否允许接收本程序以外的广播
Enabled:是否启用这个广播接收器
public class BootCompleteReceiver extends BroadcastReceiver {
private static final String TAG = "BootCompleteReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "onReceive: Boot Complete!");
Toast.makeText(context, "Boot Complete!", Toast.LENGTH_SHORT).show();
}
}
注册这一步已经自动完成了,还需要添加接收开机权限,和广播接收器接收对应的 action
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
...
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
长按电源键会出现关机跟重启的选项。
# 问题:没有收到开机广播(已解决)
原因:接收到了,但是机器需要时间反应,得等半分钟左右。这里不适合用 Toast,写条log 好点,不用盯着等
Notes:
① BroadcastReceiver需要注册(静态注册、代码注册)
② BroadcastReceiver生命周期只有十秒左右,所以在BR里不能做些耗时操作,应通过发送Intent给Service,由Service来完成
③ BR中不允许开启线程
3. 发送自定义广播
3.1 发送标准广播
1.新建 MyNormalReceiver
public class MyNormalReceiver extends BroadcastReceiver {
private static final String TAG = "MyNormalReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive: ");
Toast.makeText(context, "received in MyNormalReceiver!", Toast.LENGTH_LONG).show();
}
}
2.注册广播
<receiver
android:name=".MyNormalReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
3.发送广播
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
Log.d(TAG, "onClick: ");
}
});
# 问题:接收不到标准广播(已解决)
原因:Android8.0 为了提高电池续航时间、提高设备性能,系统限制未在前台运行的应用某些行为,如:
后台运行应用对后台服务的访问受到限制
应用无法使用清单注册的大部分隐式广播。
解决:设置参数 Component,精准定位要接收广播的接收器
参数1:应用包名
参数2:Receiver 所在的完整路径
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
intent.setComponent(new ComponentName("com.example.broadcasttest",
"com.example.broadcasttest.MyNormalReceiver"));
sendBroadcast(intent);
# 问题:程序A发广播,程序B收不到(已解决)
问题:新建应用Broadcast2,使用同样的 action 静态注册广播接收器。A发广播,B收不到
解决:这里需要 addFlag,而且不需要 setComponent
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
if(Build.VERSION.SDK_INT >= 26){
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
}
sendBroadcast(intent);
这个 intent 可以从以存在的活动启动一个新的活动
/**
* If set and this intent is being used to launch a new activity from an
* existing one, the current activity will not be counted as the top
* activity for deciding whether the new intent should be delivered to
* the top instead of starting a new one. The previous activity will
* be used as the top, with the assumption being that the current activity
* will finish itself immediately.
*/
public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 0x01000000;
3.2 发送有序广播
sendOrderedBroadcast(intent, null);
参数1不变
参数2:与权限有关的字符串,这里传 null
重新运行后,俩应用正常收到广播
在A收到广播后,截断广播:
1. 给广播接收器设置优先级
<receiver
android:name=".MyNormalReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
2. 设置截断广播
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive: ");
Toast.makeText(context, "received in MyNormalReceiver!",
Toast.LENGTH_SHORT).show();
abortBroadcast();
}
此时后面的广播接收器再无法接收到这条广播
4. 使用本地广播:安全性考虑
发出的广播只能在应用内部传递,并且广播接收器只能接收来自本应用发出的广播。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "luo-MainActivity";
public static final String LOCAL_BROADCAST = "com.example.broadcasttest2.LOCAL_BROADCAST";
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(LOCAL_BROADCAST);
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction(LOCAL_BROADCAST);
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "onReceive: ------------------");
Toast.makeText(context, "received local broadcast",
Toast.LENGTH_LONG).show();
}
}
}
Note:使用 LocalBroadcastManager 来实现发送广播、注册和注销广播接收器
5. 实践——强制下线功能
效果:
1. 输入 admin,密码 123456,点击登录到主界面
2. 点击发送广播按钮,发出强制下线的广播,弹出提示框
3. 点 OK,退回到登录界面
重要代码:BaseActivity
public class BaseActivity extends AppCompatActivity {
private ForceOffLineReceiver receiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivtiy(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MainActivity.FORCE_OFFLINE);
receiver = new ForceOffLineReceiver();
registerReceiver(receiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
class ForceOffLineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Warning");
builder.setMessage("You are forced to be offline. Please try to login again.");
builder.setCancelable(false);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
});
builder.show();
}
}
}
onCreate, onDestroy 管理活动
onResume, onPause 注册注销广播
理由:需要保证只有处于栈顶的活动,才能接收到强制下线广播,没栈顶活动没必要管这个。所以,当一个活动失去栈顶位置时就会自动注销广播接收器。