android开发笔记 (四)BroadcastReceiver

1 理解广播与广播接收器

  1. 广播事件处理属于系统级的事件处理(一般事件处理是属于View级的事件处理)
  2. 一个应用可以在发生特定事件时发送Broadcast, 系统中任何应用只要注册了对应Receiver就会接收到此Broadcast
  3. 一个应用如果对某个广播感兴趣, 就可以注册对应的Receiver来接收广播
  4. 广播事件机制是应用程序(进程间)之间通信的一种手段

2 相关API

Context:

  • sendBroadcast(Intent intent) : 发送一般广播
  • sendOrderedBroadcast(Intent intent) : 发送有序广播
  • registerReceiver(receiver, intentFilter) : 注册广播接收器
  • unRegisterReceiver(receiver) : 解注册广播接收器

BroadcastReceiver:

  • onReceive(Context context, Intent intent) : 接收到广播的回调
  • abortBroadcast() : 中断广播的继续传播
  • boolean isOrderedBroadcast() : 判断是否是有序广播

3 注意事项

1. Android 7.0 (API level 24) and higher don't send the following system broadcasts:

  • ACTION_NEW_PICTURE
  • ACTION_NEW_VIDEO

Also, apps targeting Android 7.0 and higher must register the CONNECTIVITY_ACTION broadcast using registerReceiver(BroadcastReceiver, IntentFilter). Declaring a receiver in the manifest doesn't work.

也就是说7.0及以上系统不再发送ACTION_NEW_PICTURE和ACTION_NEW_VIDEO广播,而且注册 CONNECTIVITY_ACTION广播必须动态注册

 

2.Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.

If your app targets Android 8.0 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don't target your app specifically). You can still use a context-registered receiver when the user is actively using your app.

从8.0开始,不能用静态注册的方式注册隐式广播接收器

 

3.Beginning with Android 9 (API level 28), The NETWORK_STATE_CHANGED_ACTION broadcast doesn't receive information about the user's location or personally identifiable data.

In addition, if your app is installed on a device running Android 9 or higher, system broadcasts from Wi-Fi don't contain SSIDs, BSSIDs, connection information, or scan results. To get this information, call getConnectionInfo() instead.

从9.0开始, NETWORK_STATE_CHANGED_ACTION这个广播中的信息不再包含用户的位置和一些个人信息

 

4 测试用例

4.1 首先定义一个广播接收器类

package com.example.broadcastreceivertest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver extends BroadcastReceiver {

    public MyReceiver() {
        Log.i("TAG", "MyReceiver: ");
    }

    
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("TAG", "onReceive: ");
        Log.i("TAG", "data: " + intent.getStringExtra("data"));
    }
}

BroadcastReceiver的状态影响其所在进程的状态,进而影响这个进程被系统杀死的可能性。例如,一个进程包含一个BroadcastReceiver,并且这个BroadcastReceiver的onReceive()方法正在执行,那么这个进程就被认为是一个前台进程,除了内存极度缺乏的情况下,这个进程将保持存活。

然而一旦这个BroadcastReceiver的onReceive()方法执行完毕,那么这个BroadcastReceiver就不再活跃了,持有这个reveiver的进程的优先级就依赖于其他组件了(如activity,service等)。如果这个进程仅有一个静态注册的receiver,并且用户没有与这个进程进行交互或者最近没有与这个进程进行交互,那么系统就会认为这个进程的优先级比较低,可能会杀死这个进程,腾出资源为其他更重要的进程提供服务。

因此,不应该在onReceive()方法中开启一个分线程去执行长时间的后台任务,系统随时可能杀死这个进程,并且结束掉依赖于这个进程的线程。为了避免这种情况,在需要更多时间去处理任务的时候,可以使用goAsync()或者使用JobScheduler从接收器调度JobService,以便系统知道该进程继续执行活动 工作。

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

    @Override
    public void onReceive(Context context, Intent intent) {
        final PendingResult pendingResult = goAsync();
        Task asyncTask = new Task(pendingResult, intent);
        asyncTask.execute();
    }

    private static class Task extends AsyncTask {

        private final PendingResult pendingResult;
        private final Intent intent;

        private Task(PendingResult pendingResult, Intent intent) {
            this.pendingResult = pendingResult;
            this.intent = intent;
        }

        @Override
        protected String doInBackground(String... strings) {
            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);
            return log;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            // Must call finish() so the BroadcastReceiver can be recycled.
            pendingResult.finish();
        }
    }
}

4.2 然后注册广播接收器

4.2.1 静态注册

8.0以后这样注册隐式广播接收不到消息


            
                
            

4.2.2 动态注册

IntentFilter intentFilter = new IntentFilter("android.intent.action.test_data");
if (myReceiver == null) {
    myReceiver = new MyReceiver();
}
registerReceiver(myReceiver, intentFilter);

动态注册的广播必须在context(比如activity)死亡前反注册,否则应用会crash,如果使用applicationContext注册,则广播接收器的生命周期就是app存活的周期

