自学安卓编程权威指南(二十四)

在这一章,我们将来学习如何监听系统发送的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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值