赞
踩
- 标准广播
是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。- 有序广播
是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。
- 动态注册广播
顾名思义,就是在代码中注册的。- 静态注册广播
动态注册要求程序必须在运行时才能进行,有一定的局限性,如果我们需要在程序还没启动的时候就可以接收到注册的广播,就需要静态注册了。主要是在AndroidManifest中进行注册。
- 系统广播
Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。- 自定义广播
由应用程序开发者自己定义的广播
一段比较典型的实现代码为:
- public class MyBroadcastReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
- abortBroadcast();
- }
- }
主要就是继承一个BroadcastReceiver,实现onReceive方法,在其中实现自己的业务逻辑就可以了。
- public class MainActivity extends AppCompatActivity {
-
- private IntentFilter intentFilter;
- private MyBroadcastReceiver myBroadcastReceiver;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- intentFilter = new IntentFilter();
- intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
- myBroadcastReceiver = new MyBroadcastReceiver();
- registerReceiver(myBroadcastReceiver, intentFilter);
-
- Button button = (Button) findViewById(R.id.button);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
- sendBroadcast(intent); // 发送广播
- }
- });
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- unregisterReceiver(myBroadcastReceiver);
- }
- }
-
这样MyBroadcastReceiver就可以收到相应的广播消息了。
还是用上面的按个MyBroadcastReceiver,只不过这次采用静态注册的方式
在manifest文件中增加如下的代码:
- <receiver
- android:name=".MyBroadcastReceiver"
- android:enabled="true"
- android:exported="true">
- <intent-filter>
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
- </intent-filter>
- </receiver>
几个相关解释:
- android:exported
此BroadcastReceiver能否接收其他App发出的广播(其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false);- android:name
此broadcastReceiver类名;- android:permission
如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;- android:process
broadcastReceiver运行所处的进程。默认为App的进程。可以指定独立的进程(Android四大组件都可以通过此属性指定自己的独立进程);
- 静态注册即使App退出,仍然能接收到广播
- 动态注册时,当Activity退出,就接收不到广播了
- 但是静态注册即使App退出,仍然能接收到广播这种说法自Android 3.1开始有可能不再成立。
说明:
Android 3.1开始系统在Intent与广播相关的flag增加了参数:
A. FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)
B. FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包
自Android3.1开始,系统本身增加了对所有App当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的App,同样无法接收到广播。
因此对于系统广播,由于是系统内部直接发出的,无法更改此intent的flag值,因此,从3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。
但是对于自定义的广播,可以通过覆写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是新建的。
实现代码为:
- Intent intent = new Intent();
- intent.setAction(BROADCAST_ACTION);
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- sendBroadcast(intent);
在3.1以前,不少App可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即使App已经退出,仍然能接收到,可以启动service等..),3.1后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将Service与App本身设置成不同的进程已经成为实现此类需求的可行替代方案。
前面介绍过,有序广播是异步方式传播的。指的是发送出去的广播被BroadcastReceiver按照先后循序接收。有序广播的定义过程与普通广播无异,只是其的主要发送方式变为:
- /**
- * Broadcast the given intent to all interested BroadcastReceivers, delivering
- * them one at a time to allow more preferred receivers to consume the
- * broadcast before it is delivered to less preferred receivers. This
- * call is asynchronous; it returns immediately, and you will continue
- * executing while the receivers are run.
- * @param intent The Intent to broadcast; all receivers matching this
- * Intent will receive the broadcast.
- * @param receiverPermission (optional) String naming a permissions that
- * a receiver must hold in order to receive your broadcast.
- * If null, no permission is required.
- */
- public abstract void sendOrderedBroadcast(Intent intent,
- @Nullable String receiverPermission);
-
其他的几种重载的方法可以参见官方文档。
有序广播的主要特点:
- 同级别接收是随机的(结合下一条)
- 同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
- 排序规则为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序
- 先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。实现截断的代码为:
abortBroadcast();
标准广播的主要特点为:
- 同级别接收先后是随机的(无序的)
- 级别低的后接收到广播
- 接收器不能截断广播的继续传播,也不能处理广播
- 同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
由前文阐述可知,Android中的广播可以跨进程甚至跨App直接通信,且exported属性在有intent-filter的情况下默认值是true,由此将可能出现的安全隐患如下:
- 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
- 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。
无论哪种情形,这些安全隐患都确实是存在的。由此,业界常见的一些增加安全性的方案包括:
- 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
- 在广播发送和接收时,都增加上相应的permission,用于权限验证;
- 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
- 采用LocalBroadcastManager的方式
下文分析几种常见的处理方案。
为了解决安全性问题,Android在android.support.v4.content包中引入了LocalBroadcastManager。按照官方文档的描述,使用LocalBroadcastManager有如下的优势:
- Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
- (1)You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
- (2)It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
- (3)It is more efficient than sending a global broadcast through the system.
也就是说,使用该机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本地应用程序发出的广播,这样所有的安全性问题都不存在了。
- public class MainActivity extends AppCompatActivity {
- private IntentFilter intentFilter;
- private LocalReceiver localReceiver;
- private LocalBroadcastManager localBroadcastManager;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- localBroadcastManager = LocalBroadcastManager.getInstance(this); // 获取实例
- Button button = (Button) findViewById(R.id.button);
- button.setOnClickListener(new View.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);
- }
-
- class LocalReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
- }
- }
- }
- <permission
- android:name="com.self.define.permission.test"
- android:label="BroadcastReceiverPermission"
- android:protectionLevel="signature">
- </permission>
在自定义权限时,通常会指定protectionLevel属性,常用的如下:
- normal:默认的,应用安装前,用户可以看到相应的权限,但无需用户主动授权。
- dangerous:normal安全级别控制以外的任何危险操作。需要dangerous级别权限时,Android会明确要求用户进行授权。常见的如:网络使用权限,相机使用权限及联系人信息使用权限等。
- signature:它要求权限声明应用和权限使用应用使用相同的keystore进行签名。如果使用同一keystore,则该权限由系统授予,否则系统会拒绝。并且权限授予时,不会通知用户。它常用于应用内部。把protectionLevel声明为signature。如果别的应用使用的不是同一个签名文件,就没办法使用该权限,从而保护了自己的接收者。
如果采用静态注册的方式:
- <receiver
- android:name=".common.MyBroadcastReceiver"
- android:exported="false"
- android:permission="com.self.define.permission.test">
- <intent-filter>
- <action android:name="action.name"/>
- </intent-filter>
- </receiver>
如果采用动态注册的方式
相应的API有:
- (1)registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
- Register to receive intent broadcasts, to run in the context of scheduler.
- (2)sendBroadcast(Intent intent, String receiverPermission)
- Broadcast the given intent to all interested BroadcastReceivers, allowing an optional required permission to be enforced.
- (3)sendOrderedBroadcast(Intent intent, String receiverPermission)
- Broadcast the given intent to all interested BroadcastReceivers, delivering them one at a time to allow more preferred receivers to consume the broadcast before it is delivered to less preferred receivers.
receiver是动态注册时,需要创建自己的使用权限,并且将protectionLevel设置为signature。这样,当别的应用和receiver所在的应用使用的签名不一样时,便不会启动该receiver。例如:
- MyBroadcastReceiver receiver = new MyBroadcastReceiver();
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction("com.example.broadcast.test");
- intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
- //注册receiver时,直接指定发送者应该具有的权限。不然外部应用依旧可以触及到receiver
- registerReceiver(receiver, intentFilter, "com.self.define.permission.test", null);
在注册的时候,最关键的一点是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)进行注册,而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相较于后者,前者在注册的时候要求了发送者必须具有的权限。如果发送者没有该权限,那么发送者发送的广播即使经过IntentFilter的过滤,也不会被receiver接收。此时如果再自定义一个权限,并且将权限的protectionLevel设置为signature,那么外部应用便无法使用该权限,也就无法触及到该receiver。
发送广播的代码为:
- Intent intent = new Intent("com.example.broadcast.test.permission");
- sendBroadcast(intent,"com.self.define.permission.test");
另外需要在manifest文件中定义权限并声明
- <permission
- android:name="com.self.define.permission.test"
- android:label="BroadcastReceiverPermission"
- android:protectionLevel="signature">
- </permission>
- <uses-permission android:name="com.self.define.permission.test"/>
切记要在<application>同级的位置配置使用到的权限
两个参数的官方定义为:
- Context: The Context in which the receiver is running.
- Intent: The Intent being received.
BroadcastReceiver本身不是Context,其内部也不含有Context,但在onReceive(Context context, Intent intent)中有context参数。这个context随着receiver的注册方式的不同而不同:
静态注册:context为ReceiverRestrictedContext
动态注册:context为Activity的context
LocalBroadcastManager的动态注册:context为Application的context
官方文档的描述:
When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed). You cannot launch a popup dialog in your implementation of onReceive().
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。