安卓四大组件之—— BroadcastReceiver

初识广播

什么是广播?

Broadcast直译广播,我们举个形象的例子来帮我理解下BroadcastReceiver,记得以前读书 的时候,每个班级都会有一个挂在墙上的大喇叭,用来广播一些通知,比如,开学要去搬书,广播: “每个班级找几个同学教务处拿书”,发出这个广播后,所有同学都会在同一时刻收到这条广播通知, 收到,但不是每个同学都会去搬书,一般去搬书的都是班里的"大力士",这群"大力士"接到这条 广播后就会动身去把书搬回可是!
——好吧,上面这个就是一个广播传递的一个很形象的例子:
大喇叭—> 发送广播 —> 所有学生都能收到广播 —> 大力士处理广播

通常我们自己的应用或者是Android系统本身在某些事件来临的时候会将Intent广播出去,而注册的Broadcast Receiver可以监听到这些Intent,并且可以获得保存在Intent里边的数据。其实BroadcastReceiver就是应用程序间的全局大喇叭,即通信的一个手段。

系统自己在很多时候都会发送广播,在电池电量发生变化,网络连接发生变化的时候或者来电、来短信的时候,Android 系统都会将相关的Intent进行广播。如果注册了针对这些事件的BroadcastReceiver,那么就可以处理这些事件。

如果你想让你的应用在接收到 这个广播的时候做一些操作,比如:系统开机后,偷偷后台跑服务~哈哈,这个时候你只需要为你的应用 注册一个用于监视开机的BroadcastReceiver,当接收到开机广播就做写偷偷摸摸的勾当~ 当然我们也可以自己发广播,比如:接到服务端推送信息,用户在别处登录,然后应该强制用户下线回到 登陆界面,并提示在别处登录等等。

Android 提供了一套完整的API,允许应用程序自由地发送和接收广播。
下面让我们一起来了解一下

广播的类型

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

标准广播

标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播几乎都会在同一时刻接收到这条广播,因此他们之间没有任何先后顺序而言。这种广播的效率比较高,但同时是无法被截断的。

在这里插入图片描述

有序广播

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

接收系统广播

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

两种注册广播的方式

注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
在这里插入图片描述

动态注册

新建一个类, 让它继承自BradastRecciver,并重写父类的onReccive()方法就行了。这样当有广播到来时,onReceive()方 法就会得到执行,具体的逻辑就可以在这个方法中处理。

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private NetworkChangeRecevier networkChangeRecevier;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeRecevier=new NetworkChangeRecevier();
        registerReceiver(networkChangeRecevier,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //动态注册这一步必须要写
        unregisterReceiver(networkChangeRecevier);
    }

    class NetworkChangeRecevier extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "网络状态已更改...", Toast.LENGTH_SHORT).show();
        }
    }
}

可以看到,我们在MainActivity中定义了一个内部类NetworkChangeReceiver,这个类是继承自BroadcastReceiver的,并重写了父类的onReceive()方法。这样每当网络状态发生变化时,onReceive()方 法就会得到执行,这里只是简单地使用Toast提示了一段文本信息。
然后观察onCreate()方法,首先我们创建了一个IntentFiter的实例,并给它添加了一个值为
android.net.conn.CONNECTIVITY_ CHANGE的action,为什么要添加这个值呢?因为当网络状态发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_ CHANGE 的广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action就行了。接下来创建了一个NetworkChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例都传了进去,这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_ CHANGE的广播,也就实现了监听网络变化的功能。
最后要记得,动态注册的广播接收器一定都要取消注册才行,这里我们是在onDestroy()方法中通过调用unregisterReceiver()方法来实现的。
整体来说,代码还是非常简单的,现在运行一下程序。首先你会在注册完成的时候收到一条广播,然后按下Home键回到主界面(注意不能按Back键,否则onDestroy(方法会执行),接着按下Menu键→Systcm sttings →Datausage进入到数据使用详情界面,然后尝试着开关MobileData来启动和禁用网络,你就会看到有Toast提醒你网络发生了变化。
不过只是提醒网络发生了变化还不够人性化,最好是能准确地告诉用户当前是有网络还是没有网络,因此我们还需要对上面的代码进行进一步的优化。 修改MainActivity中的代码,如下所示:

 class NetworkChangeRecevier 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, "网络可用", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
            }

        }
    }

