「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)

文章目录

        • 广播机制简介
        • 接收系统广播
          • 动态注册监听网络变化
          • 静态注册实现开机启动
        • 发送自定义的广播
          • 发送标准广播
          • 发送有序广播

广播机制简介

Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接受到自己所关心的广播内容,这些广播可能是来自系统的,也可能是来自其他应用程序的。

发送广播借助Intent
接收广播需要借助广播接收器

android中的广播主要可以分为两种类型:标准广播有序广播
标准广播(Normal broadcasts)
是一种完全异步执行的广播,在广播发出之后,所有广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。标准广播的工作流程如图 5.1 所示。

「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)_第1张图片

有序广播(Ordered broacasts)
则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。有序广播的工作流程如图 5.2 所示。
「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)_第2张图片

接收系统广播

广播接收器可以自由地对自己感兴趣的广播进行注册。
注册广播一般有两种,在代码中注册和在AndroidManifest.xml中注册,前者成为动态注册,后者称为静态注册

动态注册监听网络变化

如何创建一个广播接收器呢?只需要创建一个类,然后让它继承BroadcastReceiver,并重写父类的onReceive()方法就行,这样当有广播来的时候,onReceive()方法就能得到实行

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver,intentFilter);
    }

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

    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();
            }
        }
    }
}

在onCreate()方法中,我们首先创建一个IntentFilter实例,并给它们添加了一个值为android.net.conn.CONNECTIVITY_CHANGE的action,为什么要添加这个值呢?因为当网络发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_CHANGE的广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action。接下来创建了一个NetworkChangeReceiver的实例,然后调用registReceiver()方法进行注册,将NetworkChangeRecevier的实例和IntentFilter的实例都传了过去,这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播,也就实现了监听网络变化的功能。

在onReceive()方法中,首先通过getSystemService()方法得到ConnectivityManager的实例,这是一个系统服务类,专门用来管理网络连接的。然后调用它的getActiveNetworkInfo()方法可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable()方法,就可以判断当前是否有网络了,最后通过Toast显示下。
另外非常重要的一点是,Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序会直接崩溃,比如这里访问系统的网络状态就是需要声明权限,打开AndroidMainfest.xml文件,在里面加入如下权限就可以访问系统网络状态了。


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">
    
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
	.........

manifest>

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

静态注册实现开机启动

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

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

仍旧是接着上面的代码来
我们用Android Studio提供的快捷方式来创建一个广播接收器,右击MainActivity所在的包->new->Other->Broadcast Receiver,将广播接收器的名称命名为BootCompleteReceiver,Exported属性表示是否允许这个广播接收器接收本程序外的广播,Enabled属性表示是否启用这个广播接收器,勾选后点击Finish完成构建。

public class BootCompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show();
    }
}

注意:静态的广播接收器一定要在AndroidManifest.xml文件注册才可以使用,不过Android Stutio已经帮我们在AndroidManifest.xml注册好了。


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

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

    <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">
        receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

manifest>

可以看到标签内出现一个新的标签,所有静态的广播接收器都是在这里注册的,它的用法和标签非常类似,通过android:name来指定具体注册哪一个广播接收器,而enabled和exported属性则是根据我们刚才的勾选的状态自动生成。

不过目前BootCompleteReceiver还是不能接受到开机广播的,我们还需要对AndroidManifest.xml文件进行注册


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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <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>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

manifest>

由于Android系统启动完成后会自动发出一条值为android.intent.action.BOOT_COMPLETED的广播,因此我们在 标签里加入相应的action,另外监听系统开机广播也是需要声明权限的,可以看到我们使用标签有加入一条android.permission.RECEIVE_ROOT_COMPLETED权限。

这时,运行程序后,并重启模拟器,Toast会显示出一条信息。

到目前为止,我们在广播接收器的 onReceive() 方法中都只是简单地使用 Toast 提示了一段文本信息,当你真正在项目中使用到它的时候,就可以在里面编写自己的逻辑。需要注意的是,不要在 onReceive() 方法中添加过多的逻辑或者进行任何耗时操作,因为在广播接收器中是不允许开启线程的,当 onReceive() 方法运行了较长时间而没有结束时,程序就会报错。因此广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动一个服务等。

