您的位置:首页 > 移动开发 > Android开发

[Android知识体系]之四大组件:broadcastReciever

2016-09-25 01:01 513 查看
好的好的现在轮到一个使用起来很简单的组件——broadcastReciever!虽然使用简单但也不能小看它,做好总结,以后碰见它还可以再复习复习所学,更深刻地理解。任何一样东西都有学习的价值呐。


初认识

android广播分为两个方面,广播发送者和广播接受者。通常情况下,broadcastReciever就是广播接受者(器)。广播作为android组件间的通信方式,可以使用的场景如下:

1.同一app内部的同一组件内的消息通信(单个或多个线程之间);

2.同一app内部的不同组件之间的消息通信(单个进程);

3.同一app具有多个进程的不同组件之间的消息通信;

4.不同app之间的组件之间消息通信;

从实现原理看上,Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型。因此,从实现的角度来看,Android中的广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。具体实现流程要点粗略概括如下:

1.广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;

2.广播发送者通过binder机制向AMS发送广播;

3.AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;

4.消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

对于不同的广播类型,以及不同的BroadcastReceiver注册方式,具体实现上会有不同。但总体流程大致如上。

由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。

在上文说列举的广播机制具体可以使用的场景中,现分析实际应用中的适用性:

第一种情形:同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉,会显太“重”;

第二种情形:同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些教复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松解耦。

第三、四、五情形:由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜。下面主要针对Android广播中的具体知识点进行总结。

具体知识点

主要涉及以下几个内容:

1.什么是BroadcastReceiver

2.BroadcastReceiver的种类

3.如何发送一个广播

4.如何使用BroadcastReceiver

什么是BroadcastReceiver?

  BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,所以它可以很方便的进行系统组件之间的通信。

  BroadcastReceiver虽然是一个监听器,但是它和之前用到的OnXxxListener不同,那些只是程序级别的监听器,运行在指定程序的所在进程中,当程序退出的时候,OnXxxListener监听器也就随之关闭了,但是BroadcastReceiver属于系统级的监听器,它拥有自己的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。

  虽然同属Android的四大组件,BroadcastReceiver也有自己独立的声明周期,但是和Activity、Service又不同。当在系统注册一个BroadcastReceiver之后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会创建与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成之后,BroadcastReceiver的实例就会被销毁。虽然它独自享用一个单独的进程,但也不是没有限制的,如果BroadcastReceiver.onReceive()方法不能在10秒内执行完成,Android系统就会认为该BroadcastReceiver对象无响应,然后弹出ANR(Application No Response)对话框,所以不要在BroadcastReceiver.onReceive()方法内执行一些耗时的操作。

  如果需要根据广播内容完成一些耗时的操作,一般考虑通过Intent启动一个Service来完成该操作,而不应该在BroadcastReceiver中开启一个新线程完成耗时的操作,因为BroadcastReceiver本身的生命周期很短,可能出现的情况是子线程还没有结束,BroadcastReceiver就已经退出的情况,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。

  

BroadcastReceiver的种类

  上面提到,当系统以一个Intent的形式发送一个Broadcast出去之后,所有与之匹配的BroadcastReceiver都会被实例化,但是这里是有区别的,根据Broadcast的传播方式区别,在系统中有如下两种Broadcast:

1.普通广播(Normal Broadcast):它是完全异步的,也就是说,在逻辑上,当一个Broadcast被发出之后,所有的与之匹配的BroadcastReceiver都同时接收到Broadcast。优点是传递效率比较高,但是也有缺点,就是一个BroadcastReceiver不能影响其他响应这条Broadcast的BroadcastReceiver。

2.有序广播(Ordered broadcast):它是同步执行的,也就是说有序广播的接收器将会按照预先声明的优先级依次接受Broadcast,是链式结构,优先级越高(-1000~1000),越先被执行。因为是顺序执行,所有优先级高的接收器,可以把执行结果传入下一个接收器中,也可以终止Broadcast的传播(通过abortBroadcast()方法),一旦Broadcast的传播被终止,优先级低于它的接收器就不会再接收到这条Broadcast了。

对于有序广播,其主要特点总结如下:

1>多个具当前已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。

2>先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。

  虽然系统存在两种类型的Broadcast,但是一般系统发送出来的Broadcast均是有序广播,所以可以通过优先级的控制,在系统内置的程序响应前,对Broadcast提前进行响应。这就是市场上一些拦截器类(如:短信拦截器、电话拦截器)的软件的原理。

  

当然,根据广播的发送方式,除了以上两种,还有:

3.系统广播(System Broadcast):Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,有系统自动发出。

4.粘性广播(Sticky Broadcast):在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated。所以不再多做总结。

5.App应用内广播(此处的App应用以App应用进程为界)(Local Broadcast):Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:

1)其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;

2)其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:

1)对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;

2)在广播发送和接收时,都增加上相应的permission,用于权限验证;

3)发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。

相比于全局广播,App应用内广播优势体现在:1)安全性更高;2)更加高效。

为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。(这里只做简单介绍,我将会在下篇文章中研究LocalBroadcastManager类,毕竟少用)