在onReceive()方法中,首先通过getSystemService()方法得到了ConnectivityManager 的实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的getActiveNetworkInfo()方法 可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable(方法,就可以判断出当前是否有网络了,最后我们还是通过Toast的方式对用户进行提示。
另外,这里有非常重要的一点需要说明,Android 系统为了保证应用程序的安全性做了规定,如果程序需要访问一些系统的关键性信息,必须在配置文件中声明权限才可以,否则程序将会直接崩溃,比如这里查询系统的网络状态就是需要声明权限的。打开AndroidManifest.xml文件,在里面加入如下权限就可以查询系统网络状态了:

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

访问https:/developer.android.google.cn/reference/android/Manifest.permission.html 可以查看Android系统所有可声明的权限。

静态注册

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

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "开机启动完成", Toast.LENGTH_SHORT).show();
    }
}

可以看到,这里不再使用内部类的方式来定义广播接收器,因为稍后我们需要在AndroidManifest.xml中将这个广播接收器的类名注册进去。在onReceive()方法中,还是简单地使用Toast弹出一段提示信息。

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

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".BootCompleteReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

终于,标签内出现了一 个新的标签,所有静态注册的广播接收器都是在这里进行注册的。它的用法其实和标签非常相似,首先通过android:name来指定具体注册哪一个广 播接收器,然后在标签里加入想要接收的广播就行了,由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_ COMPLETED的广播,因此我们在这里添加了相应的action。
另外,监听系统开机广播也是需要声明权限的,可以看到,我们使用标签又加入了一条android.permission.RECEIVE BOOT_ COMPLETED权限。
然后将模拟器关闭并重新启动,在启动完成之后就会收到开机厂播了

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

注意: Google从Android8.0版本开始,对在清单文件中静态注册广播做了限制。特殊的广播:指那些操作比较频繁的广播事件类型。如:屏幕的开、关广播,电量的变化广播等等这种特殊的广播事件在AndroidManifest.xml中注册是无效的!
因为这种特殊的广播如果在清单文件中注册,会浪费内存资源。你可以想象下,如果有100个应用在清单文件中注册了手机电量变化广播接收者,那当手机电量发生变化时,这100个应用的广播接收者就有可能都运行…那会造成什么结果.所以:只能动态注册(在代码中注册)。

发送广播

发送标准广播

在发送广播之前,我们还是需要先定义一个广播接收器来准备接收此广播才行,不然发出去也是徒劳的。

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

这里当MyBroadcastReceiver收到自定义的广播时,就会弹出received in MyBroadcastReceiver的提示。然后在AndroidManifest.xml中对这个广播接收器进行注册:

<!--静态注册一个广播接收者-->
 <receiver android:name=".MyBroadcastReceiver"
            android:exported="false">
            <!--定义一个意图过滤器来接收(监听)指定的action-->
            <intent-filter>
            <!--配置自定义的action(事件类型)-->
                <action android:name="com.android.MY_BROADCAST"/>
            </intent-filter>
</receiver>

可以看到,这里让MyBroadcastReceiver接收一条值为com.android. MY_BROADCAST的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广 播。
接下来修改activity_ main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:text="点击发送自定义广播"
        />
</LinearLayout>

Android 8.0 以前

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.android.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });

Android 8.0之后,必须使用全类名方式

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.android.MY_BROADCAST");
                intent.setComponent(new ComponentName("com.android","com.android.MY_BROADCAST"));
                sendBroadcast(intent);
            }
        });

说明: Android 8版本之后的变化:setComponent(new ComponentName(“目标广播接收器所在应用ID(build.gradle文件中声明的applicationId的值)”, “目标广播接收器类全路径”)); 通常Android的应用ID与包名是绑定的,所以在Android API中,一些方法和参数从名称上看似乎它们返回的是包名,事实上它们返回的是应用ID值.例如,Context.getPackageName()方法返回的是应用ID,而不是包名.
这样所有监听com.android.MY_ BROADCAST 这条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
这样我们就成功完成了发送自定义广播的功能。另外,由于广播是使用Intent 进行传递的,因此你还可以在Intent中携带一些数据传递给广播接收器。
广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看出来了。因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。

