本章需求:首先,让应用轮询新结果并在有所发现时及时通知用户,即使用户重启设备后还没有打开过应用。其次,保证用户在使用应用时不出现新结果通知。

1. 一般intent和broadcast intent

  许多系统组件需要知道某些事件的发生(WIFI信号时有时无,电话的呼入等),为满足这样的需求,Andorid提供了broadcast intent 组件。

  broadcast intent的工作原理类似于之前学过的intent,但不同的是broadcast intent可以被多个叫做broadcast receiver的组件接收、

2. 接受系统broadcast : 重启后唤醒

2.1 standalone receiver

  standalone receiver 是一个在manifest配置文件中声明的broadcast receiver。即使应用进程已消灭,standalone receiver也可以被激活。(还有一种是可以同fragemt或activity的生命周期绑定的dynamic receiver)

  broadcast receiver必须在系统中登记后才能发挥作用,如果不登记,系统就不知道该向哪里发送intent,broadcast receiver的onReceiver()方法也就得不到预定的调用了。

     要登记broad receiver,首先要创建它:

public class StartupReceiver extends BroadcastReceiver {
private static final String TAG = "StartupReceiver"; @Override
public void onReceive(Context context, Intent intent) { //onReceiver是在主线程中执行的
Log.i(TAG, "Received broadcast intent: " + intent.getAction()); boolean isOn = QueryPreferences.isAlarmOn(context);
PollService.setServiceAlarm(context, isOn);
}
}

  broadcast receiver是接受intent的组件,当有intent发送给StartupReceiver时,它 的onReceive()方法会被调用。

  然后在AndroidManifest.xml文件中声明:

  

<usespermissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name=".StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>

  完成声明后,即使app并未运行,只要有匹配的broadcast intent发来,broadcast receiver就会醒来接受,一收到intent,broadcast receiver的onReceive(Context. Intent)方法即开始执行,随后会被销毁。

3. 过滤前台通知消息

  PhotoGallery应用的另一缺陷,通知消息虽然很有用,但应用开着的时候不应该受到通知消息。

  解决方式:

    首先,我们发送(或接收)定制版broadcast intent(最后会锁定它,只允许PhotoGallery应用部件接收它)。其次,不再使用manifest文件,改用代码为broadcast intent动态登记receiver。(动态注册的receiver与fragment进行绑定,收到广播时说明是在app中) 最后,发送一个有序broadcast在一组receiver中传递数据,借此保证最后才运行某个receiver(最后的receiver决定显不显示通知,这个receiver是静态注册的)。

  3.1 发送broadcast intent

    要发送broadcast intent,需要创建一个intent,并传入sendBroadcast(intent)方法即可。

public static final String ACTION_SHOW_NOTIFICATION = "com.bignerdranch.android.photogallery.SHOW_NOTIFICATION";

sendBroadcast(new Intent(ACTION_SHOW_NOTIFICATION));

  3.2 动态broadcast receiver

    动态broadcast receiver是在代码中,而不是在配置文件中完成登记声明。要在代码中登记,可调用registerReceiver(BroadcastReceiver, IntentFliter)方法,取消登记时,则调用unregiseterReceiver

(BroadcastReceiver)方法。receiver自身通常被定义为一个内部类实例,如同一个按钮点击监听器。在registerReceiver()和unregisterReceiver()方法的BroadcastReceiver需要的是同一个实例、

    我们要只在应用开启的时候接受发过来的广播过滤,就不能在 manifest 中声明一个过滤器,而是要动态地建立一个广播接收器。我们在这里建立一个用于隐藏前台通知的通用 fragment 子类:

    

public abstract class VisibleFragment extends Fragment {
private static final String TAG = "VisibleFragment"; @Override
public void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().registerReceiver(mOnShowNotification, filter,
PollService.PERM_PRIVATE, null);
} @Override
public void onStop() {
super.onStop();
getActivity().unregisterReceiver(mOnShowNotification);
} private BroadcastReceiver mOnShowNotification = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 如果接收到广播,说明应用正在前台,所以把 ResultCode 更改掉
Log.i(TAG, "canceling notification");
setResultCode(Activity.RESULT_CANCELED);
}
};
}

  3.3 使用私有权限

    使用动态broadcast receiver存在一个问题,即系统中的任何应用均可监听并触发我们的receiver。

    有两种办法可以阻止应用闯入我们的私人领域,一种办法是在mainfest配置文件里给receiver标签添加一个android:exported= “false”属性,声明它仅限应用内部使用。

    

    另外,也可以创建自己的使用权限,可以通过在AndroidManifest.xml中添加一个permission标签来完成:

