在这一章,我们将来学习如何监听系统发送的broadcast intent,以及如何使用broadcast receiver处理他们,此外我们会在应用运行时动态发送与接收broadcast intent,最后还会使用有序的broadcast判断应用是不是在前台运行
在安卓中,各种事件都有可能发生,WiFi的有无,电话的接打,短信的收发,系统的组件需要掌握这些动态,为了满足这些需求,Android提供了broadcast intent组件,它的原理和之前的intent类似,但是它可同时被多个叫做broadcast receiver的组件接收,这也是存在的意义
上面的后台定时器随人已经挺完美的,但是如果我们重启了手机,那么定时器就会失效,设备重启后,那些持续运行的应用也需要重启,那么监听带有BOOT_COMPLETED操作的broadcast intent就可以知道设备是不是已经完成启动了,只要打开设备,系统就会发送一个BOOT_COMPLETED broadcast intent,想要监听它,那么就得创建和登记一个standalone broadcastcast receiver
一.创建并登记 standalone receiver
standalone recevier是一个在manifest配置文件中的声明的broadcast receiver,即使进程已经消亡了,它也可以被激活,这个receiver也需要在系统中登记才可以来使用,如果不登记,receiver的onReceiver就不能按正常去调用了
(1)创建一个broadcast receiver去继承BroadcastReceiver
public class StartupReceiver extends BroadcastReceiver {
private static final String TAG = "S...";
//当有intent发送给这个类的时候,这个方法就会调用
public void onReceive(Context context,Intent intent ) {
Log.i(TAG,"iii" + intent.getAction);
}
(2)登记StartupReceiver,和配置权限
在<application>里面登记
<receiver android:name = ".StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
配置权限
<uses-permission android:name="android.permission.RECETVE_BOOT_COMPLETED"/>
此时完成声明后,即使应用都还没有运行,只要有比配的broadcast intent发来,broadcast Receiver就会醒来接收,一接受到intent,那么它的onReceive()方法就会开始运行,随后销毁
(3)使用receiver
broadcast receiver的生命非常短暂,我们无法任何异步API或登记监听器,因为一旦onReceive())方法运行完后,receiver就会不存在,onReceive()方法同样运行在主线程上面,因此不会做一些费时费力的事情,如网络连接,数据的永久保存,对于它,它很适合一些便利型的任务,比如启动activity或服务,以及系统重启后重置定时运行的定时器,
(4)添加定时器的状态preference
receiver需要知道定时器的启停状态,为了存储它的状态,给QueryPreferences添加一个preference常量和两个便利方法
private static final String PREF_IS_ALARM_ON = "isAlarmOn";
public static boolean isAlarmOn(Context context){
return PreferenceManager.getDefautSharedPreferences(context).getBoolean(PREF_IS_ALARM_ON,false);
}
public static void setAlarmOn(Context context,boolean isOn) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(PREF_IS_ALARM_ON,isOn).apply();
}
(5)然后就是需要存储定时器的状态
QueryPreferences.setAlarmOn(context,isOn);
(6)这样设备启动后就可以启动定时器了,在onReceive()方法中添加下面的代码
boolean isOn = QueryPreference.isAlarmOn(context);
PooService.setServiceAlarm(context,isOn);
进程上面的实现后,重启设备,这次后台的Polling服务就会重启
二.过滤前台通知服务
当应用开着的时候就不应该收到通知的消息,同样我们这边使用broadcast intent来实现它
这边我们发送和接收定制的broadcast intent(最后会去锁定它,只允许我们这个应用去接受它),我们不再在manifest文件里面去登记,而是在代码中为broadcast intent动态登记receiver,最后发送一个有序的broadcast 在一组receiver中传递数据,借此保证最后才会运行某个receiver
(1)发送broadcast intent
这边也就是先创建一个intent,并传入sendBroadcast(Intent)方法就可以,因此这边需要先定义一个操作常量
在PollService类中
private static final long ACTION_SHOW_NOTIFICATION = "com.bignerdranch.android.photogallery.SHOW_NOTIFICATION";
sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION));
现在有了结果就会对外面进行广播
(2)创建和动态登记receiver
完成intent的发送后就需要用receiver俩收ACTION_SHOW_NOTIFICATION了,我们这边需要在fragment还在活动的时候去接收intent,而在manifest文件中声明的receiver总是在接收intent,所以这边就采用动态的broadcast receiver来解决问题,要在代码中登记,那么就需要使用registerReceiver()方法,取消登记的时候就需要使用unregisterReceiver()方法,receiver在这边通常是定义为一个内部类实例,在上的两个方法中,我们需要的是同一个实例,因此需要将receiver赋值给一个实例变量
新建立一个VisibleFragment的抽象类,这个类是一个掩藏前台通知的通用fragment
public abstract class VisibleFragment extends Fagment {
private static final String TAG ="VisbleFragment";
public void onStart(){
super.onStart();
IntentFilter filter = new IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().resgisterReceiver(mOnShowNotification,filter);
}
public void onStop(){
super.onStop();
getActivity().unregisterReceiver(moOnShowNotification);
}
private BroadcastReceiver mOnShowNotification = new BroadcastReceiver(){
public void onReceive(Context context,Intent intent) {
Toast.makeText(getActivity(),"ddd"+intent.getAction(),Toast.LENGTH_LONG).show();
}
}
}
要传入一个IntentFilter,那么就必须先用代码去创建它,这里创建的IntentFilter应该和XML文件里面定义的filter一样
任何使用XML定义的IntentFilter都可以用代码的方式来定义,想要在代码中配置IntentFilte可以使用addCategory(String),addAction(String),addDataPath(String);
使用动态代码登记的broadcast Receiver,那么就要记得事后清理,在声明生命周期启动方法中登记,就需要在相应的停止方法中去撤销
如:在onStart()中就需要在onStop()方法中,在onCreate()中就需要在onDestroy()方法中(在这两个方法中的话就应该使用getActivity().getApplicationContext()方法了,因为在设备旋转时,我们会发现他们的activity是不一样的)
(3)设置fragment为可见的
现在我们用PhotoGallery去继承VisibleFragment
public class PhotoGalleryFragment extends VisibleFragment{
}
现在只要我们运行应用,多次打开后台检查服务,就会看见提示
三.使用私有权限限制broadcast
上面的使用动态权限虽然很好,但是它有一个问题,就是系统中的任何应用都可以监听和触发我们的receiver,为了解决这个问题,可以采用下面的两种方法
(1)在manifest文件中给我们的receiver标签添加一个android:exported = "false",这样就是声明了它仅仅提供给应用内部使用,系统中的其他应用就无法使用到
(2)可以创建自己的使用权限,这需要在AndroidManifest文件里面添加一个permission标签,如同下面声明和获取自己的使用权限
<permission android:name="com.bingnerdranch.android.photogallery,PRIVATE"
android:protectionLevel = "signature"/>//这里我们定义了自己的定制权
<user-permission android:name="com.bignerdranch.android.photogallery.PRIVATE"/>//这样的代码需要三次出现,那么就最好使用复制的方法保证没有出现其他问题
(3)发送带有权限的broadcast
为了使用权限,我们需要在代码中定义一个对应常量,然后将其传入sendBroadcast()方法中
public static final String PERM_PRIVATE = "com.bignerdranch.android.photogallery.PRIVATE"
sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION),PERM_PRIVATE);
要使用权限就需要将其作为参数传入sendBroadcast(),有了这个权限所以的应用必须有这个权限才可以接收到我们发送的intent
(4)保护我们的broadcast receiver
对于上面来说,其他应用也可以创建自己的broadcast intent来触发它,为了解决这个问题,我们可以在registerReceiver()方法中传入自定义的权限就可以,这样就只有我们自己这个应用才可以发送broadcast intent给它了
在VisibleFragment类中
public void onStart(){
super.onStart();
IntentFilter filter = new IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().resgisterReceiver(mOnShowNotification,filter,PollService.PERM_PRIVATE,null);
}
(5)安全级别
在上面我们使用了android:protectionLevel属性值,在上面我们设置为signatu,那么此时就是如果其他应用想要使用我们的自定义权限,那么就必须使用当前应用相同的key做为签名认证,有了自己的key那么就可以用于将来自己开发的其他应用中
四.使用有序的broadcast收发数据
最后我们就是需要保证动态登记的receiver总是比其他receiver先于接收到PollService.ACTION_SHOW_NOTIFICATION broadcast,然后还要修改这个broadcast,制止通知消息的发布
对于上的代码来说,虽然现在可以发个人私有的broadcast intent,但是此时却还是不能收的通信,这是因为普通的broadcast intent只是概念上被所有人接收,事实上onReceive()方法是在主线程上调用的,所以每个receiver就没有并发运行,也就不能要求他们去按某种顺序去执行,
但是对于有序broadcast intent来说,它就可以实现双向通信,有序broadcast intent允许多个receiver来依次处理broadcast intent,另外通过传入一个名为result receiver的特殊broadcast receiver,有序broadcast还支持发送者接受接收者返回结果
由此我们就可以获得一个改变receiver的返回值的方法
这里我们需要取消通知信息,由此我们使用一个简单的整形结果码,将次要求告诉给信息发送者就可以了
下面修改VisibleFragment类,告诉SHOE_NOTIFICATION的发送者如何处理通知信息
在onReceive()方法中
setResultCode(Activity.RESULT_CANCELED);//这边是由于开着的时候它先接收到发来的信息,这边改变了返回code,所以后面创建的broad receiver由于得到的返回code与设置的不一样就不会去实现某一些功能了
这边我们只需要yes和no,所以只使用int结果码就可以了,如果需要更多的数据,那么就可以调用setResultData(String)
和setResultExtras(Bundle)方法,设定返回值后后续的接受者都会看到或是修改他们
为了上面的方法有效,那么就必须使用有序的broadcast,在PollService类中,我们编写一个可发送有序broadcast的新方法,然后再onHandleIntent()方法中删除直接发送给NotificationManager的代码,调用这个新方法去发出有序的broadcast
public static final String REQUEST_CODE = "REQUEST_CODE";
public static final String NOTIFICATION = "NOTIFICATION";
private void showBackgroundNotification(int requestCode,Notification notification) {
Intent i = new Intent(ACTION_SHOW_NOTIFICATION);
i.putExtra(REQUEST_CODE,requestCode);
i.putExtra(NOTIFICATION,notification);
sendOrderedBroadcast(i,PERM_PRIVATE,null,null,Activity.RESULT_OK,null,null);//参数中除了前两个后面的意思依次是一个result receiver,一个支持result receiver的Handler,结果代码初始值,结果数据,以及有序broadcast的结果附加内容
}
result receiver是一个比较特殊的,只有当所有的有序broadcast receiver接受者结束允许后,它才开始运行,无论如何都必须保证它在其他动态登记的receiver之后运行
(1)新建立一个NotificationReceiver的类
public NotificationReceiver extends BroadcastReceiver {
private static final String TAG = "Noti...";
public void onReceive(Context c ,Intent i) {
if(getResultCode() !=Activity.RESULT_OK) {
return;
}
int requestCode = i.getIntExtra(PollService.REQUEST_CODE,0);
Notification notification = (Notification)i.getParcelableExtra(PollService.NOTIFICATION);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(c);
notificationManager.notify(requestCode,notification);
}
}
然后登记这个新建的receiver,然后赋予它优先级,保证它是最后一个接受到的目标broadcast(这样它就知道应不应该向NotificationManager发出通知了)
<receiver android:name=".NotificationReceiver"
android:export = "false">
<intent-filter android:priority = "-999">
<action android:name="com.bignerdranch.android.photogallery.SHOE_NOTIFICATION"/>
<intent-filter>
</receiver>
这样就可以发现当应用开着的时候不会发送通知消息
(2)receiver与长时间的运行任务
如果不愿意被主线程所限制,希望用broadcast intent触发一个长时间运行的任务,那么就可以把任务交给服务去处理,然后我们通过broadcast receiver瞬时去启动服务
五.学习本地事件
broadcast intent可以实现系统全局性的消息传递,但是如果只想在应用进程中去的消息广播,那么我们就可以使用事件总线(event bus)
事件总线的思路是:提供一个应用中的部件可以订阅的共享总线,或是数据流,事件一旦发布到总线上面,各订阅的部件就会激活并执行相对于的回调代码
对于事件总线,我们经常使用的是EventBus
(1)使用EventBus
首先需要对它在项目中添加依赖库,然后就可以定义事件类了(如果需要传送数据,那么就需要向事件里添加数据字段了)
public class NewFriendAddedEvent{}
(2)在应用的任何地方都可以把消息事件发送到总线上面去
EventBus eventBus = EventBus.getDefault();
eventBus.post(new NewFriendAddedEvent());
(3)在总线上监听,应用的其他部分也可以订阅接收事件接收消息,通常,activity和fragment的登记和撤销登记是在相应的生命周期里面去处理的,如onStart()和onStop()
在activity中的
private EventBus mEventBus;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEventBus = EventBus.getDefault();
}
public void onStart(){
super.onStart();
mEventBus.register(this);
}
public void onStop(){
super.onStop();
mEventBust.unregister(this);
}
有订阅的事件发布时,可以实施一个方法,传入合适的事件类型和添加Subscribe注解,让订阅者作出响应
如果不添加上面的注解,那么事件消息来自哪一个线程就在哪一个线程上解决,有了的话就可以确保它在主线程上处理
(4)使用Rxjava
}