@Override
    protected void onDestroy() {
        unregisterReceiver(myReceiver);
        super.onDestroy();
    }

4.3 发送广播

4.3.1 发送普通广播

Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","这是我的测试数据");
sendBroadcast(intent);

sendBroadcast(Intent)方法发送的广播是不带有顺序的,几乎所有有效注册了的广播接收器都能收到此广播,也被称为正常广播。

4.3.2 发送有序广播

Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","这是我的测试数据");
sendBroadcast(intent);

sendOrderedBroadcast(Intent,String)方法一次向一个接收器发送广播。 当每个接收器依次执行时,它可以将结果传播到下一个接收器,或者它可以完全中止广播,以便它不会传递给其他接收器。 可以使用匹配的intent-filter的android:priority属性来控制运行的订单接收器;priority的值越大优先级越高, 具有相同优先级的接收器将以任意顺序运行。

4.3.3 发送本地广播

Intent intent = new Intent("android.intent.action.test_data");
intent.putExtra("data","这是我的测试数据");
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);

LocalBroadcastManager.sendBroadcast方法将广播发送到与发送方位于同一应用程序中的接收方。 如果不需要跨应用程序发送广播,最好使用本地广播。 实现效率更高(无需进程间通信),也不需要担心与其他应用程序能够接收或发送广播相关的任何安全问题。

 

5 用权限来限制广播

权限允许将广播限制为具有特定权限的应用程序集, 可以对广播的发送者或接收者实施限制。

5.1 发送带权限的广播

当调用sendBroadcast(Intent,String)或sendOrderedBroadcast(Intent,String,BroadcastReceiver,Handler,int,String,Bundle)时,您可以指定权限参数。 只有那些已经在其清单文件中申请了权限的接收者(并且如果它是危险的,则随后需要动态授权)可以接收广播。 例如,以下代码发送广播:

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

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

可以指定现有系统权限(如SEND_SMS)或使用元素定义自定义权限。

注意:安装应用程序时会注册自定义权限。 必须在使用该应用程序之前安装定义自定义权限的应用程序。

5.2 接收带权限的广播

如果在注册广播接收器时指定了权限参数(使用registerReceiver(BroadcastReceiver,IntentFilter,String,Handler)或清单文件中的标记),那么必须在清单文件中使用请求权限(并且如果它是危险的,则随后要被动态授权),才可以向接收者发送intent。

例如,接收应用程序具有清单声明的接收器,如下所示:


    
        
    

或者接收应用程序有像如下方式动态注册广播接收器:

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

然后,为了能够向这些接收者发送广播,发送应用必须请求许可,如下所示:

也就是说无论是发送方还是接收方都必须像上面的代码这种方式,在清单文件中授权权限才能正常收发广播,如果是危险权限还需要动态授予才行。

6 一些安全注意事项和最佳做法

  • 如果不需要向应用程序外部的组件发送广播,则使用支持库中提供的LocalBroadcastManager发送和接收本地广播。 LocalBroadcastManager效率更高(无需进程间通信),并且不用考虑与其他应用程序能够接收或发送您的广播相关的任何安全问题。
  • 如果许多应用在清单文件中注册接收相同的广播,则可能导致系统启动大量应用,从而对设备性能和用户体验产生重大影响。为避免这种情况,优先使用上下文注册而不是静态注册,Android系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION广播仅传递给上下文注册的接收器。
  • 不要使用隐式意图广播敏感信息。任何注册广播接收器的应用都可以读取该信息。有三种方法可以控制谁可以接收您的广播:

    1. 您可以在发送广播时指定权限。
    2. 在Android 4.0及更高版本中,您可以在发送广播时指定包含setPackage(String)的包。系统将广播限制为与包匹配的应用程序集。
    3. 您可以使用LocalBroadcastManager发送本地广播。

  • 当您注册接收器时,任何应用都可以向您的应用接收器发送潜在的恶意广播。有三种方法可以限制应用收到的广播:

    1. 您可以在注册广播接收器时指定权限。
    2. 对于清单声明的接收器,您可以在清单中将android:exported属性设置为“false”。接收方不接收来自应用程序之外的来源的广播。
    3. 您可以将自己限制为仅使用LocalBroadcastManager进行本地广播。

  • 广播操作的命名空间是全局的。确保操作名称和其他字符串都写在您拥有的命名空间中,否则您可能会无意中与其他应用程序发生冲突

  • 因为接收者的onReceive(Context,Intent)方法在主线程上运行,所以它应该快速执行并返回。如果需要执行长时间运行的工作,请注意生成线程或启动后台服务,因为系统可能会在onReceive()返回后终止整个进程。

    建议在接收者的onReceive()方法中调用goAsync()并将BroadcastReceiver.PendingResult传递给后台线程。这使得从onReceive()返回后广播保持活动状态。但是,即使采用这种方法,系统也希望您能够非常快速地完成广播(10秒以内)。它允许您将工作移动到另一个线程,以避免故障主线程。

你可能感兴趣的:(android开发笔记 (四)BroadcastReceiver)