美文网首页面试
android BroadcastReceiver之无序广播、有

android BroadcastReceiver之无序广播、有

作者: duoduo7628 | 来源:发表于2021-03-31 17:26 被阅读0次

发送无序广播

    Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
    sendBroadcast(intent);

随机将给定的意图广播给所有感兴趣的BroadcastReceivers。 这个调用是异步的; 它立即返回,您将继续在接收器运行时执行。任何app注册都可以接收,可以使用intent.setPackage(String)指定接收应用。没有传播任何结果来自接收者,接收者不能中止广播。 如果你想要允许接收方传播结果或中止广播,您必须使用以下命令发送有序广播 sendOrderedBroadcast(Intent,String)

发送有序广播

    Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
    sendOrderedBroadcast(intent,null);

一次向一个接收器发送广播。当接收器逐个顺序执行时,接收器可以向下传递结果,也可以完全中止广播,使其不再传递给其他接收器。接收器的运行顺序可以通过匹配的 intent-filter 的 android:priority 属性来控制;具有相同优先级的接收器将按随机顺序运行。

关于 android:priority 的取值范围,官网给出的是 -1000 ~ 1000 ,但是看到很多人设置成2147483647(Integer.MAX_VALUE)这个值,可能因为 android:priority 的属性值是 integer 类型,系统会拿这个值和其他值做比较,结果怎么都是它最大了。

        <receiver android:name=".receiver.MyReceiver1"
            android:protectionLevel="normal"
            >
            <intent-filter
                android:priority="100">

                <action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
                <category android:name="android.intent.category.DEFAULT"/>

            </intent-filter>
        </receiver>

代码中设置priority

        IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi");
        intentFilter.setPriority(100);

中断有序广播

public class MyReceiver2  extends BroadcastReceiver {

    private static final String TAG = "MyReceiver2";

    @Override
    public void onReceive(Context context, Intent intent) {

        Toast.makeText(context,"MyReceiver2 收到广播",Toast.LENGTH_SHORT).show();
        Log.e(TAG, "onReceive: MyReceiver2 收到广播");
        //终止广播像低优先级传递
        abortBroadcast();
    }
}

发送本地广播

            Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.local");
            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);

BroadcastReceiver的设计初衷是全局性,可接收来自本应用和其他应用发过来的intent广播。这也同时给app带来了一定的安全风险。为了解决这个问题,LocalBroadcastManager横空出世。LocalBroadcastManager 只会将广播限定在当前应用程序中。LocalBroadcastManager 发送的广播不会离开你的应用程序,同样也不会接收来自其它应用程序的广播,因此你可以放心的在 LocalBroadcastManager 中传播敏感信息。同时由于LocalBroadcastManager不需要用到跨进程机制,因此相对 BroadcastReceiver 而言要更为高效。LocalBroadcastManager只在动态广播时使用,静态广播不能使用LocalBroadcastManager。

另外本地广播注册和反注册方式与有序无序广播不一样,需要用到 LocalBroadcastManager.getInstance(context).registerReceiver()

    LocalReceiver1 localReceiver1 = new LocalReceiver1();
    IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi.local");
    //注册本地广播
    LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver1,intentFilter);

    if(localReceiver1 != null){
        //反注册本地广播
        LocalBroadcastManager.getInstance(context).unregisterReceiver(localReceiver1);
    }

粘性广播

            Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.sticky");
            sendStickyBroadcast(intent);

粘性广播通过 Context.sendStickyBroadcast() 函数来发送,用此函数发送的广播会一直滞留,当有匹配此广播的广播接收器被注册后,该广播接收器就会收到此条广播。使用此函数发送广播时,需要获得 BROADCAST_STICKY 权限:<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
sendStickyBroadcast() 只保留最后一条广播,并且一直保留下去,这样即使已经有广播接收器处理了该广播,当再有匹配的广播接收器被注册时,此广播仍会被接收。如果你只想处理一遍该广播,可以通过 removeStickyBroadcast() 函数实现。