发送自定义的广播

发送标准广播

在发送广播之前,我们还是需要先定义一个广播接收器来准备接收此广播才行,不然发出去也是白发。
首先我们新建一个项目,名为BroadcasstTest。然后我们先静态注册一个广播接收器:
右击包名,在打开的快捷菜单中选择New–Other–Broadcast Receiver
然后输入名字,我这里输入的是MyReceiver,并勾选enable和exported
enable表示是否启用
exported表示是否接收本程序以外的广播

public class MyReceiver extends BroadcastReceiver {

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

我们再修改AndroidManitest.xml中的代码


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

    <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=".MyReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="com.example.broadcassttest.MY_BROADCAST"/>
            intent-filter>
        receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

manifest>

这里我们让MyReceive接收一条值为com.example.broadcassttest.MY_BROADCAST的广播,因为我们待会发送广播的时候,我们就需要发出这样一条广播。
修改activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

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

LinearLayout>

我们在布局中添加一个按钮,作为发送广播的触发点
最后修改MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button  = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.example.broadcassttest.MY_BROADCAST");
                intent.setPackage(getPackageName());
                sendBroadcast(intent);
            }
        });
    }
}

我们在点击事件中首先构建一个Intent对象,并把要发送的广播的值传入,然后调用Intent的setPackage()方法,并传入包 名,最后调用sendBroadcast()方法将广播发送出去,这样所有监听com.example.broadcassttest.MY_BROADCAST的BroadcastReceiver都会接受到消息,你还可以在Intent中携带一些数据传递给相应的BroadcastReceiver。
对于调用Intent的setPackage()方法这儿进行更详细说明的话,就是Android 8.0系统之后,静态注册的BroadcastReceiver是无法接收到隐式广播的,而默认情况下我们发出的广播都是隐式的,因此这里我们一定要调用setPackage()方法,指定这条广播时发给哪个应用程序的,从而让它称为一条显式广播。

发送有序广播

和标准广播不同,有序广播是一种同步执行的广播,并且是可以被截断的。为了验证这一点,接着上面的BroadcasstTest项目,我们需要再创建一个新的BroadcastReceiver,名为AnotherBroadcastReceiver,代码如下

public class AnotherBroadcastReceiver extends BroadcastReceiver {

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

然后修改AndroidManifest.xml代码

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

    <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=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="com.example.broadcassttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

运行后,点击按钮,可以发现会弹出两次提示信息。

「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)_第3张图片
「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)_第4张图片
值的一提的是,上面两个提示出现的顺序和你AndroidManifest.xml中标签出现的顺序有关。

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

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button  = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.example.broadcassttest.MY_BROADCAST");
                intent.setPackage(getPackageName());
                sendOrderedBroadcast(intent,null);
            }
        });
    }
}

可以看到,发送有序广播只需要在原来的代码上改动一行,即将sendBroadcast()方法改为sendOrderBroadcast()方法,它有两个参数,第一个仍然是Intent,第二个是一个与权限有关的字符串,这里传入null就行了。

重新运行一下,发现和原来的程序似乎并没有什么区别。不过,这个时候的广播接收器是有先后顺序的,而且前面的广播接收器还可以将广播截断,以阻止其继续传播。
那么如何设定广播接收器的先后顺序呢?当然是在注册的时候进行设定了,修改AndroidManifest.xml中的代码,


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

    <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=".MyReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.example.broadcassttest.MY_BROADCAST" />
            intent-filter>
        receiver>

    application>

manifest>

我们通过android:priority属性给广播设置了优先级,优先级比较高的广播接收器就可以先收到广播了,这里将MyReceiver的优先级设为100,保证它一定会在AnotherBroadcastReceiver之前收到广播。
既然已经获得了接收广播的优先权,就可以选择是否允许广播继续传递了。
修改MyReceiver.java的代码

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"received in MyBroadcastReceive",
                Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

如果在onReceiver()方法中调用了abortBroadcast()方法,以表示将这条广播截断,后面的广播接收器将无法再接收这条广播,现在运行程序测试下,发现只会弹出一条提示。
「「第一行代码(第二版)」」学习笔记 6 广播机制(连载中)_第5张图片

你可能感兴趣的:(#,android,学习笔记)