再新建一个Android项目。将项目创建好之后,还需要在这个项目下定义一个广播接收器,用于接收上一小节中的自定义广播。新建AnotherBroadcastReceiver继承自BroadcastReceiver,代码如下所示:

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

这里仍然是在广播接收器的onReceive)方法中弹出了一段文本信息。然后在AndroidManifest.xml中对这个广播接收器进行注册,代码如下所示:

 <receiver android:name=".AnotherBroadcastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.android.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

main.xml

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn1"
        android:text="发送自定义广播"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn2"
        android:text="发送自定义广播使其他应用接收"
        />

</LinearLayout>

MainActivity.java

package com.jingyi.anotherbroadcastdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn1 = findViewById(R.id.btn1);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent1 = new Intent();
                intent1.setAction("com.android.broadcstdemo.MY_BROADCAST");
                intent1.setComponent(new ComponentName("com.android.anotherbroadcastdemo","com.android.anotherbroadcastdemo.AnotherBroadcastReceiver"));
                sendBroadcast(intent1);
            }
        });

        Button btn2 = findViewById(R.id.btn2);
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent2 = new Intent();
                intent2.setAction("com.android.MY_BROADCAST");
                intent2.setComponent(new ComponentName("com.android.broadcastdemo","com.android.broadcastdemo.MyBroadcastReceiver"));
                sendBroadcast(intent2);
            }
        });
    }
}

注意: sctComponent()的第一 个 参数是该应用的applicationId。

在这里插入图片描述

运行这两个应用程序,可以看到,AnotherBroadcastReceiver 同样接收的是com.android.broadcstdemo.MY_ BROADCAST这条广播。现在运行BroadcastTest2项目将这个程序安装到模拟器上,然后重新回到BroadcastTest项目的主界面,并点击一下 "发送自定义广播使其他应用接收"按钮,就会分别弹出两次提示信息。
这样就强有力地证明了,我们的应用程序发出的广播是可以被其他的应用程序接收到的。

以上代码只能在安卓8.0运行,因为在安卓9.0之后静态注册的自定义广播无法接收,需要改为动态注册

于是,上面的代码应该为

broadcastdemo项目

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:text="点击发送自定义广播"
        />


</LinearLayout>

MainActivity.java

package com.jingyi.broadcastdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private MyBroadcastReceiver myBroadcastReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button =findViewById(R.id.btn);
        button.setOnClickListener(v -> {
            Intent intent = new Intent("com.jingyi.broadcastdemo.MY_BROADCAST");
            //intent.setComponent(new ComponentName("com.jingyi.broadcastdemo","com.jingyi.broadcastdemo.MY_BROADCAST"));
            sendBroadcast(intent);
        });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.jingyi.broadcastdemo.MY_BROADCAST");
        myBroadcastReceiver = new MyBroadcastReceiver();
        registerReceiver(myBroadcastReceiver,intentFilter);

    }

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

MyBroadcastReceiver.java

package com.jingyi.broadcastdemo;

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

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.jingyi.broadcastdemo.MY_BROADCAST")){
            Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
        }
    }
}

在这里插入图片描述

anotherbroadcastdemo项目

main.xml

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn1"
        android:text="发送自定义广播"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn2"
        android:text="发送自定义广播使其他应用接收"
        />

</LinearLayout>

AnotherBroadcastReceiver.java

package com.jingyi.anotherbroadcastdemo;

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

public class AnotherBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.jingyi.broadcastdemo.MY_BROADCAST")){
            Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
        }
    }
}

MainActivity.java

