美文网首页Android开发经验谈
Android基础回顾(四)| 关于广播机制

Android基础回顾(四)| 关于广播机制

作者: CCCode1997 | 来源:发表于2018-11-29 10:40 被阅读14次

    参考书籍:《第一行代码》 第二版 郭霖
    如有错漏,请批评指出!

    广播机制简介

    Android中的广播主要分两种类型:标准广播和有序广播

    • 标准广播:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播信息,没有先后顺序可言。这种广播效率比较高,但是无法截断。

    • 有序广播:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后广播才会继续传递。此时广播接收器有先后顺序,优先级高的广播接收器可以先收到广播消息,并且前面的广播接收器可以截断广播。

    接收系统广播

    Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。使用广播接收器可以自由地对需要的广播进行注册,当有相应广播发出时,广播接收器就能够收到该广播,并对其进行逻辑处理。
    注册广播的方式一般有两种:一是动态注册,即在代码中注册;二是静态注册,即在AndroidManifest文件中注册。
    创建广播接收器的方法是新建一个类,继承BroadcastReceiver类,并重写onReceive()方法,当有广播到来时,onReceive()方法会被调用,我们可以在onReceive()方法中添加具体逻辑。

    • 动态注册监听网络变化
      下面我们使用动态注册的方式来完成一个监听网络变化的demo:
      首先,在AndroidManifest文件中声明系统网络状态的权限,因为我们要监听系统网络状态的变化,就必须拥有这个权限:

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

      接下来修改BroadcastActivity中的代码:

      public class BroadcastActivity extends AppCompatActivity {
      
          private NetworkChangeReceiver networkChangeReceiver;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              initBroadCast();
          }
      
          private void initBroadCast() {
              IntentFilter 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 manager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
                  NetworkInfo networkInfo = manager.getActiveNetworkInfo();
                  if (networkInfo != null && networkInfo.isConnected()) {
                      ToastUtil.showShortToast(context, "Network is connected");
                  }else {
                      ToastUtil.showShortToast(context, "Network not connected");
                  }
              }
          }
      }
      
      1. 第一步,创建一个内部类NetworkChangeReceiver,继承BroadcastReceiver类,重写它的onReceive()方法。在onReceive()方法中通过getSystemService()方法得到一个ConnectivityManager的实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的getActiveNetworkInfo()方法可以得到NetworkInfo的实例,接着调用NetworkInfo的isConnected()方法,就可以判断出当前是否有网络了。当然,想要onReceive()方法被触发,我们需要对网络状态变化时发出的系统广播进行注册。
      2. 第二步,因为系统网络状态发生变化时,会发出一条值为
        android.net.conn.CONNECTIVITY_CHANGE 的广播,所以我们创建一个IntentFilter实例,并给它添加值为 android.net.conn.CONNECTIVITY_CHANGE 的action(也就是说,我们想要监听什么广播,就添加其对应的action)。
      3. 第三步,创建一个NetworkChangeReceiver的实例,然后调用 registerReceiver() 方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例传进去,这样就完成了注册,也就是说,当系统发出值为android.net.conn.CONNECTIVITY_CHANGE的广播时,我们的onReceive()方法就会被触发。
      4. 第四步,重写onDestroy()方法,调用unregisterReceiver()方法取消注册。这里要注意,我们动态注册的广播都需要取消注册。下面看效果(打开我们的Demo,然后切到网络设置的页面,注意,不要按back键退出demo,不然BroadcastActivity会被销毁):
    • 静态注册实现开机启动
      动态注册的广播接收器可以自由地控制注册与注销,在灵活性上有很大的优势,但是它也有自己的局限性,即必须在程序启动后才能接收到广播,因为注册逻辑是写在onCreate()方法中的。这时候,静态注册的方式就有用武之地了。接下来,我们通过静态注册的方式来接收一条开机广播。

      1. 第一步,新建一个BootCompleteReceiver类,继承BroadcastReceiver类,并重写其onReceive()方法,使用Toast弹出一条消息:
      public class BootCompleteReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              ToastUtil.showShortToast(context, "Boot Completed");
          }
      }
      
      1. 第二步,在AndroidManifest文件中注册广播:
      <receiver
          android:name=".broadcast.BootCompleteReceiver"
          android:enabled="true"
          android:exported="true" >
          <intent-filter>
              <action android:name="android.intent.action.BOOT_COMPLETED"/>
          </intent-filter>
      </receiver>
      

      通过android:name属性指定我们创建的广播接收器(完整路径);android:enabled
      属性定义系统是否能够实例化这个广播接收器,为true时这个广播接收器才能被启用,默认为true;android:exported属性用于指示该广播接收器是否能够接收来自应用程序外部的消息。然后在<intent-filter>标签中添加系统开机广播对应的action,这和动态注册创建Intentfilter实例并添加action的作用是一样的。

      1. 第三步,由于我们需要监听系统开机广播,因此也需要为其声明权限,在AndroidManifest文件中声明权限:
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
      

    这样,我们的静态注册广播就完成了,具体结果自行验证。
    注意:在onReceive()方法中不能进行耗时操作,因为广播接收器中不允许开启线程,若onReceive()方法运行较长时间,程序就会报错。

    自定义广播

    前面是关于如何使用广播接收器来接收系统广播,接下来我们来看看如何发送自定义广播。

    • 发送标准广播
      首先来新建一个DiyBroadcastReceiver类,继承BroadcastReceiver类,重写onReceive()方法:

      public class DiyBroadcastReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              ToastUtil.showShortToast(context, "received in my broadcast receiver");
              abortBroadcast();
          }
      }
      

      然后在AndroidManifest文件中对这个广播接收器进行注册:

      <receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver"
              android:enabled="true"
              android:exported="true">
              <intent-filter android:priority="13">
                  <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
              </intent-filter>
          </receiver>
      

      我们添加一条值为 com.laughter.broadcast.DIY_BROADCAST 的action,也就意味着,我们待会儿发出一条值为这个的广播,我们的广播接收器就能收到。
      接下来,在我们的布局文件中添加一个Button,用于触发发送广播(很简单,我就不贴代码了),然后修改BroadcastActivity中的代码:

      public class BroadcastActivity extends AppCompatActivity {
      
          @BindView(R.id.but_send)
          Button send;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_broadcast);
              ButterKnife.bind(this);
          }
      
          @OnClick(R.id.but_send)
          public void send() {
              Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST");
              sendBroadcast(intent);
          }
      }
      

      发送广播的方法很简单,就是创建一个Intent对象,将需要发送的广播的值作为参数传递进去,然后调用sendBroadcast()方法将广播发送出去,这样监听 com.laughter.broadcast.DIY_BROADCAST 这个值的广播接收器就能收到这条广播(这里就不贴效果图了,大家可以自己验证)。由于是用Intent发送广播,因此,还可以用这个Intent对象携带一些参数。
      广播是一种可以跨进程的通信方式,因此,我们在应用程序中发送的广播,别的应用程序也是可以收到的。下面我们来验证一下:
      首先创建一个BroadcastTest项目,然后定义一个广播接收器:

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

      在AndroidManifest文件中进行注册:

      <receiver
          android:name=".AnotherReceiver"
          android:enabled="true"
          android:exported="true">
          <intent-filter>
              <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
          </intent-filter>
      </receiver>
      
      这样,我们的两个程序就都能说收到这条广播了,下面将两个app都运行起来,验证一下:

      可以看到,当我们点击Button时,两条Toast都弹出了, 也就意味着,两个app中的广播接收器都收到了这条广播。

    • 发送有序广播
      要发送一条有序广播,我们只需要在前面的BroadcastActivity中,将sendBroadcast(intent) 方法换成 sendOrderedBroadcast(intent, null) 方法就行了(第一个参数还是Intent对象,第二个参数是与权限相关的字符串,这里直接传null就行):

      public class BroadcastActivity extends AppCompatActivity {
      
          ···
      
          @OnClick(R.id.but_send)
          public void send() {
              Intent intent = new Intent("com.laughter.broadcast.DIY_BROADCAST");
              sendOrderedBroadcast(intent, null);
          }
      }
      

      前面说过,发送有序广播的时候,广播接收器之间存在优先级,那么如何指定优先级呢?

      <receiver android:name=".broadcast.broadcast.DiyBroadcastReceiver"
          android:enabled="true"
          android:exported="true">
          <intent-filter android:priority="13">
              <action android:name="com.laughter.broadcast.DIY_BROADCAST"/>
          </intent-filter>
      </receiver>
      

      我们只需要在注册广播接收器的时候给<intent-filter>标签指定一个android:priority 属性就行了,里面的值就是优先级,值越大,优先级越高。同样的,给AnotherReceiver指定一个优先级,数字比这里的13小就行。这样,优先级高的广播接收器就会先收到广播。(这里也不贴图了,自行验证)
      前面还提到过,有序广播是可以被拦截的,如何拦截呢?我们只需要在广播接收器的onReceive()方法中调用 abortBroadcast(); 方法就可以拦截广播,这样优先级比这个广播接收器低的就收不到这条广播了。

      public class DiyBroadcastReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              ToastUtil.showShortToast(context, "received in my broadcast receiver");
              abortBroadcast();
          }
      }
      
      还是刚才的两个app,我们再来运行起来看看:

      可以看到,现在只有一个优先级高的广播接收器的onReceive()方法被触发,弹出了Toast,而由于广播被拦截,优先级低的AnotherReceiver没有收到广播,所以没有弹出Toast。

    使用本地广播

    前面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被任何其他的应用程序接收到,并且也可以接受其他任何应用程序的广播。这样就很容易引起安全性问题。为此,Android引入了一套本地广播机制,使用本地广播机制,广播只能在应用程序内部进行传递,并且广播接收器也只会接收到应用程序内部的广播。
    其实本地广播的用法和前面差不多,我们先来看代码:

    public class BroadcastActivity extends AppCompatActivity {
    
        @BindView(R.id.but_send)
        Button send;
    
        LocalBroadcastManager manager;
        LocalReceiver localReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_broadcast);
            ButterKnife.bind(this);
            initView();
        }
    
        private void initView() {
            manager = LocalBroadcastManager.getInstance(this);
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("com.laughter.broadcast.LOCAL_BROADCAST");
            localReceiver = new LocalReceiver();
            manager.registerReceiver(localReceiver, intentFilter);
        }
    
        @OnClick(R.id.but_send)
        public void send() {
            Intent intent = new Intent("com.laughter.broadcast.LOCAL_BROADCAST");
            manager.sendBroadcast(intent);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            manager.unregisterReceiver(localReceiver);
        }
    
        class LocalReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                ToastUtil.showShortToast(context, "received local broadcast");
            }
        }
    }
    

    和前面一样,我们首先定义一个内部类,即广播接收器,然后定义一个 LocalBroadcastManager 对象,通过它的 getInstance() 方法获取一个实例,接下来就是定义IntentFilter对象和广播接收器对象,给IntentFilter添加一条值为 com.laughter.broadcast.LOCAL_BROADCAST 的action,然后调用 LocalBroadcastManager 的 registerReceiver() 方法注册本地广播监听器,接下来在Button的点击事件中通过 LocalBroadcastManager 的 sendBroadcast() 方法发送广播,最后别忘了在onDestroy() 方法中取消注册。这样看起来,本地广播区别于全局广播的地方仅仅就是通过一个LocalBroadcastmanager 来管理广播的注册和发送。这样我们的广播就是仅仅在应用程序内部传递的了(感兴趣的自行验证)。
    注意:既然需要通过 LocalBroadcastmanager 来管理广播的注册和发送,那么对应的静态注册的方式怎么实现呢?很遗憾,本地广播是无法通过静态注册的方式来接收的。不过仔细想想,本地广播的发送很显然是需要在应用程序启动的情况下完成的,既然要在应用程序启动的情况下发送广播,那么也不用考虑在应用程序未启动的情况下接收广播了。而我们使用静态注册主要就是为了在应用程序未启动的情况下也能收到广播,所以思路就很清晰了。


    上一篇:Android基础回顾(三)| 关于Fragment
    下一篇:Android基础回顾(五)| 数据存储——持久化技术


    相关文章

      网友评论

        本文标题:Android基础回顾(四)| 关于广播机制

        本文链接:https://www.haomeiwen.com/subject/twyxcqtx.html