Alexlee 2018-04-20
10、广播接收器(Broadcast Receiver)
Android 中的广播主要可以分为两种类型,标准广播和有序广播。
标准广播(Normal broadcasts):
是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言
有序广播(Ordered broadcasts):
是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播
注册广播的方式一般有两种,在代码中注册和在 AndroidManifest.xml 中注册,其中前者也被称为动态注册,后者也被称为静态注册。
创建一个广播接收器:
新建一个类,让它继承自 BroadcastReceiver,并重写父类的 onReceive()方法就行了。这样当有广播到来时,onReceive()方法就会得到执行
动态注册:
因为逻辑都是写在onCreate()中,所以必须要在程序启动之后才能接收到广播
动态注册的广播接收器一定都要取消注册才行,这里我们是在 onDestroy()方法中通过调用 unregisterReceiver()方法来实现的
class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "network changes",Toast.LENGTH_SHORT).show(); } }
//MainActivity: protected void onCreate(Bundle savedInstanceState) { ... //当网络状态发生变化时,系统发出的正是一条值为 android.net.conn.CONNECTIVITY_CHANGE 的广播 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 { }
打 开AndroidManifest.xml 文件,在里面加入如下权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
静态注册:
可以让程序在未启动的情况下就能接收到广播
新建一个 BootCompleteReceiver 继承自BroadcastReceiver:
public class BootCompleteReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show(); } }
在 AndroidManifest.xml中添加:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application …… <receiver android:name=".BootCompleteReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application>
发送自定义广播
发送标准广播:
1、新建一个广播接收器
新建一个 MyBroadcastReceiver继承自 BroadcastReceiver
2、在 AndroidManifest.xml 中对这个广播接收器进行注册
<receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="com.example.broadcasttest.MY_BROADCAST"/> </intent-filter> </receiver>
3、定义按钮发送广播
public void onClick(View v) { Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); sendBroadcast(intent); }
发送有序广播:
1~2跟标准广播一样
4、
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); sendOrderedBroadcast(intent, null);
sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是Intent,第二个参数是一个与权限相关的字符串,这里传入 null 就行了
android:priority 属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播
<receiver android:name=".MyBroadcastReceiver"> <intent-filter android:priority="100" > <action android:name="com.example.broadcasttest.MY_BROADCAST"/> </intent-filter> </receiver>
abortBroadcast()方法,就表示将这条广播截断,在广播接收器中使用
使用本地广播:
前面我们发送和接收的广播全部都是属于系统全局广播,
即发出的广播可以被其他任何的任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播
本地广播的用法并不复杂,主要就是使用了一个 LocalBroadcastManager 来对广播进行管理
1、接收器
class LocalReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received local broadcast",Toast.LENGTH_SHORT).show(); } }
2、
public class MainActivity extends Activity { private IntentFilter intentFilter; private LocalReceiver localReceiver; private LocalBroadcastManager localBroadcastManager; @Override protected void onCreate(Bundle savedInstanceState) { 。。。 //获取localBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 发送本地广播 Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST"); localBroadcastManager.sendBroadcast(intent); } }); intentFilter = new IntentFilter(); intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST"); localReceiver = new LocalReceiver(); localBroadcastManager.registerReceiver(localReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(localReceiver); }
广播的最佳实践——实现强制下线功能
原理:
点击强制下线按钮,发送一条”强制下线“的广播,在广播接收器里弹出一个不可取消的对话框,点击对话框确定按钮,调用 ActivityCollector 的 finishAll()方法来销毁掉所有活动,并重新启动 LoginActivity 这个活动
强制用户下线的逻辑并不是写在 MainActivity 里的,而是应该写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只需要发出这样一条广播,就可以完成强制下线的操作了。
注意:
由于我们是在广播接收器里启动活动的,因此一定要给Intent 加入 FLAG_ACTIVITY_NEW_TASK 这个标志。
还需要把对话框的类型设为 TYPE_SYSTEM_ALERT,不然它将无法在广播接收器里弹出
1、发送广播:
public void onClick(View v) { Intent intent = new Intent("com.example.broadcastbestpractice.FORCE_OFFLINE "); sendBroadcast(intent); }
2、接收广播:
public class ForceOfflineReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { Toast.makeText(context, "received in ForceOfflineReceiver", Toast.LENGTH_SHORT).show(); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); dialogBuilder.setTitle("Warning"); dialogBuilder.setMessage("You are forced to be offline. Please try to login again."); dialogBuilder.setCancelable(false); //点击对话框OK dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCollector.finishAll(); // 销毁所有活动 Intent intent = new Intent(context,MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); // 重新启动LoginActivity } }); AlertDialog alertDialog = dialogBuilder.create(); // 需要设置AlertDialog的类型,保证在广播接收器中可以正常弹出 alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST); alertDialog.show(); // context.unregisterReceiver(this); } }
3、注册广播接收器,声明权限 android.permission.SYSTEM_ALERT_WINDOW
问题:
1、Broadcast的onReceive方法中弹出AlertDialog
使用静态注册广播,只能弹出System Alert类型的Dialog。但google在 6.0加入了运行时权限的概念,需要在java代码中进行动态申请。为了防止旧的应用程序崩溃,只对targetSDK为23及以上的程序使用新的权限机制。
相应的解决方法有如下几种。
第一种方式是:targetSDK设为23以下就可以规避问题。
第二种方式是:设置里面给了这个调试应用【允许悬浮窗】 但是得手动的打开(我是没有找到这个悬浮窗到底在哪里)。
第三种方式是:使用无需权限显示悬浮窗。
a 修改广播接收类中的import包 将
import android.support.v7.app.AlertDialog;
修改为:
import android.app.AlertDialog;
b 修改AlertDialog的弹窗类型为:TYPE_TOAST 将
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
修改为:
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST);
这样编译运行就可以正常弹出AlertDialog了。
2、在广播中弹出对话框与在Activiity中弹出对话框有所不同,在Activiity中弹出对话框,我们需要用到当前Activiity的Context,而在广播中并没有,如果一定要在广播中弹出一个对话框,我需要定义这个对话框是一个系统级别的。
首先需要有弹出系统对话框的权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
在show()之前,要先设置Dialog的类型为TYPE_SYSTEM_ALERT。
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
最后,最最重要的,AlertDialog要使用android.app.AlertDialog,不要用v7包下的Dialog,会报错。