<permission android:name="com.bignerdranch.android.photogallery.PRIVATE"
android:protectionLevel="signature" /> <uses-permission android:name="com.bignerdranch.android.photogallery.PRIVATE" />

    要使用权限,须将其作为参数传入sendBroadcast(),有了这个权限,所有应用都必须使用同样的权限才能接受我们发送的intent。

    要怎么保护我们的broad receiver呢?其他应用可通过创建自己的broadcast intent来触发它。同样,在 registerReceiver(...) 方法中传入自定义权限就能解决该问题:

public abstract class VisibleFragment extends Fragment {
...
@Override
public void onStart() {
super.onStart();
IntentFilter filter = newIntentFilter(PollService.ACTION_SHOW_NOTIFICATION);
getActivity().registerReceiver(mOnShowNotification, filter,
PollService.PERM_PRIVATE, null);
}
...
}

   

     3.3.1 深入学习安全级别          

        自定义权限必须指定 android:protectionLevel 属性值。Android根据 protectionLevel 属性值确定自定义权限的使用方式。在PhotoGallery应用中,我们使用的 protectionLevel 是signature 。signature 安全级别表明,如果其他应用需要使用我们的自定义权限,则必须使用和当前应用相同的key做签名认证。对于仅限应用内部使用的权限,选择 signature 安全级别比较合适。既然其他开发者没有相同的key,自然也就无法接触到权限保护的东西。此外,有了自己的key,将来还可用于我们开发的其他应用中。

    3.4 使用有序broadcast

    如果想让程序在打开时不发送出通知,就不能再让服务来发出通知了,因为它无法知道前台的运行状态。所以我们让 PollService 发送一个有序广播。

     

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);
}

    有序广播是按照优先级发送的,先发送给优先级高的接收器,再发给优先级低的接收器。因为在应用结束后也要发出通知,显然我们发出通知的广播接收器是需要声明在 manifest 文件中的。

    内部实现如下:

    

public class NotificationReceiver extends BroadcastReceiver {
private static final String TAG = "NotificaitonReceiver"; @Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "received result: " + getResultCode());
if (getResultCode() != Activity.RESULT_OK) {
// PollService 发出的 intent 带的结果码是 RESULT_OK
// 如果接到的不是,说明应用在前台,将结果码修改了
return;
} // 如果没有 return,说明应用不在前台,就可以发出通知了。
int requestCode = intent.getIntExtra(PollService.REQUEST_CODE, 0);
Notification notification = (Notification)
intent.getParcelableExtra(PollService.NOTIFICATION); NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(context);
notificationManager.notify(requestCode, notification);
}
}

  

<receiver android:name=".NotificationReceiver"
android:exported="false">
<!-- 在这里将优先级设为最低,即 -999 -->
<intent-filter
android:priority="-999">
<action android:name="com.kniost.photogallery.SHOW_NOTIFICATION" />
</intent-filter>
</receiver>

 

    3.5 receiver与长时间运行任务

    如不想受限与主线程的时间限制,希望broadcast intent触发一个长时间运行任务,该如何做呢?

  •      将任务交给服务处理,然后通过broadcast receiver启动服务。
  •         使用BroadcastReceiver.getAsync()方法。该方法返回一个 BroadcastReceiver.PendingResult 对象,随后可使用该对象提供结果。因此,可将 PendingResult 交给AsyncTask 去执行长时运行的任务,然后再调用 PendingResult 的方法响应broadcast。
  •       goAsync() 方法的弊端是不够灵活。我们仍需快速响应broadcast(10秒内),并且与使用服务相比,没什么架构模式好选择。当然, goAsync() 方法也有明显的优势:可调用该方法设置有序broadcast的结果。

     3.6 使用EventBus

broadcast intent可实现系统内全局性的消息传递。如果仅需要应用内的消息事件广播,该怎
么做呢?答案是使用事件总线(event bus)。

事件总线的设计思路就是,提供一个应用内的部件可以订阅的共享总线或数据流。事件一旦
发布到总线上,各订阅部件就会被激活并执行相应的回调代码。

由greenrobot出品的EventBus是目前广为人知的一个第三方事件总线库。

为实现在应用内发送broadcast intent,Android自己也提供了一个叫作 LocalBroadcast-
Manager 的广播管理类;但上述第三方类库用起来更为灵活和方便。

    

