Android – 广播机制

广播机制

6.1 广播机制简介

  • 标准广播

标准广播是一种完全异步执行的广播,在广播发出后,所有 BroadcastReceiver 几乎会在现一时刻收到这条广播信息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。标准广播的工作流程如图所示:

image-20210609193032385
  • 有序广播

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

image-20210609194106065

6.2 接收系统广播

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

6.2.1 动态注册监听时间变化

我们可以根据自己感兴趣的广播,自由了注册BroadcastReceiver,这样当有相应的广播发出时,相应的BroadcastReceiver就能够收到该广播。并可以在内部进行逻辑处理,注册BroadcastReceiver的方式有两种:在代码中注册和在AndroidManifest.xml中注册,前者为被称为动态注册,后者被称为静态注册。

实现监听时间变化


public class MainActivity extends AppCompatActivity {

  TimeChangeReceiver timeChangeReceiver;

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

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

   class TimeChangeReceiver extends BroadcastReceiver {

       @Override
       public void onReceive(Context context, Intent intent) {
           Toast.makeText(MainActivity.this, "时候有变化", Toast.LENGTH_SHORT).show();
      }
  }

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

继承BroadcastReceiver类,并实现onReceiver() 方法。当系统时间发生变化时,该方法就会得到执行。

录系统时间发生变化时,系统发出的正是一条为android.intent.action.TIME_TICK的广播,所以我们需要监听该广播,而达到监听时间变化,去响应事件。

如果是动态注册的广播,一定要记得取消,我们在onDestroy中使用unregisterReceiver进行广播的取消。

动态广播可以灵活注册及关闭,但是它存在一个缺点:必须在程序启动之后才能接收广播,因为注册的逻辑是写在onCreate()方法中的,如果想要不启动程序的情况下监听广播,则需要使用静态广播。

效果,时间有变化时

image-20210609204914730

6.2.2的静态注册实现开机启动

其实从理论上来说,动态注册能监听到的系统广播,静态注册也应该能监听到,在过去的Andoird系统中确实是这样的,但是由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台唤醒,严重影响了用户手机电量和性能,因此Android系统几乎每个版本都在削减静态注册的BroadcastReceiver功能。

在Android8.0之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播。但少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见:https://developer.android.google.cn/guide/components/broadcast-exceptions.html

在这些特殊的广播中,有一条值为android.intent.action.BOOT_COMPLETED,这是一条开机广播,我们就用它来实现开机启动吧

BootCompleteReceiver.java
public class BootCompleteReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
       Toast.makeText(context, "年少有为", 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.ziyia.broadcastreceiver">
<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.MyApplication">
       <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的广播,因此我们在<receiver>标题中又添加了一个<intent-filter>标签,并在里面声明了相应的action。

另外。这里有非常重要的一点需要说明。Android系统为了保护用户设备的安全和隐私。做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限的声明,否则程序将会直接崩溃,比如我们现在这个例子接收系统的开机广播就是需要进行权限的声明的。所以我们在上述代码中使用<uses-permission>标签中声明了android.permission.RECEIVE_BOOT_COMPLETED权限。

6.3 发送自定义广播

经过上面的学习,现在你学会了通过BroadcastReceiver来接收系统广播,接下来我们就要学习五如果在应用程序中发送自定义广播,前面已经介绍过了,广播分为两种类型,标准广播和有序广播。

6.3.1 发送标准广播

发送广播之前我们需要先定义一个BroadcastReceiver来准备接收此广播。

public class MyBroadcastReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
       Toast.makeText(context, "我已经接收到了一条自定义广播", Toast.LENGTH_SHORT).show();
  }
}

当MyBroadcastReceiver接到自定义广播时,就会弹出 “我已经接收到了一条自定义广播”

我们还需要对AndroidManifest.xml文件进行修改:

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

   <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.MyApplication">
       <receiver
           android:name=".MyBroadcastReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter>
               <action android:name="com.ziyia.broadcastreceiver.MY_BRADCAST"/>
           </intent-filter>
       </receiver>
       <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>

可见,我们让MyBroadcastReceiver接收一条值为”com.ziyia.broadcastreceiver.MY_BRADCAST”的广播,我们待会在发送广播的时候,就要发送这样一条广播。

我们添加一个按钮绑定点击事件来发送广播,修改activity_main.xml

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

   <Button
       android:id="@+id/button1"
       android:text="发送一条值自定义的标准广播"
       android:onClick="ButtonClick"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
</LinearLayout>

修改MainActivity.java

public class MainActivity extends AppCompatActivity {

  TimeChangeReceiver timeChangeReceiver;

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

   public void ButtonClick(View view) {
       switch (view.getId()) {
           case R.id.button1:
               Intent intent = new Intent("com.ziyia.broadcastreceiver.MY_BRADCAST");
               intent.setPackage(getPackageName());
               sendBroadcast(intent);
               break;
      }
  }
}