粘性广播在Android5.0(API 21)的时候就不推荐使用了,他们不提供安全性(任何人可以访问它们),没有保护(任何人都可以修改它们)以及许多其他问题。推荐使用非粘性广播,所以下文不再讨论粘性广播。

广播的安全性

因为只要注册了广播接收者,就可以收到广播,比如A、B接收者都注册了广播,当你只想向A接收者发送广播时,结果B接收者也能收到广播,也许数据就被泄露了,这是我们都不愿意看到的。所以BroadcastRecevier提供了限制接收者的方法,就是在发送时指定接收者权限receiverPermission

            Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
            String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
            sendBroadcast(intent,receiverPermission);

当然有序广播也可以指定接收者权限receiverPermission, 本地广播是不可以的。

            Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
            String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
            sendOrderedBroadcast(intent,receiverPermission);

要说明一下的是这里的receiverPermission,是一个自定义权限,需要在清单文件中定义后才能使用。如果没有声明自定义权限,发送的广播将没有符合要求接收者。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.noahedu.my">

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

    <!--自定义权限-->
    <permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name="MyApplication"
        android:theme="@style/AppTheme">

      ...
    </application>

</manifest>

这里的 <uses-permission /><permission />可能大家会不怎么明白,这里说一下。

<uses-permission /> :这个是申请权限。
比如要访问网络就使用<uses-permission android:name="android.permission.INTERNET"/>之后应用才有访问网络的权限。
<uses-permission android:name="android.permission.SEND_SMS"/>声明要使用这个权限后就可以发送短信了,当然这是个危险权限,android6.0之后就需要动态申请了。

<permission /> 这个是定义权限。就是声明一个新的权限,使用方法就如上面发送有权限限定的广播,应该还有其他使用方法,这里我暂时不知道。

当然这里也可以直接写系统定义的权限,这样可以不需要自定义权限,当然自定义权限安全性更高。

            Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
            sendBroadcast(intent,Manifest.permission.SEND_SMS);

这里不讨论系统权限,还是使用自定义权限,那既然有定义权限,就有使用权限的地方,是的,如果你想接收到有权限限定的广播,就必须申请使用发送广播时定义的权限,也就是使用 <uses-permission />,这里申请上面发送广播时定义的权限。

<uses-permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>

这里你会想到一个问题,即使是在应用内发送广播,应用内难道也要申请权限吗?是的,也要申请权限,不然也是收不到广播的。
这样之后,再按正常流程注册广播,就可以收到有权限的广播了。

限制广播发送者

可以限制接收者是否能收到广播,当然也有限制发送者的。
限制接收者是,发送的时候限定了权限。就是接收的地方,要声明权限,才能收到广播。
那么限制发送者,也就是在接收的地方定义权限了,在发送的地方就要申请权限。具体请看

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.noahedu.testmyservice">

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

    <!--定义权限-->
    <permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
      
        ...

        <!--android:permission 在广播接收者中使用定义的权限-->
        <receiver android:name=".receiver.ServiceReceiver"
            android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
            >
            <intent-filter>

                <action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
                <category android:name="android.intent.category.DEFAULT"/>

            </intent-filter>
        </receiver>

    </application>

</manifest>

这里receiverandroid:permission属性可以使用定义的权限。这样发送者必须申明使用这个权限,发送的广播这里才能收到。

  <uses-permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>

发送的应用,清单文件这样申请使用这个权限之后,发送广播,接收方就可以收到了。

简单总结一下就是:

  1. (发/收)自定义权限(<permission />)
  2. (发/收)设置自定义权限 sendOrderedBroadcast(Intent,String) 或 android:permission
  3. (收/发)申请使用自定义权限 <uses-permission />

到这里,其实大家也会想到一个问题,那就是可以双向绑定,这样安全性会更高!代码就不贴了

protectionLevel