如何发送一个广播

  上面已经介绍了系统中两种不同的Broadcast,而根据Broadcast传播的方式,Context提供了不同的方法来发布它们:

-sendBroadcast():发送普通广播。

-sendOrderedBroadcast():发送有序广播。

  以上两个方法都有多个重载方法,根据不同的场景使用,最简单的莫过于直接传递一个Intent来发送一个广播。

如何使用BroadcastReceiver

  BroadcastReceiver本质上还是一个监听器,所以使用BroadcastReceiver的方法也是非常简单,只需要继承BroadcastReceiver,在其中重写onReceive(Context context,Intent intent)即可。一旦实现了BroadcastReceiver,并部署到系统中后,就可以在系统的任何位置,通过sendBroadcast、sendOrderedBroadcast方法发送Broadcast给这个BroadcastReceiver。

  但是仅仅继承BroadcastReceiver和实现onReceive()方法是不够的,同为Android系统组件,它也必须在Android系统中注册,注册一个BroadcastReceiver有两种方式:

-在代码中使用Content.registerReceiver(BroadcastReceiver receiver, IntentFilter filter)进行注册,在使用完毕使用Content.unregisterReceiver(BroadcastReceiver receiver)方法进行注销。

-使用清单文件AndroidManifest.xml注册,在节点中,使用节点注册,并用android:name属性中指定注册的BroadcastReceiver对象,一般还会通过指定和,并在节点中通过android:priority属性设置BroadcastReceiver的优先级,在-1000~1000范围内,数值越到优先级越高。

  虽然Android系统提供了两种方式注册BroadcastReceiver,但是一般在实际开发中,还是会使用清单文件进行注册:

<receiver android:name="cn.bgxt.Broadcastdemo.Basic.BasicBroadcast">
<intent-filter android:priority="100">
<action android:name="cn.bgxt.Broadcastdemo.Basic.broadcast"/>
</intent-filter>
</receiver>


注意注意!

在这其中需要注意的属性:

-android:exported ——此broadcastReceiver能否接收其他App的发出的广播,这个属性默认值有点意思,其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。(同样的,activity/service中的此属性默认值一样遵循此规则)同时,需要注意的是,这个值的设定是以application或者application user id为界的,而非进程为界(一个应用中可能含有多个进程)

-android:name —— 此broadcastReceiver类名;

-android:permission ——如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;

-android:process ——broadcastReceiver运行所处的进程。默认为app的进程。可以指定独立的进程(Android四大基本组件都可以通过此属性指定自己的独立进程

再举一个栗子!

<receiver android:name=".MyBroadcastReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>


其中,

-intent-filter由于指定此广播接收器将用于接收特定的广播类型。本示例中给出的是用于接收网络状态改变或开启启动时系统自身所发出的广播。当此App首次启动时,系统会自动实例化MyBroadcastReceiver,并注册到系统中。

之前常说:静态注册的广播接收器即使app已经退出,只要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立,具体分析可以看后面。

下面通过一个简单的示例,讲解一下BroadcastReceiver的声明,以及如何向这个BroadcastReceiver发送消息。

  首先先声明一个BroadcastReceiver,BasicBroadcast.java:

package cn.bgxt.Broadcastdemo.Basic;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BasicBroadcast extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,
"接收到Broadcast,消息为:" + intent.getStringExtra("msg"),
Toast.LENGTH_SHORT).show();
}
}


再声明一个Activity,用于发送Broadcast:BasicActivity.java: 

package cn.bgxt.Broadcastdemo.Basic;

import com.bgxt.datatimepickerdemo.R;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class BasicActivity extends Activity {
Button btnBasicSendNormal, btnBasicSendOrdered;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic);

btnBasicSendNormal = (Button) findViewById(R.id.btnBasicSendNormal);
btnBasicSendOrdered = (Button) findViewById(R.id.btnBasicSendOrdered);
btnBasicSendNormal.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent broadcast=new Intent();
broadcast.setAction("cn.bgxt.Broadcastdemo.Basic.broadcast");
broadcast.putExtra("msg", "这是一个普通广播");
sendBroadcast(broadcast);
}
});

btnBasicSendOrdered.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
Intent broadcast=new Intent();
broadcast.setAction("cn.bgxt.Broadcastdemo.Basic.broadcast");
broadcast.putExtra("msg", "这是一个有序广播");
sendOrderedBroadcast(broadcast, null);
}
});
}
}


效果展示:





不过,一般实际项目中,很少自己定制Broadcast去使用,大多数情况下是去拦截系统发布的Broadcast,针对系统发布的Broadcast内容来做相应的操作。Android系统中原生定义了很多广播的内容,如:开机、关机、来电、短信等等一些系统状态的变化的Broadcast。

说几个注意事项

1.Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister。

2.打代码了就知道,虽然概念上经常说”发送广播“和”接收“,表面上看广播作为Android广播机制中的实体,实际上这一实体本身是并不是以所谓的”广播“对象存在的,而是以”意图“(Intent)去表示。定义广播的定义过程,实际就是相应广播”意图“的定义过程,然后通过广播发送者将此”意图“发送出去。被相应的BroadcastReceiver接收后将会回调onReceive()函数。

好了,就到这里,有点乱,但我觉得还算全。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android