首选创建了一个Intent对象,并把要头发的广播的值传入,调用Intent的setPackage()方法,并传入当前应用程序的包名 getPackageName() 。最后调用snedBoroadcast() 方法将广播发送出去,这样所有监听”com.ziyia.broadcastreceiver.MY_BRADCAST”这条广播的BroadcastReceiver就会收到消息了,此时发送出去的是一条标准广播。最后补充一点:之所以调用sendPackage() 方法。前面已经说过,在Android8.0之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下我们发送的自定义广播恰好是隐式广播。因此这里 一定要调用setPackage() 方法,指定这条广播是发给哪个应用程序的,从而让它变成一条显式广播,否则静态注册的BroadcastReceiver将无法接收到这条广播。

效果,点击按钮时

image-20210609204728070

6.3.2 发送有序广播

和标准广播不同,有序广播是一种同步执行的广播,并且是可以被截断的。

我们通过代码来一步步演示:

AnotherBroadcastReceiver.java

public class AnotherBroadcastReceiver extends BroadcastReceiver {

   @Override
   public void onReceive(Context context, Intent intent) {
       Toast.makeText(context, "我接收到了一条有序广播", 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.ziyia.broadcastreceiver">

   <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.MyApplication">
       <receiver
           android:name=".AnotherBroadcastReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter>
               <action android:name="com.ziyia.broadcastreceiver.MY_BRADCAST" />
           </intent-filter>
       </receiver>
       <receiver
           android:name=".MyBroadcastReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter>
               <action android:name="com.ziyia.broadcastreceiver.MY_BRADCAST" />
           </intent-filter>
       </receiver>
       <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>

可见。我们AnotherBroadReceiver同样接收的是”com.ziyia.broadcastreceiver.MY_BRADCAST”这条广播,我们运行程序,并点击按钮发送广播,就会分别出现再次信息。

到目前为止,程序发出的都是标准广播,现在我们来尝试一下发送有序广播。修改MainActivity.java

public class MainActivity extends AppCompatActivity {

  TimeChangeReceiver timeChangeReceiver;

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

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

   public void ButtonClick(View view) {
       switch (view.getId()) {
           case R.id.button1:
               Intent intent = new Intent("com.ziyia.broadcastreceiver.MY_BRADCAST");
               intent.setPackage(getPackageName());
//               sendBroadcast(intent);
               sendOrderedBroadcast(intent, null);
               break;
      }
  }

   class TimeChangeReceiver extends BroadcastReceiver {

       @Override
       public void onReceive(Context context, Intent intent) {
           Toast.makeText(MainActivity.this, "时候有变化", Toast.LENGTH_SHORT).show();
      }
  }

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

我们只改动了一行代码,即:将sendBroadcast() 方法改成 sendOrderedBroadcast()方法

该方法接收两个参数,第一个参数是Intent,第二个参数是与权限相关的字符串,我们暂时不需要,因此传入 null即可。

现在重新运行程序,点击按钮发送广播,你会发现,两个 BroadcastReceiver仍然都可以接收到这条广播,看似没有什么区别,不过别忘了,这个时候的BnotherReceiver是有先后顺序的,而且前面的BroadcastReceiver还可以将广播截断,以阻止广播继续传播。

那么?如何指定BroadcastReceiver的优先级呢?当然是在注册的时候进行设定了,修改AndoridManifest.xml

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

   <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.MyApplication">
       <receiver
           android:name=".AnotherBroadcastReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter>
               <action android:name="com.ziyia.broadcastreceiver.MY_BRADCAST" />
           </intent-filter>
       </receiver>
       <receiver
           android:name=".MyBroadcastReceiver"
           android:enabled="true"
           android:exported="true">
           <intent-filter android:priority="10">
               <action android:name="com.ziyia.broadcastreceiver.MY_BRADCAST" />
           </intent-filter>
       </receiver>
       <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:priority属性给BroadcastReceiver设置了优先级,优先级较高的BroadcastReceiver就可以先收到广播,这里将MyBroadcastReceiver的优先级设置成了10,以保证它一定会在AnotherBroadcastReceiver之前收到广播。

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

public class MyBroadcastReceiver extends BroadcastReceiver {

   @Override
   public void onReceive(Context context, Intent intent) {
       Toast.makeText(context, "我已经接收到了一条自定义广播", Toast.LENGTH_SHORT).show();
       abortBroadcast();
  }
}

如果在onReceiver() 方法中调用了 abortBroadcast() 方法,就表示将这条广播截断,后面的BroadcastReceiver将无法再接收到这条广播。现在运行程序,你会发现在MyBroadcastReceiver之后确实被终止传递了。

发表回复

相关

浙ICP备2021031744号-3