package com.jingyi.anotherbroadcastdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private  AnotherBroadcastReceiver anotherBroadcastReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn1 = findViewById(R.id.btn1);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent1 = new Intent();
                intent1.setAction("com.jingyi.anotherbroadcastdemo.MY_BROADCAST");
                sendBroadcast(intent1);
            }
        });
        IntentFilter intentFilter1 = new IntentFilter();
        intentFilter1.addAction("com.jingyi.anotherbroadcastdemo.MY_BROADCAST");
        anotherBroadcastReceiver = new AnotherBroadcastReceiver();
        registerReceiver(anotherBroadcastReceiver,intentFilter1);

        Button btn2 = findViewById(R.id.btn2);
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent2 = new Intent();
                intent2.setAction("com.jingyi.broadcastdemo.MY_BROADCAST"); 
                sendBroadcast(intent2);
            }
        });
        IntentFilter intentFilter2= new IntentFilter();
        intentFilter2.addAction("com.jingyi.broadcastdemo.MY_BROADCAST");
        anotherBroadcastReceiver = new AnotherBroadcastReceiver();
        registerReceiver(anotherBroadcastReceiver,intentFilter2);
    }

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

运行结果

会得到两条消息

在这里插入图片描述

在这里插入图片描述

在配置文件中(AndroidManifest.xml),如果没有设置优先级(android:priority= “XXX’”),那么接收到广播的先后顺序会根据配置文件中书写的顺序产生变化,接收顺序会由上而下。这种接收顺序不可取,所以,为了避免这种情况,有序广播的接收者必须配置优先级,防止接收顺序错乱。
priority的优先级:最高1000 ~最低 -1000
静态注册代码如下:

<receiver android :name= " . receiver.Test1Receiver">
		<intent -filter android ; priority= "1000" >
			<action android:name="x. xx.xxx.有序" />
		</intent-filter>
	</receiver>
<receiver android : name= " . receiver. Test2Receiver">
		<intent -filter android: priority= "999">
			<action android:name="x. xx.xxx.有序" />
		</intent-filter>
</receiver>
<receiver android : name="。receiver.Test3Receiver">
		< intent-filter android: priority= "998">
			<action android:name= "x. xx.xxx.有序"/>
		</intent-filter>
</receiver>

从Android版本8.0开始,由于Google对清单文件中静态注册广播接收者做了限制,只能通过动态注册的方式,实现有序广播。

动态注册代码如下:

IntentFilter filter2 = new IntentFilter();
filter2.addAction("x.xx. xxx.有序");
filter2.setPriority(999);
this.registerReceiver (new Test2Receiver(), filter2);

IntentFilter filter1 = new IntentFilter();
filter1.addAction("x. xx.xxx.有序");
filter1.setPriority(1000);
this.registerReceiver(new Test1Receiver(), filter1);

IntentFilter filter3 = new IntentFilter();
filter3. addAction("x.xx. xxx.有序");
filter3. setPriority(998);
this.registerReceiver ( new Test3Receiver(), filter3);

有序广播例子
Main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:text="点击发送有序广播"
        android:layout_gravity="center"
        />
</LinearLayout>

MainActivity.java

package com.jingyi.orderbroadcast;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private OrderBroadcastReceiver orderBroadcastReceiver;
    private OrderBroadcastReceiver2 orderBroadcastReceiver2;
    private OrderBroadcastReceiver3 orderBroadcastReceiver3;
    private OrderBroadcastReceiver4 orderBroadcastReceiver4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button button =findViewById(R.id.btn);
        button.setOnClickListener(v -> {
            Intent intent = new Intent();
            intent.setAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
            sendOrderedBroadcast(intent,null);//第一个参数是intent,第二个参数是一个与权限相关的字符串
        });
        orderBroadcastReceiver = new OrderBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
        intentFilter.setPriority(88);//设置优先级
        registerReceiver(orderBroadcastReceiver,intentFilter);

        //orderBroadcastReceiver2优先级比orderBroadcastReceiver高
        orderBroadcastReceiver2 = new OrderBroadcastReceiver2();
        IntentFilter intentFilter2 = new IntentFilter();
        intentFilter2.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
        intentFilter2.setPriority(100);//设置优先级
        registerReceiver(orderBroadcastReceiver2,intentFilter2);
        //orderBroadcastReceiver3在接收到广播之后会截断广播
        orderBroadcastReceiver3 = new OrderBroadcastReceiver3();
        IntentFilter intentFilter3 = new IntentFilter();
        intentFilter3.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
        registerReceiver(orderBroadcastReceiver3,intentFilter3);
        //orderBroadcastReceiver4接收不到广播
        orderBroadcastReceiver4 = new OrderBroadcastReceiver4();
        IntentFilter intentFilter4 = new IntentFilter();
        intentFilter4.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
        registerReceiver(orderBroadcastReceiver4,intentFilter4);
    }
}