安卓权威编程指南-笔记(第27章 broadcast intent)的更多相关文章

  1. 安卓权威编程指南 -笔记(19章 使用SoundPool播放音频)

    针对BeatBox应用,可以使用SoundPool这个特别定制的实用工具. SoundPool能加载一批声音资源到内存中,并支持同时播放多个音频文件.因此所以,就算用户兴奋起来,狂按按钮播放全部音频, ...

  2. 安卓权威编程指南 -笔记(18章 处理assets)

    resources资源可以存储声音文件,但当处理多个音乐文件时,效率会很低. assets可以被看作随应用打包的微型文件系统,支持任意层次的文件目录结构.类似游戏这样需要加载大量图片和声音资源的应用通 ...

  3. 安卓权威编程指南-笔记(第21章 XML drawable)

    在Andorid的世界里,凡事要在屏幕上绘制的东西都可以叫drawable,比如抽象图形,Drawable的子类,位图图形等,我们之前用来封装图片的BitmapDrawable就是一种drawable ...

  4. 安卓权威编程指南-笔记(第23章 HTTP与后台任务)

    1. 网络连接基本 //通过指定URL获取原始数据,并返回一个字节流数组. public byte[] getUrlBytes(String urlSpec)throws IOException{ / ...

  5. 安卓权威编程指南-笔记(第22章 深入学习intent和任务)

    本章,我们会使用隐式intent创建一个替换android默认启动器的应用.名为NerdLauncher. NerdLauncher应用能列出设备上的其他应用,点选任意列表项会启动相应应用. 1. 解 ...

  6. 安卓权威编程指南-笔记(第24章 Looper Handler 和 HandlerThread)

    AsyncTask是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务. 1. Looper Android中,线程拥有一个消息队列(message queue),使用消息队列的线程叫做 ...

  7. 安卓权威编程指南 挑战练习 25章 深度优化 PhotoGallery 应用

    你可能已经注意到了,提交搜索时, RecyclerView 要等好一会才能刷新显示搜索结果.请接受挑战,让搜索过程更流畅一些.用户一提交搜索,就隐藏软键盘,收起 SearchView 视图(回到只显示 ...

  8. 安卓权威编程指南 - 第五章学习笔记(两个Activity)

    学习安卓编程权威指南第五章的时候自己写了个简单的Demo来加深理解两个Activity互相传递数据的问题,然后将自己的学习笔记贴上来,如有错误还请指正. IntentActivityDemo学习笔记 ...

  9. 安卓权威编程指南 挑战练习(第26章 在 Lollipop 设备上使用 JobService)

    26.11 挑战练习:在 Lollipop 设备上使用 JobService 请创建另一个 PollService 实现版本.新的 PollService 应该继承 JobService 并使用 Jo ...

随机推荐

  1. mysql only_full_group_by

    下载安装的是最新版的mysql5.7.x版本,默认是开启了 only_full_group_by 模式的,但开启这个模式后,原先的 group by 语句就报错,然后又把它移除了. 一旦开启 only ...

  2. C语言代码在内存中的存储

    http://blog.chinaunix.net/uid-26430381-id-4359960.html

  3. Spring4.3.25版本使用的积累性总结(不定期更新)

    Spring4.3.25版本使用的积累性总结 Spring4.x所有Maven依赖 Spring基于XML配置方式注入bean对象和@Resource注解的使用 详解Spring3.x 升级至 Spr ...

  4. 14 微服务电商【黑马乐优商城】:day04-ES6语法入门

    day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一)  :day02-springcloud(理论篇二)  :day ...

  5. 关于前端jquery的总结

    简介 jQuery是一个JavaScript库,特性丰富,包含若干对象和很多函数,可以代替传统DOM编程的操作方式和操作风格,通过对DOM  API.DOM事件的封装,提供了一套全新的API,这套全新 ...

  6. ubuntu下切换默认的python版本

    Ubuntu系统自带python2.7(默认)和python3.5,所以不需要自己安装python.我们可以使用命令python -V或python3 -V来查看默认的python版本. 现在越来越多 ...

  7. MFC的程序,不想显示窗口,任务栏里也不显示

    在dialog的oninitdialog里设置如下属性,很简单,网上一些乱七八糟的做法,一行代码就能搞定啊 SetWindowPos(&CWnd::wndNoTopMost,0,0,0,0,S ...

  8. DFS-BFS(深搜广搜)原理及C++代码实现

    深搜和广搜是图很多算法的基础,很多图的算法都是从这两个算法中启发而来. 深搜简单地说就是直接一搜到底,然后再回溯,再一搜到底,一直如此循环到没有新的结点. 广搜简单地说就是一层一层的搜,像水的波纹一样 ...

  9. C++ 回调函数简单示例

    回调函数其实就是以函数指针做函数参数传递给另一个函数,在另一个函数执行的时候可以根据函数指针执行回调函数的代码.简单示例,便于理解,防止遗忘. #include <iostream> ty ...

  10. SpringMVC配置前端控制器的路径问题

    在使用SpringMVC时,都需要在web.xml中配置一个前端控制器DispatcherServlet 控制器是一个servlet,但这个路径配置就有很多种,之前都是以扩展名配置的路径,如*.do, ...