Android 广播详解与本地广播 LocalBroadcastManager

3,568 阅读3分钟

概述

广播是 Android 四大组件之一,是 Android 系统提供的一种通讯方式。Android 中的广播机制允许程序可以只对自己感兴趣的广播进行注册,使得程序只会接收到自己所关心的广播内容。

Android 中的广播有来自系统的,比如电量变化、wifi 连接变化开机完成等,还有来自其他应用程序的,如自定义广播等。

Android 系统中要接受广播就需要使用 BroadcastReceiver

广播的应用场景

  1. 不同组件间的通信(含: 应用内和不同应用之间)
  2. 多线程通信
  3. 组件和系统之间的通信(比如网路变化)

广播类型

  • 标准广播:一种完全异步执行的广播,发出广播后,该广播事件的接收者,几乎会在同一时刻收到通知,都可以响应或不响应该事件
  • 有序广播:一种同步执行的广播,发出广播后,同一时刻,只有一个广播接收者能收到、一个接收者处理完后之后,可以选择继续向下传递给其它接收者,也可以拦截掉广播。

注册方式

上面我们说过 Android 中的广播机制允许程序可以只对自己感兴趣的广播进行注册。这里的“注册”又可以分为两种:

  • 动态注册: 利用代码的方式注册【推荐使用】
  • 静态注册:在清单文件中注册,从 Android 8.0 开始,所有隐式广播都不允许使用静态注册的方式来接收了,建议大家不要在清单文件中静态注册广播接收者

示例 1:接收系统的广播

这里以系统广播中的关闭或打开飞行模式为例子,当我们关闭或打开飞行模式时,系统会发一条 Intent.ACTION_AIRPLANE_MODE_CHANGED 广播。广播的实现基本上就两个步骤:

  1. 实现 BroadcastReceiver,重写 onReceive() 函数
  2. 注册 BroadcastReceiver

实现 BroadcastReceiver 的代码如下:

class TestBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {
            Intent.ACTION_AIRPLANE_MODE_CHANGED -> 
                Toast.makeText(context, "您的飞行模式状态改变了", Toast.LENGTH_SHORT).show()
        }
    }
}

注意:这里的 onReceive() 函数是在主线程中,不要处理耗时的操作。也不要开启一个子线程去执行耗时操作,因为会出现在子线程还没有结束的情况下,Activity 已经被用户退出了,或者 BroadcastReceiver 已经结束了的情况。

注册 BroadcastReceiver 的代码如下:

class TestBroadcastReceiverActivity : AppCompatActivity() {

    private lateinit var mReceiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_broadcast_receiver)

        mReceiver = TestBroadcastReceiver()
        val intentFilter = IntentFilter()
        intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
        registerReceiver(mReceiver, intentFilter)

    }

    override fun onDestroy() {
        super.onDestroy()

        unregisterReceiver(mReceiver)
    }
}

效果如下所示:

示例1效果

示例 2:发送自定义广播并接收数据

在发送广播之前,我们同样需要实现 BroadcastReceiver,重写 onReceive() 函数,因为这样我们发出去的自定义广播才能被接收。代码如下所示:

class TestBroadcastReceiver(private val tv: TextView?) : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {

            Intent.ACTION_AIRPLANE_MODE_CHANGED ->
                Toast.makeText(context, "您的飞行模式状态改变了", Toast.LENGTH_SHORT).show()

            "com.geely.interview.TEST_BROADCAST_RECEIVER" -> {
                val data = intent.getStringExtra("content")
                Toast.makeText(context, "接收到了自定义广播,携带的数据为$data", Toast.LENGTH_LONG).show()
                tv?.let { it.text = data }
            }
        }
    }
}

这里我在主构造函数中传入 TextView,主要是为了一会将接收到的值设置到界面中,这里可以用接口,也可以将 TestBroadcastReceiver 直接写在 Activity 里面等,这里我为了简单起见,直接将 TextView 从构造函数中传过来了。后面的示例 5,改成了利用接口来实现将值设置到界面中。

发送广播只需要构建一个 Intent 对象,并把要发送的广播的值传入,然后调用 sendBroadcast() 方法就可以将广播发送出去了。具体代码如下所示:

class TestSendBroadcastActivity : AppCompatActivity() {

    lateinit var mReceiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_send_broadcast)

        mReceiver = TestBroadcastReceiver(tv_content)
        val intentFilter = IntentFilter("com.geely.interview.TEST_BROADCAST_RECEIVER")
        registerReceiver(mReceiver, intentFilter)

        btn_send.setOnClickListener {
            val intent = Intent("com.geely.interview.TEST_BROADCAST_RECEIVER")
            intent.putExtra("content", et_content.text.toString())
            sendBroadcast(intent)
        }
    }

    override fun onDestroy() {
        super.onDestroy()

        unregisterReceiver(mReceiver)
    }
}

完整效果如下所示: 示例2效果

示例 3:静态注册实现示例 2

上述的示例 1 和示例 2 我们都是通过动态注册广播的,这也正是推荐的方法,但是我们还是需要知识静态注册是如何使用的,我们将示例 2 改成静态注册的。首先我们将动态注册的代码去掉:

class TestSendBroadcastActivity : AppCompatActivity() {

//    lateinit var mReceiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_send_broadcast)

//        mReceiver = TestBroadcastReceiver(tv_content)
//        val intentFilter = IntentFilter("com.geely.interview.TEST_BROADCAST_RECEIVER")
//        registerReceiver(mReceiver, intentFilter)

