广播概述

这是我对官方文档的一个渣翻译,兼我的学习笔记,原文在此。
Android app可以从Android系统和其他Android应用发送或接收广播信息,类似发布-关注设计模式。当关注的事件发生时就会发送广播。举个例子,当各种系统事件发生时发送广播,比如系统启动或设备开始充电。应用也可以发送自定义广播,例如,通知其他应用他们可能关注的东西(比如一些新数据被下载了)。
应用可以通过注册来接收特定的广播。当广播被发送时,系统会自动将广播路由到这些订阅接收这类广播的应用。
通常来说,广播可以用作跨应用和正常用户流之外的消息系统。但是,你要小心不要滥用在后台响应广播和运行作业的机会,它会导致系统性能变慢。

关于系统广播


各种系统事件发生时,系统会自动发送广播,例如当系统切入或切出飞行模式。系统广播会被发送到所有订阅接收这个事件的应用。
广播信息自身被包装在一个Intent对象中,这个对象的action字符标识了发生的事件(例如android.intent.action.AIRPLANE_MODE)。这个intent还可能包含了被捆绑了附加信息的其他域。例如,飞行模式intent包含了一个boolean类型的附加物来表示飞行模式是开还是关。
了解更多关于如何读取intents和从intent中获取action字符的信息,查看Intents and Intent Filters。
完整的系统广播action列表,查看Android SDK中的BROADCAST_ACTIONS.TXT文件。每个广播action关联了一个一个常量域。例如,ACTION_AIRPLANE_MODE_CHANGED的常量值是android.intent.action.AIRPLANE_MODE。每个广播action的文档都可以在它关联的常量域中获得。

系统广播的变化

Android 7.0 以及更高版本不再需要发送以下系统广播。此优化会影响所有应用,而不仅仅是针对Android 7.0的应用。

  • ACTION_NEW_PICTURE
  • ACTION_NEW_VIDEO
    针对Android 7.0 (API level 24) 以及更高版本的应用必须要使用registerReceiver(BroadcastReceiver, IntentFilter)来注册以下广播。在manifest中声明receiver不再起作用。
  • CONNECTIVITY_ACTION
    从Android 8.0 (API level 26) 起,系统对manifest声明的receiver增加了额外的限制。如果你的应用针对API26或更高的版本,则不能使用manifest来声明大多数隐式广播的receiver(不仅仅是应用的广播)。

接收广播

应用有两种方式接收广播:通过Manifest-declared receivers和context-registered receivers。

Manifest-declared receivers

如果你在你的manifest中声明了一个广播接收器,当广播被发送时,系统会启动你的应用(即使应用不在运行中)。

如果你的应用针对API level 26或更高版本,则不能在manifest中声明隐式广播的接收器(不仅仅是你的应用的广播),除了少数可以免除此限制的隐式广播。大多数情况下,你可以使用scheduled jobs代替。
按照以下步骤在manifest中声明广播接收器:

  1. 在应用的manifest中使用元素。

    
        
        
    

intent filters指定了应用要订阅的广播action。

  1. 创建BroadcastReceiver的子类,并实现onReceive(Context, Intent)。在下面的例子中,广播接收器输出日志并显示广播内容。
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();
    }
}

当应用安装时,系统包管理器注册了接收器。接收器就成为了应用的独立入口点,即使当前应用尚未运行,系统也可以启动应用并且发送广播。
如果接收到广播,系统会创建一个新的BroadcastReceiver组件对象来处理每一个广播。该对象仅在调用onReceive(Context, Intent)的期间中有效。一旦代码从这个方法返回,系统就会认为这个组件不再处于活动状态。

Context-registered receivers

根据以下步骤通过context注册接收器:

  1. 创建BroadcastReceiver的实例。
BroadcastReceiver br = new MyBroadcastReceiver();
  1. 创建IntentFilter并且调用registerReceiver(BroadcastReceiver, IntentFilter)注册接收器。
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);

注意:要注册本地广播,调用LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)
Context-registered接收器只在注册的context有效时接收广播。例如,当你使用Activitycontext注册时,你仅在activity没有被销毁前接收广播。如果你使用Application context注册,那么你只能在应用运行时接收广播。

  1. 调用unregisterReceiver(android.content.BroadcastReceiver)停止接收广播。要确认在你不再需要广播时,或context不再存在时注销接收器。
    要注意你注册和注销接收器的位置。例如,当你在onCreate(Bundle)中使用activity context注册接收器,那么你就要在onDestroy()中注销,防止接收器从activity context中泄露。如果你在onResume()中注册接收器,那么你就要在onPause()中注销以防止多次注册(如果你不想在暂停时接收广播,并且它会减少不必要的系统开销)。不要在onSaveInstanceState(Bundle)中注销,因为如果用户移回到历史堆栈,它就不会被调用。

对进程状态的影响

