详解广播机制(上)

总结自郭神的《第一行代码》

Android中的广播类型

Android中的广播主要可以分为两种类型,标准广播和有序广播

标准广播(Normal broadcasts)
是一种完全异步执行的广播,在广播发出之后,所有的 广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可 言。这种广播的效率会比较高,但同时也意味着它是无法被截断的

这里写图片描述

有序广播(Orderedbroadcasts)
则是一种同步执行的广播,在广播发出之后,同一时刻 只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广 播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先 收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器 就无法收到广播消息了

这里写图片描述

接收系统广播

Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到 各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一 条广播,时间或时区发生改变也会发出一条广播等等。如果想要接收到这些广播,就需要使 用广播接收器,下面我们就来看一下它的具体用法

动态注册监听网络变化
广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广 播接收器就能够收到该广播,并在内部处理相应的逻辑

注册广播的方式一般有两种,在代 码中注册和在 AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态 注册

那么该如何创建一个广播接收器呢?
其实只需要新建一个类,让它继承自BroadcastReceiver, 并重写父类的 onReceive()方法就行了
这样当有广播到来时,onReceive()方法就会得到执行, 具体的逻辑就可以在这个方法中处理

动态注册

栗子:网络变化时提示

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

public class Test extends AppCompatActivity {
    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");

        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver,intentFilter);
    }

    class NetworkChangeReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
        }
    }

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

运行结果:当网络发生变化时,提示信息

对代码的解释

我们在 MainActivity 中定义了一个内部类 NetworkChangeReceiver,这个类 是继承自 BroadcastReceiver的,并重写了父类的 onReceive()方法。这样每当网络状态发生变 化时,onReceive()方法就会得到执行,这里只是简单地使用 Toast提示了一段文本信息

然后观察 onCreate()方法,首先我们创建了一个 IntentFilter的实例,并给它添加了一个 值为 android.net.conn.CONNECTIVITY_CHANGE 的 action,为什么要添加这个值呢?因为 当网络状态发生变化时,系统发出的正是一条值为 android.net.conn.CONNECTIVITY_ CHANGE 的广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的 action 就行了

接下来创建了一个 NetworkChangeReceiver的实例,然后调用 registerReceiver() 方法进行注册,将 NetworkChangeReceiver 的实例和 IntentFilter 的实例都传了进去,这样 NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广 播,也就实现了监听网络变化的功能

最后要记得,动态注册的广播接收器一定都要取消注册才行,这里我们是在 onDestroy() 方法中通过调用 unregisterReceiver()方法来实现的

栗子优化

不过只是提醒网络发生了变化还不够人性化,最好是能准确地告诉用户当前是有网络还 是没有网络,因此我们还需要对上面的代码进行进一步的优化

class NetworkChangeReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if(networkInfo != null && networkInfo.isAvailable()){
                Toast.makeText(context, "network is available", Toast.LENGTH_SHORT).show();
            }else{
                Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();
            }
            Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
        }
    }

这里查询系统的网络状态就是需要声明权限的。打开 AndroidManifest.xml文件,在里面加入如下权限就可以查询系统网络状态了

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

运行结果:打开网络,提示 network is available,关闭网络提示network is unavailable

对代码的解释

onReceive()方法中,首先通过 getSystemService()方法得到了 ConnectivityManager的 实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的 getActiveNetworkInfo() 方法可以得到 NetworkInfo的实例,接着调用 NetworkInfoisAvailable()方法,就可以判断 出当前是否有网络了,最后我们还是通过 Toast 的方式对用户进行提示

下面用 kotlin 来监听时间变化

class MainActivity : AppCompatActivity() {
    lateinit var timeChangeReceiver: TimeChangeReceiver

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

        val intentFilter = IntentFilter()
        intentFilter.addAction("android.intent.action.TIME_TICK")
        timeChangeReceiver = TimeChangeReceiver()
        registerReceiver(timeChangeReceiver,intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeChangeReceiver)
    }

    inner class TimeChangeReceiver: BroadcastReceiver(){
        override fun onReceive(p0: Context?, p1: Intent?) {
            Toast.makeText(p0,"Time has changed",Toast.LENGTH_SHORT).show()
        }
    }
}

系统每隔一分钟会发出一条 android.intent.action.TIME_TICK 的广播,因此我们最多秩序等待一分钟就可以接收到这条广播了

静态注册实现开机启动

动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是 它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在 onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下就能接收到广播 呢?这就需要使用静态注册的方式了

这里我们准备让程序接收一条开机广播,当收到这条广播时就可以在 onReceive()方法里 执行相应的逻辑,从而实现开机启动的功能

栗子

新建一个 BootCompleteReceiver 继承自 BroadcastReceiver

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BootCompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xx.yy">

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

    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootCompleteReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

对代码的解释

AndroidManifest.xml的<application>标签内出现了一个新的标签<receiver>,所有静态注册的广播接收器都是在这里进行注册的。首先通过 android:name 来指定具体注册哪一个广播接收器,然后在<intent-filter>标签里加入想要接收的广播就行了, 由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED的广 播,因此我们在这里添加了相应的 action