OrderBroadcastReceiver.java

public class OrderBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Receiver in OrderBroadcastReceiver1", Toast.LENGTH_SHORT).show();
    }
}

OrderBroadcastReceiver2.java

public class OrderBroadcastReceiver2 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Receiver in OrderBroadcastReceiver2", Toast.LENGTH_SHORT).show();
    }
}

OrderBroadcastReceiver3.java

public class OrderBroadcastReceiver3 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Receiver in OrderBroadcastReceiver3", Toast.LENGTH_SHORT).show();
        abortBroadcast();//截断广播
    }
}

OrderBroadcastReceiver4.java

public class OrderBroadcastReceiver4 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Receiver in OrderBroadcastReceiver4", Toast.LENGTH_SHORT).show();
    }
}

可以看到,我们在MainActivity类中动态注册广播接收器时,四个android:priority属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播。这里将OrderedBroadcastReceiver2的优先级设成了100, 以保证它一定 会在orderedBroadcastReceiver和orderedBroadcastReceiver3之前收到广播。
既然已经获得了接收广播的优先权,那么orderedBroadcastReceiver3就可以选择是否允许广播继续传递了。orderedBroadcatReceiver3就可以决定广播是否继续向后传递。如果在onReceive()方法中调用了abortBroadcat()方法,就表示将这条广播截断,后面的广播接收器将无法再接收到这条广播。

运行结果:
Receiver in OrderBroadcastReceiver2
Receiver in OrderBroadcastReceiver1
Receiver in OrderBroadcastReceiver3

广播进阶

本地广播

前面我们发送和接收的广播全部都是属于系统全局广播,即发出的广播可以被其他任何的任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播。这样就很容易会引起安全性的问题,比如说我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序不停地向我们的广播接收器里发送各种垃圾广播。
为了能够简单地解决广播的安全性问题,Android 引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。
本地广播的用法并不复杂,主要就是使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。下面我们就通 过具体的实例来尝试一下 它的用法。

在这里插入图片描述
Main.xml

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

   <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:id="@+id/btn"
       android:text="点击发送一条本地广播信息"
       android:layout_gravity="center"
       />
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private LocalBroadcastReceiver localBroadcastReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button =findViewById(R.id.btn);
        localBroadcastReceiver = new LocalBroadcastReceiver();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.jingyi.localbroadcast.MY_LOCALBROADCAST");
                LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);
            }
        });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.jingyi.localbroadcast.MY_LOCALBROADCAST");
        LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(localBroadcastReceiver,intentFilter);
    }


    class  LocalBroadcastReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "这是一条本地广播的信息", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(localBroadcastReceiver);
    }
}

有没有感觉这些代码很熟悉?没错,其实这基本上就和我们前面所学的动态注册广播接收器以及发送广播的代码是一样。 只不过现在首先是通过LocalBroadcastManager的gtnstance()方法得到了它的一个实例, 然后在注册广播接收器的时候调用的是LocalBroadcastManager 的registrReceiver0方法,在发送广播的时候调用的是LocalBroadcastManager的sendBroadcast()方法,仅此而已。这里我们在按钮的点击事件里面发出了一条
com.android.localbroadcastmanager.MY_ LOCALBROADCAST广播,然后在LocalReceiver里去接收这条广播。
运行程序可以看到,LocalReceiver 成功接收到了在这里插入图片描述
这条本地广播,并通过Toast提示了出来。如果你还有兴趣进行实验,可以尝试在BroadcastTest2中也去接收这条广播,答案是显而易见的,肯定无法收到,因"com. android.localbroadcastmanager.MY_ LOCALBROADCAST为这条广播只会在BroadcastTest程序内传播。
另外还有一点需要说明,本地广播是无法通过静态注册的方式来接收的。其实这也完全可以理解,因为静态注册主要就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,我们的程序肯定是已经启动了,因此也完全不需要使用静态注册的功能。最后我们再来盘点一下使用本地广播的几点优势吧。

  1. 可以明确地知道正在发送的广播不会离开我们的程序,因此不需要担心机密数据泄漏的问题。
  2. 其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患。
  3. 发送本地广播比起发送系统全局广播将会更加高效。