        btn_send.setOnClickListener {
            val intent = Intent("com.geely.interview.TEST_BROADCAST_RECEIVER")
            intent.putExtra("content", et_content.text.toString())
            sendBroadcast(intent)
        }
    }

    override fun onDestroy() {
        super.onDestroy()

//        unregisterReceiver(mReceiver)
    }
}

然后在 AndroidManifest.xml 中静态注册 BroadcastReceiver,代码如下所示:

<receiver android:name=".broadcast.TestBroadcastReceiver">
    <intent-filter>
        <action android:name="com.geely.interview.TEST_BROADCAST_RECEIVER"/>
    </intent-filter>
</receiver>

Android 8.0 之前,这样就可以了,但是我们前面已经说过,在 Android 8.0 系统之后,静态注册的 BroadcastReceiver 是无法接收隐式广播的,而默认情况下我们发出的自定义广播恰恰都是隐式广播。

所以我们需要调用 IntentsetPackage() 方法,并传入当前应用程序的包名。使得这条广播变成显示广播或者通过 Intent.setComponent(包名, 接收广播类的完整类名)来显示的设置广播接收者。

代码如下所示:

class TestSendBroadcastActivity : AppCompatActivity() {

//    lateinit var mReceiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_send_broadcast)

//        mReceiver = TestBroadcastReceiver(tv_content)
//        val intentFilter = IntentFilter("com.geely.interview.TEST_BROADCAST_RECEIVER")
//        registerReceiver(mReceiver, intentFilter)

        btn_send.setOnClickListener {
            val intent = Intent("com.geely.interview.TEST_BROADCAST_RECEIVER")
            intent.putExtra("content", et_content.text.toString())
            
            // 下面两种方法都可以
            intent.setPackage(packageName)
//            intent.component = ComponentName(packageName, "com.xt.interview.broadcast.TestBroadcastReceiver")
            
            sendBroadcast(intent)
        }
    }

    override fun onDestroy() {
        super.onDestroy()

//        unregisterReceiver(mReceiver)
    }
}

这样我们就实现了通过静态注册来发送并接收广播。

示例 4:不同应用间广播的发送与接收

Android 中的 BroadcastReceiver 不仅能接收系统和本应用的广播,还能收到其他应用的广播,只要自定义的 Action 一样就可以了。

这里我将这个项目复杂了一份,并将包名改成不一样的。然后将这个两个应用都装到手机上,取名分别为 demo1demo2

1632382145(1).png

因为 demo1demo2 的代码完全一样,所以这两个自定义广播的 Action 都是 "com.geely.interview.TEST_BROADCAST_RECEIVER",所以当我在 demo1 中发送广播的时候,demo2 也可以接收到,并且也可以正确解析出广播所携带的数据。

效果如下所示:

ezgif.com-gif-maker (1).gif

可以看到当我们在 demo1 中发送广播的时候,demo2 也可以接收到,并且也成功的解析出了广播所携带的数据。

但是有些时候我们不希望自己的广播被别的 App 监听到,因为我们广播里面可能携带了一些隐式数据,这时候要怎么办呢?这时候我们就可以使用 LocalBroadcastManager 来发送应用内广播。

  1. App 应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个 App
  2. 相比于全局广播(普通广播),App 应用内广播优势体现在:安全性高 & 效率高

示例 5:利用 LocalBroadcastManager 改写示例 4

其实这个改写很简单,只需要改写注册和发送这两个地方即可。

// 使用 LocalBroadcastManager 来注册广播
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, intentFilter)

// 使用 LocalBroadcastManager 来发送广播
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)

TestBroadcastReceiver 代码如下:

class TestBroadcastReceiver: BroadcastReceiver() {

    private lateinit var tvInteraction: TVInteraction

    override fun onReceive(context: Context?, intent: Intent?) {
        when (intent?.action) {

            Intent.ACTION_AIRPLANE_MODE_CHANGED ->
                Toast.makeText(context, "您的飞行模式状态改变了", Toast.LENGTH_SHORT).show()

            "com.geely.interview.TEST_BROADCAST_RECEIVER" -> {
                val data = intent.getStringExtra("content")
                Toast.makeText(context, "接收到了自定义广播,携带的数据为$data", Toast.LENGTH_LONG).show()
                // 判断延迟初始化的变量是否已经初始化了
                if (::tvInteraction.isInitialized) {
                    tvInteraction.setText(data)
                }
            }
        }
    }

    fun setTVInteractionListener(mtvInteraction: TVInteraction) {
        tvInteraction = mtvInteraction
    }

    // 利用接口的方式将数据传递出去
    interface TVInteraction {
        fun setText(content: String?)
    }
}

TestSendBroadcastActivity 代码如下:

class TestSendBroadcastActivity : AppCompatActivity(), TestBroadcastReceiver.TVInteraction {

    private lateinit var mReceiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_send_broadcast)

        mReceiver = TestBroadcastReceiver()
        val intentFilter = IntentFilter("com.geely.interview.TEST_BROADCAST_RECEIVER")
        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, intentFilter)

        mReceiver.setTVInteractionListener(this)

        btn_send.setOnClickListener {
            val intent = Intent("com.geely.interview.TEST_BROADCAST_RECEIVER")
            intent.putExtra("content", et_content.text.toString())
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
        }
    }

    override fun onDestroy() {
        super.onDestroy()

        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver)
    }

    override fun setText(content: String?) {
        content?.let {
            tv_content.text = it
        }
    }
}

这样改写完以后,广播就无法被其他应用所接收了。