监听系统开机广播也是需要声明权限的,可以看到,我们使用<uses-permission> 标签又加入了一条 android.permission.RECEIVE_BOOT_COMPLETED权限

长按模拟机关机键,选择 Restart 等重启后稍等,就能看到弹出信息了
在这里插入图片描述

Kotlin 版本
我们使用 Kotlin 再完成一遍这个功能
New - Other - Broadcast Receiver
在这里插入图片描述
Exported 属性表示是否允许这个 BroadcastReceiver 接收本程序以外的广播
Enabled 属性表示是否启用这个 BroadcastReceiver

class BootCompleteReceiver: BroadcastReceiver(){
    override fun onReceive(p0: Context?, p1: Intent?) {
        Toast.makeText(p0,"Boot Complete",Toast.LENGTH_SHORT).show()
    }
}

AndroidManifest 中

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xx.kotlinapplication">
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <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">
        ......
        <receiver android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

发送标准广播

先定义一个广播接收器来准备接收此广播,新建一个MyBroadcastReceiver继承自BroadcastReceiver

class MyBroadcastReceiver: BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
        Toast.makeText(p0,"Received",Toast.LENGTH_SHORT).show()
    }
}

在 AndroidManifest.xml中对这个广播接收器进行注册

<receiver android:name=".MyBroadcastReceiver">
           <intent-filter>
               <action android:name="com.xx.kotlinapplication.BroadcastReceiver"/>
           </intent-filter>
       </receiver>

布局文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/newsTitle"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="send"/>

</FrameLayout>

Activity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent = Intent("com.xx.kotlinapplication.BroadcastReceiver")
            intent.setPackage(packageName)
            sendBroadcast(intent)
        }
    }
}

运行结果:
当MyBroadcastReceiver收到自定义的广播时,就会弹出提示
在这里插入图片描述

对代码的解释
其中 setPackage()方法需要进一步说明。在 Android 8.0 之后,静态注册的 BroadcaseReceiver 是无法接收隐式广播的,而默认情况下我们给系统发出的自定义广播恰恰都是隐式广播。因此这里一定要调用 setPackage() 方法,指定这条广播发给哪个应用程序,从而让它变成一条显示广播,否则静态注册的 BroadcaseReceiver 将无法收到这条广播

因为是使用 Intent 发送的,还可以携带一些信息数据传递给相应的 BraodcastReceiver

发送有序广播

和标准广播不同,有序广播是一种同步执行的广播,可以被截断。建一个新的 BroadcastReceiver 命名为 AnotherBroadcastReceiver

class AnotherBroadcastReceiver:BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
        Toast.makeText(p0,"Received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show()
    }
}

AndroidManifest.xml注册

<receiver android:name=".AnotherBroadcastReceiver">
            <intent-filter>
                <action android:name="com.xx.kotlinapplication.BroadcastReceiver"/>
            </intent-filter>
</receiver>

AnotherBroadcastReceiver 同 样 接 收 的 是"com.xx.kotlinapplication.BroadcastReceiver" 这条广播。重新运行项目,点击按钮会发现弹出两次提示
在这里插入图片描述

不过到目前为止,程序里发出的都还是标准广播,现在我们来尝试一下发送有序广播,修改 MainActivity 中代码

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent = Intent("com.xx.kotlinapplication.BroadcastReceiver")
            intent.setPackage(packageName)
            sendOrderedBroadcast(intent,null)
        }
    }
}

运行结果:
现在重新运行程序, 并点击按钮,仍然是弹出两条消息

对代码的解释
sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是 Intent,第二个参数是一个与权限相关的字符串,这里传入 null就行了

看上去好像和标准广播没什么区别嘛,不过别忘了,这个时候的广播接收器是有先后顺 序的,而且前面的广播接收器还可以将广播截断,以阻止其继续传播

那么该如何设定广播接收器的先后顺序呢?当然是在注册的时候进行设定的了,修改 AndroidManifest.xml中的代码

<receiver android:name=".MyBroadcastReceiver"
           android:exported="true"
           android:enabled="true">
           <intent-filter android:priority="100">
               <action android:name="com.xx.kotlinapplication.BroadcastReceiver"/>
           </intent-filter>
       </receiver>

我们通过 android:priority属性给广播接收器设置了优先级,优先级比较高的 广播接收器就可以先收到广播。这里将 MyBroadcastReceiver 的优先级设成了 100,以保证它 一定会在 AnotherBroadcastReceiver 之前收到广播

既然已经获得了接收广播的优先权,那么 MyBroadcastReceiver 就可以选择是否允许广 播继续传递了。修改 MyBroadcastReceiver中的代码

class MyBroadcastReceiver: BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
        Toast.makeText(p0,"Received",Toast.LENGTH_SHORT).show()
        abortBroadcast()
    }
}

如果在 onReceive() 方法中调用了 abortBroadcast()方法,就表示将这条广播截断,后面的 广播接收器将无法再接收到这条广播。现在重新运行程序,并点击一下按钮, 你会发现,只有 MyBroadcastReceiver 中的 Toast 信息能够弹出,说明这条广播经过 MyBroadcastReceiver之后确实是终止传递了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值