Broadcasts详解

Android系统和Android应用程序都可以发送和接收广播,类似于publish-subscribe设计模式。广播在某个特定事件发生时被发送,例如,当各种系统事件发生时(如系统启动或设备开始充电),Android系统就会发送广播。应用程序也可以发送自定义广播,例如,通知其他应用程序他们关注的事件(如数据下载完成)。

应用程序可以注册广播接收器。当广播被发送时,系统自动将广播传递到已注册接收该广播的应用程序。一般来说,广播可以作为应用程序间、正常用户流之外的消息传递系统。

系统广播

当各种系统事件发生时,系统会自动发送广播,例如系统进入/退出飞行模式。系统广播被发送给所有注册该广播接收器的应用程序。广播消息被封装在一个Intent对象中,用Action字符串标识所发生的事件(例如android.intent.action.AIRPLANE_MODE)。Intent还可能包含附加到其extra字段中的附加信息。例如,飞行模式的Intent包括一个boolean的附加值,指示当前是否处于飞行模式。

完整的系统广播Action清单,请查看Android SDK中的BROADCAST_ACTIONS.TXT文件。每个广播Action都有一个与之相关联的常量字段。例如,常量字段ACTION_AIRPLANE_MODE_CHANGED的值是android.intent.action.AIRPLANE_MODE

系统广播的变化

Android 7及以上版本不再发送以下系统广播。这种优化影响到所有应用程序,而不仅仅针对Android 7的应用程序。
- ACTION_NEW_PICTURE
- ACTION_NEW_VIDEO

Target为Android 7(API 24)及以上的应用程序要接收以下广播,必须在代码中动态注册,在manifest中静态注册将收不到广播。
- CONNECTIVITY_ACTION

从Android 8.0 (API 26)开始,系统对静态注册的广播接收器做了额外的限制。如果应用程序target为API 26及以上,则不能使用静态注册方式注册大多数隐式广播(不具体针对某个应用程序的广播)。

接收广播

应用程序有两种方式注册广播接收器:静态注册(在manifest中注册)和动态注册(在java代码中注册)。

静态注册广播接收器

如果你在manifest中注册一个广播接收器,且应用程序没有运行,系统就会在广播发送时启动你的应用程序。静态注册广播的步骤如下:
1、 在应用程序manifest中指定<receiver>元素。

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
       <action android:name="android.intent.action.BOOT_COMPLETED"/>
       <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

IntentFilter指定广播接收器关注的Action。

2、 继承BroadcastReceiver类并实现onReceive(Context,Intent)方法。下面示例中的广播接收器用log打印出了广播内容:

public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "MyBroadcastReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        StringBuilder sb = new StringBuilder();
        sb.append("Action: " + intent.getAction() + "\n");
        sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
        String log = sb.toString();
        Log.d(TAG, log);
        Toast.makeText(context, log, Toast.LENGTH_LONG).show();
    }
}

当安装应用程序时,System Package Manager就注册了接收器。然后,广播接收器就成为应用程序的一个单独的入口点,即如果应用程序当前没有运行,系统可以通过广播启动应用程序。系统将创建一个新的BroadcastReceiver组件对象来处理每个接收到的广播。这个对象在调用onReceive(Context, Intent)的时间段内有效,一旦这个方法return,系统就认为该BroadcastReceiver组件不再active,并销毁它。

动态注册广播接收器

动态注册广播接收器的步骤如下:
1、 创建一个BroadcastReceiver的实例:

BroadcastReceiver br = new MyBroadcastReceiver();

2、 创建一个IntentFilter,通过调用代码registerReceiver(BroadcastReceiver,IntentFilter)注册广播接收器:

IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);

只要注册广播的context有效,动态注册的广播接收器就能接收广播。例如,如果您用Activity的context注册广播接收器,则只要Activity未被销毁,就能接收广播。如果您用Application的context注册广播接收器,则只要APP在运行,就能接收广播。

3、 调用unregisterReceiver(android.content.BroadcastReceiver)注销广播接收器。当你不再需要它,或context不再有效时,一定要注销接收器。

要注意注册和注销接收器的时机要配对,例如,如果你用Activity的context在onCreate(Bundle)中注册接收器,那么要在onDestroy()中注销接收器,以防止广播接收器泄露。如果Activity处于pause状态时你不想接收广播,那么你需要在onResume()中注册接收器,在onPause()中注销接收器,以防止异常的多次注册,这也可以减少不必要的系统开销。不要在onSaveInstanceState(Bundle)中注销接收器,因为这个函数不是每次都被调用到。

对进程状态的影响

BroadcastReceiver是否运行影响其所在进程的状态,进而影响进程被系统杀死的可能性。例如,当一个进程正在运行一个广播接收器(即执行onReceive()方法中的代码),那么它被认为是一个前台进程,它会保持正常运行,除开系统有极端内存压力。

然而,一旦你的代码从onReceive()返回,BroadcastReceiver就不再active。一个常见现象:用户近期从未与应用交互过,如果这个进程中只运行了一个静态注册的Receiver,没有其它运行的组件,一旦代码从onReceive() return,系统就会认为这是个低优先级的进程并可能会杀死它,使资源可用于其他更重要的进程。

因此,您不应该在BroadcastReceiver中启动长时间运行的后台线程。onReceive()执行完后,系统可能杀死进程以随时回收内存,并终止运行在这个进程中的线程。为了避免这种情况,如果你想用更多时间在后台线程中处理广播,那么你应该调用goAsync()JobService来让系统知道进程要继续执行一些工作。下面的代码显示了一个BroadcastReceiver使用goAsync()标志onReceive()返回后,它需要更多的时间来处理广播。