注意事项

  1. 本地广播无法通过静态册来接收!相比起系统全局广播更加高效
  2. 在广播中启动Activity的话需要为intent加入FLAG ACTIVITY NEW TASK的标记,不然会报错因为需要一个栈来存放标志活动新任务的标记,不然会报错因为需要一个栈来存放新打开的Activity
  3. J广播中弹出AlertDialog的话,需要设置对话框的类型为TYPE SYSTEM ALERT不然是无法弹出的~
    类型系统警报不然是无法弹出的~

常用的系统广播总结

intent.action.AIRPLANE_MODE;
//关闭或打开飞行模式时的广播

Intent.ACTION_BATTERY_CHANGED;
//充电状态,或者电池的电量发生变化
//电池的充电状态、电荷级别改变,不能通过组建声明接收这个广播,只有通过Context.registerReceiver()注册

Intent.ACTION_BATTERY_LOW;
//表示电池电量低

Intent.ACTION_BATTERY_OKAY;
//表示电池电量充足,即从电池电量低变化到饱满时会发出广播

Intent.ACTION_BOOT_COMPLETED;
//在系统启动完成后,这个动作被广播一次(只有一次)。

Intent.ACTION_CAMERA_BUTTON;
//按下照相时的拍照按键(硬件按键)时发出的广播

Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
//当屏幕超时进行锁屏时,当用户按下电源按钮,长按或短按(不管有没跳出话框),进行锁屏时,android系统都会广播此Action消息

Intent.ACTION_CONFIGURATION_CHANGED;
//设备当前设置被改变时发出的广播(包括的改变:界面语言,设备方向,等,请参考Configuration.java)

Intent.ACTION_DATE_CHANGED;
//设备日期发生改变时会发出此广播

Intent.ACTION_DEVICE_STORAGE_LOW;
//设备内存不足时发出的广播,此广播只能由系统使用,其它APP不可用?

Intent.ACTION_DEVICE_STORAGE_OK;
//设备内存从不足到充足时发出的广播,此广播只能由系统使用,其它APP不可用?

Intent.ACTION_DOCK_EVENT;
//
//发出此广播的地方frameworks\base\services\java\com\android\server\DockObserver.java

Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE;
移动APP完成之后,发出的广播(移动是指:APP2SD)

Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
//正在移动APP时,发出的广播(移动是指:APP2SD)

Intent.ACTION_GTALK_SERVICE_CONNECTED;
//Gtalk已建立连接时发出的广播

Intent.ACTION_GTALK_SERVICE_DISCONNECTED;
//Gtalk已断开连接时发出的广播

Intent.ACTION_HEADSET_PLUG;
//在耳机口上插入耳机时发出的广播

Intent.ACTION_INPUT_METHOD_CHANGED;
//改变输入法时发出的广播

Intent.ACTION_LOCALE_CHANGED;
//设备当前区域设置已更改时发出的广播

Intent.ACTION_MANAGE_PACKAGE_STORAGE;
//

Intent.ACTION_MEDIA_BAD_REMOVAL;
//未正确移除SD卡(正确移除SD卡的方法:设置--SD卡和设备内存--卸载SD卡),但已把SD卡取出来时发出的广播
//广播:扩展介质(扩展卡)已经从 SD 卡插槽拔出,但是挂载点 (mount point) 还没解除 (unmount)

Intent.ACTION_MEDIA_BUTTON;
//按下"Media Button" 按键时发出的广播,假如有"Media Button" 按键的话(硬件按键)