但是这样还是不够安全,如果有人反编译了代码,就会发现自定义权限了,数据还是可能会泄漏,那有没有更高级的方法呢,如果是一个公司的两个app,使用相同的签名,可以将安全级别提升到签名级别,使用android:protectionLevel

        <receiver android:name=".receiver.ServiceReceiver"
            android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
            android:protectionLevel="signature">

            <intent-filter>

                <action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
                <category android:name="android.intent.category.DEFAULT"/>

            </intent-filter>
        </receiver>

这样只有具有相同签名的app,并且申请了对应权限,这里才会收到广播了。安全性会提高很多。
如果只是在本应用内使用,推荐使用本地广播LocalBroadcastManager,效率会更高。

广播的性能问题

如果很多app注册了开机广播<action android:name="android.intent.action.BOOT_COMPLETED"/>,那么系统会一个一个启动这些app进程,这样势必会消耗很多系统资源,对用户体验造成严重影响。这种情况建议动态注册。

安全注意事项和最佳做法

  • 如果您不需要向应用以外的组件发送广播,则可以使用支持库中提供的 LocalBroadcastManager 来收发本地广播。LocalBroadcastManager 效率更高(无需进行进程间通信),并且您无需考虑其他应用在收发您的广播时带来的任何安全问题。本地广播可在您的应用中作为通用的发布/订阅事件总线,而不会产生任何系统级广播开销。

  • 如果有许多应用在其清单中注册接收相同的广播,可能会导致系统启动大量应用,从而对设备性能和用户体验造成严重影响。为避免发生这种情况,请优先使用上下文注册而不是清单声明。有时,Android 系统本身会强制使用上下文注册的接收器。例如,CONNECTIVITY_ACTION 广播只会传送给上下文注册的接收器。

  • 请勿使用隐式 intent 广播敏感信息。任何注册接收广播的应用都可以读取这些信息。您可以通过以下三种方式控制哪些应用可以接收您的广播:

    • 您可以在发送广播时指定权限。
    • 在 Android 4.0 及更高版本中,您可以在发送广播时使用 setPackage(String) 指定软件包。系统会将广播限定到与该软件包匹配的一组应用。
    • 您可以使用 LocalBroadcastManager 发送本地广播。
  • 当您注册接收器时,任何应用都可以向您应用的接收器发送潜在的恶意广播。您可以通过以下三种方式限制您的应用可以接收的广播:

    • 您可以在注册广播接收器时指定权限。
    • 对于清单声明的接收器,您可以在清单中将 android:exported 属性设置为“false”。这样一来,接收器就不会接收来自应用外部的广播。
    • 您可以使用 LocalBroadcastManager 限制您的应用只接收本地广播。
  • 广播操作的命名空间是全局性的。请确保在您自己的命名空间中编写操作名称和其他字符串,否则可能会无意中与其他应用发生冲突。

  • 由于接收器的 onReceive(Context, Intent) 方法在主线程上运行,因此它会快速执行并返回。如果您需要执行长时间运行的工作,请谨慎生成线程或启动后台服务,因为系统可能会在 onReceive() 返回后终止整个进程。如需了解详情,请参阅对进程状态的影响。要执行长时间运行的工作,我们建议:

    • 在接收器的 onReceive() 方法中调用 goAsync(),并将 BroadcastReceiver.PendingResult 传递给后台线程。这样,在从 onReceive() 返回后,广播仍可保持活跃状态。不过,即使采用这种方法,系统仍希望您非常快速地完成广播(在 10 秒以内)。为避免影响主线程,它允许您将工作移到另一个线程。
    • 使用 JobScheduler 调度作业。如需了解详情,请参阅智能作业调度
  • 请勿从广播接收器启动 Activity,否则会影响用户体验,尤其是有多个接收器时。相反,可以考虑显示通知

参考:
goole官方-广播概览
Android四大组件之——广播
Android 广播权限保护
Android 基础知识3:四大组件之 Broadcast(广播)

相关文章

网友评论

    本文标题:android BroadcastReceiver之无序广播、有

    本文链接:https://www.haomeiwen.com/subject/cjeghltx.html