public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "MyBroadcastReceiver";

    @Override
    public void onReceive(final Context context, final Intent intent) {
        final PendingResult pendingResult = goAsync();
        AsyncTask<String, Integer, String> asyncTask = new AsyncTask<String, Integer, String>() {
            @Override
            protected String doInBackground(String... params) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                Log.d(TAG, log);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
                return data;
            }
        };
        asyncTask.execute();
    }
}

发送广播

Android为应用程序发送广播提供了三种方式:
1、 sendOrderedBroadcast(Intent, String):每次发送广播到一个接收器,每个接收器依次接收广播,并将结果传播给下一个接收器或终止广播。接收器运行的顺序可以通过IntentFilter的android:priority属性来控制;具有相同优先级的接收器将以任意顺序运行。

2、 sendBroadcast(Intent):标准的广播发送方式,以随机顺序发送广播给所有接收器。这种广播更高效,但意味着接收器不能读取其他接收器的结果,也不能中止广播。

3、 LocalBroadcastManager.sendBroadcast:发送广播和接收广播都在同一个应用中。如果你不需要在应用程序之间发送广播,那就使用本地广播。这种广播没有进程间通信,所以更高效,更安全。你不需要担心其他应用程序能够接收或发送你的广播导致的安全问题。

下面的代码演示了如何创建一个Intent和调用sendBroadcast(Intent)发送广播:

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);

广播消息被封装在一个Intent对象中。Intent的Action字符串必须以应用程序的java包名为前缀并且能唯一标识这个广播事件。您可以用putExtra(String,Bundle)向Intent附加额外的信息。你也可以通过调用setPackage(String)限制广播的接收者。

广播的权限设置

通过设置广播的权限,有特定权限的APP才能接收此广播。您可以对广播的发送方或接收方执行权限限制。

发送权限

当你调用sendBroadcast(Intent,String)sendOrderedBroadcast(Intent,String, BroadcastReceiver, Handler, int, String, Bundle)时,你可以指定一个权限参数。只有在其manifest中申请了权限许可的接收者(并且在危险的情况下被授予许可)才能接收广播。例如,下面的代码发送一个广播:

sendBroadcast(new Intent("com.example.NOTIFY"),
              Manifest.permission.SEND_SMS);

要接收广播,接收应用程序必须请求如下所示的权限:

<uses-permission android:name="android.permission.SEND_SMS"/>

您可以指定一个现有的系统权限如SEND_SMS,或用<permission>元素自定义权限。有关权限和安全的一般信息,请参见 System Permissions。自定义权限是在安装应用程序时注册的。定义自定义权限的应用程序必须安装在使用它的应用程序之前。

接收权限

如果你注册Receiver时指定了一个权限参数,那么只有在manifest中用<uses-permission>申请了权限的Broadcaster才能发送广播到这些Receiver。例如,假设您的接收应用程序有一个静态注册的接收器,如下所示:

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
    </intent-filter>
</receiver>

或您的接收应用程序有一个动态注册的接收器,如下所示:

IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );

然后,为了能够向这些接收器发送广播,发送广播的应用程序必须请求如下所示的权限:

<uses-permission android:name="android.permission.SEND_SMS"/>

安全和性能

下面是发送和接收广播的一些安全和性能相关的注意事项:
1、 如果你不需要在应用程序之间发送广播,那么使用Support Library库中的LocalBroadcastManager发送广播。这种广播没有进程间通信,所有更高效,更安全。你不需要担心其他应用程序能够接收或发送你的广播导致的安全问题。本地广播可以在应用程序中作为通用的pub/sub event bus使用,而无需任何系统范围的广播开销。

2、 如果许多应用程序在其manifest中注册接收相同的广播,这可能导致该系统启动大量应用程序,从而对设备性能和用户体验造成重大影响。为了避免这种情况,尽量用动态注册替代静态注册。有时,Android系统本身强制使用动态注册接收器。例如,CONNECTIVITY_ACTION广播仅发送给动态注册接收器。

3、 不要使用隐式Intent传播敏感信息。这些信息可以被注册接收广播的任何应用程序读取。有三种方法可以控制谁可以接收你的广播:
a、 可以在发送广播时指定权限。
b、 在Android4.0及以上系统上,发送广播时你可以用setPackage(String)指定包名。系统将广播限制为与包名匹配的应用程序集。
c、 使用LocalBroadcastManager发送本地广播

4、 当你注册一个接收器时,任何应用程序都可以向你的应用程序接收器发送潜在的恶意广播。有三种方法可以限制应用程序接收的广播:
a、 注册广播接收器时指定权限。
b、 对于静态注册的广播,把android:exported设置为false。Receiver将不接收来自应用程序外部的广播。
c、 可以用LocalBroadcastManager限制自己只接收本地广播。

5、 广播Action的命名空间是全局的。确保Action名称是在您自己的命名空间中命名的,否则您可能会无意中与其他应用程序发生冲突。

6、 因为一个接收器的onReceive(Context, Intent)方法在主线程上运行,它应该快速执行并返回。onReceive()执行完后,系统可能杀死进程以随时回收内存,并终止了运行在这个进程中的线程。如果你想用更多时间在后台线程中处理广播,请用下面方法来完成:
a、 在onReceive()中调用goAsync(),并且传入BroadcastReceiver.PendingResult。这会让Receiver在onReceive()返回后保持Active。然而,即使采用这种方法,系统也希望您能在10秒内完成广播。它允许你把工作移动到另一个线程中,避免阻塞主线程。
b、 用JobScheduler设置一个任务。更多信息请参考 Intelligent Job Scheduling
7、不要从广播接收器中启动一个Activity,因为这样做的用户体验是不和谐的,特别是如果有多个接收器的话。相反,可以考虑显示通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值