Intent.ACTION_MEDIA_CHECKING;
//插入外部储存装置,比如SD卡时,系统会检验SD卡,此时发出的广播?
Intent.ACTION_MEDIA_EJECT;
//已拔掉外部大容量储存设备发出的广播(比如SD卡,或移动硬盘),不管有没有正确卸载都会发出此广播?
//广播:用户想要移除扩展介质(拔掉扩展卡)。
Intent.ACTION_MEDIA_MOUNTED;
//插入SD卡并且已正确安装(识别)时发出的广播
//广播:扩展介质被插入,而且已经被挂载。
Intent.ACTION_MEDIA_NOFS;
//
Intent.ACTION_MEDIA_REMOVED;
//外部储存设备已被移除,不管有没正确卸载,都会发出此广播?
// 广播:扩展介质被移除。
Intent.ACTION_MEDIA_SCANNER_FINISHED;
//广播:已经扫描完介质的一个目录
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE;
//
Intent.ACTION_MEDIA_SCANNER_STARTED;
//广播:开始扫描介质的一个目录

Intent.ACTION_MEDIA_SHARED;
// 广播:扩展介质的挂载被解除 (unmount),因为它已经作为 USB 大容量存储被共享。
 Intent.ACTION_MEDIA_UNMOUNTABLE;
//
Intent.ACTION_MEDIA_UNMOUNTED
// 广播:扩展介质存在,但是还没有被挂载 (mount)。
Intent.ACTION_NEW_OUTGOING_CALL;

Intent.ACTION_PACKAGE_ADDED;
//成功的安装APK之后
//广播:设备上新安装了一个应用程序包。
//一个新应用包已经安装在设备上,数据包括包名(最新安装的包程序不能接收到这个广播)
 Intent.ACTION_PACKAGE_CHANGED;
//一个已存在的应用程序包已经改变,包括包名
Intent.ACTION_PACKAGE_DATA_CLEARED;
//清除一个应用程序的数据时发出的广播(在设置--应用管理--选中某个应用,之后点清除数据时?)
//用户已经清除一个包的数据,包括包名(清除包程序不能接收到这个广播)

Intent.ACTION_PACKAGE_INSTALL;
//触发一个下载并且完成安装时发出的广播,比如在电子市场里下载应用?
//
Intent.ACTION_PACKAGE_REMOVED;
//成功的删除某个APK之后发出的广播
//一个已存在的应用程序包已经从设备上移除,包括包名(正在被安装的包程序不能接收到这个广播)

Intent.ACTION_PACKAGE_REPLACED;
//替换一个现有的安装包时发出的广播(不管现在安装的APP比之前的新还是旧,都会发出此广播?)
Intent.ACTION_PACKAGE_RESTARTED;
//用户重新开始一个包,包的所有进程将被杀死,所有与其联系的运行时间状态应该被移除,包括包名(重新开始包程序不能接收到这个广播)
Intent.ACTION_POWER_CONNECTED;
//插上外部电源时发出的广播
Intent.ACTION_POWER_DISCONNECTED;
//已断开外部电源连接时发出的广播
Intent.ACTION_PROVIDER_CHANGED;
//

Intent.ACTION_REBOOT;
//重启设备时的广播

Intent.ACTION_SCREEN_OFF;
//屏幕被关闭之后的广播

Intent.ACTION_SCREEN_ON;
//屏幕被打开之后的广播

Intent.ACTION_SHUTDOWN;
//关闭系统时发出的广播

Intent.ACTION_TIMEZONE_CHANGED;
//时区发生改变时发出的广播

Intent.ACTION_TIME_CHANGED;
//时间被设置时发出的广播

Intent.ACTION_TIME_TICK;
//广播:当前时间已经变化(正常的时间流逝)。
//当前时间改变,每分钟都发送,不能通过组件声明来接收,只有通过Context.registerReceiver()方法来注册

Intent.ACTION_UID_REMOVED;
//一个用户ID已经从系统中移除发出的广播
//

Intent.ACTION_UMS_CONNECTED;
//设备已进入USB大容量储存状态时发出的广播?

Intent.ACTION_UMS_DISCONNECTED;
//设备已从USB大容量储存状态转为正常状态时发出的广播?

Intent.ACTION_USER_PRESENT;
//

Intent.ACTION_WALLPAPER_CHANGED;
//设备墙纸已改变时发出的广播
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值