BroadcastReceiver(不论是否运行)影响了它包含的进程的状态,进而影响系统杀死它的可能性。例如,当一个进程执行接收器(准确来说,是运行它的onReceive()方法中的代码)时,它会被当做前台进程。系统会一直保持进程除非内存压力过大。
然而,只要你的代码从onReceive()中返回,BroadcastReceiver 将不再活动。接收器的主机进程的重要性变得和正在运行的其他应用组件一样。如果这个进程主机只是manifest-declared 接收器(应用中用户从未或最近没有与其交互的普通组件),则从onReceive()返回时,系统会认为这个进程是一个优先级很低的进程,并很有可能会杀死它,让其他重要的进程获得更多资源。
因为这个原因,你不能再广播接收器中开启长时间运行的后台应用。在onReceive()后,系统会在任意时间杀死这个进程来释放内存,并且,它会中止这个进程中的衍生
线程。为了避免这种情况发生,你应该调用goAsync()(如果你想在后台线程中多用一点时间处理广播),或者在接收器中使用 JobScheduler来调度JobService,系统就会知道进程会继续执行有效的工作。要了解更多信息,查看Processes and Application Life Cycle。
以下片段显示了一个BroadcastReceiver使用goAsync()来表示它在完成onReceive()后需要更多时间才能完成。当你想在你的onReceive()完成的工作用时很长,导致UI线程错过一帧(大于16ms),使用后台线程会更合适。

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 asyncTask = new AsyncTask() {
            @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 为应用发送广播提供了三种方法。

  • sendOrderedBroadcast(Intent, String)方法在一个时间向一个接收器发送广播。当每个接收器依次执行时,它可以将结果发送到下一个接收器,它也可以完全中止广播,使其不会被传递给其他广播。
  • sendBroadcast(Intent)方法无序地将广播发送给所有接收器。它被成为普通广播。这种广播更有效,但也意味着接收器不能读取其他接收器的结果,发送从广播接收到的数据,或者终止广播。
  • 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 string必须提供这个应用的java包名句法并且唯一定义广播事件名。你可以使用putExtra(String, Bundle)向intent中附加额外的信息。你还可以通过调用intent的setPackage(String)方法,将广播限制在同一组织中的同一组应用。

虽然intent既可以用来发送广播,又可以用startActivity(Intent)来启动activity,但这两种行为是完全无关的。广播接收器无法看到或捕获用来启动activity的intent,同样,当你广播intent时,你也无法找到或启动一个activity。

限制有权限的广播


权限允许你对有特定权限的一系列应用限制广播。你可以对广播的发送者或接收者实施限制。

权限发送

当你调用sendBroadcast(Intent, String)sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)时,你可以制定一个权限参数。只有在manifest中满足权限的接收器(如果是危险权限并在后来被授权)才可以接收这个广播。例如,下面的代码发送了一个广播:

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

要接收这个广播,应用必须要如下请求权限:


你可以如SEND_SMS指定一个现成的系统权限,也可以使用元素定义自定义权限。了解一般权限和安全信息,查看System Permissions。

应用安装时会注册自定义权限。在应用使用前必须要定义自定义权限。

权限接收

如果你在注册广播接收器时指定了权限参数(不管是使用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)还是在manifest中声明),只有使用在manifest中申请权限的广播(如果是危险权限并在后来被授权可以发送一个intent到接收器中。
例如,确定你的接收应用有如下的manifest声明接收器:


    
        
    

或者你的接收应用有如下的context-registered接收器:

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

然后,将广播发送给这些接收器,发送应用必须要如下申请权限:


安全考虑和最佳实践

这里是一些发送与接收广播的安全考虑与最佳实践。

  • 如果你不需要发送广播到应用外的组件,可以使用LocalBroadcastManager发送、接收本地广播,它可以从Support Library中获得。LocalBroadcastManager更有效(不需要进程间通信),并且你可以不用担心别的应用接收或发送你的广播的相关安全问题。本地广播可以在你的应用中作为通用的发布/订阅的事件总线,而不需要系统广播的任何开销。
  • 如果有很多应用都在它们的manifest中注册了要接收同一个广播,会导致系统启动过多应用,对设备性能和用户体验都造成重大影响。为了避免这种情况,最好使用context注册替代manifest声明。有时Android系统本身会强制使用context-registered接收器。例如CONNECTIVITY_ACTION广播就只能使用context-registered发送给接收器。
  • 不要使用隐式intent广播敏感信息。任何注册了要接收这个广播的应用都可以读取这个信息。这里有三种方式可以限制接收你的广播。
    • 你可以在发送广播时指定权限。
    • 在Android 4.0或更高版本,你可以在发送广播时使用 setPackage(String)指定一个包。系统将广播限制为匹配了这个包的一系列应用。
    • 你可以使用LocalBroadcastManager发送本地广播。
  • 当你注册接收器时,任何应用都可以发送潜在的恶意广播到接收器中。这里有三种方法可以限制你的应用接收的广播:
    • 你可以在注册广播接收器时指定权限。
    • 对于manifest-declared接收器,你可以在manifest中设置android:exported属性为false。接收器就不会接收应用外部的广播。
    • 你可以通过LocalBroadcastManager限制你的应用。
  • 广播action的命名空间是全局的。确认action名和其他字符是依照你自己的命名空间写的,否则你可能会无意中与其他应用发生冲突。
  • 因为接收器的onReceive(Context, Intent)方法在主线程中运行,它应该快速执行并返回。如果你需要执行长时间的工作,要注意派生线程或启动后台服务,因为系统会在onReceive()返回后杀死整个进程。了解更多信息,查看Effect on process state来执行长时间的工作,我们建议:
    • 在你接收器的onReceive()方法中调用goAsync()并将BroadcastReceiver.PendingResult传递给后台线程。它会保持广播在onReceive()返回后继续活动。但是,即使使用这种方法,系统也希望你尽快结束广播(10秒内)。它还允许你将工作转移到另一个线程以避免妨碍主线程。
    • 使用JobScheduler调度作业。了解更多信息,查看Intelligent Job Scheduling。
  • 不要从广播接收器打开活动。因为这样的用户体验很突兀。尤其是当这里有多个接收器时。作为替代,请考虑显示一个notification。

你可能感兴趣的:(广播概述)