Android——广播机制原理

在Android开发中,BroadcastReceiver(广播接收器)的应用场景非常多,是一个全局的监听器,属于Android四大组件之一。

一、广播类型

Andriod 中的广播主要分为两个类型:标准广播有序广播

标准广播(Normal broadcasts)是一种完全异步执行的广播。在广播发送后,所有的广播接收器都可以在同一时刻接收到这条广播信息,然后各自进行相应的逻辑处理。标准广播的优点是效率高,但与此同时,也意味着标准广播是无法被截断的,所有的广播接收器都可以接收到。标准广播示意图如下:

 

有序广播(Ordered broadcasts)是一种同步执行的广播。在广播发出后,同一时间只能有一个优先级最高的广播接收器来接收。在优先级高的广播接收器接收到广播并且执行完内部逻辑后,广播才会继续往优先级低的广播接收器传递,或者优先级高的广播接收器判定广播不需要继续往下传递的时候,则会截断广播,这样后面的广播接收器就不会接收到广播了。有序广播示意图如下:

二、广播的作用

用于监听/接收应用发出的广播消息,并做出响应。

应用场景:

a.不同组件之间的通信(应用内、应用间)

b.与Android系统在特定情况下的通信,Android内置了许多系统级别的广播接收器,如更换系统语言、电池电量变化等都会发出相应的广播。如果我们希望在自己的应用中可以接收到这些广播,就需要注册对应的广播接收器。

c.多线程通信

三、注册广播接收器

广播接收器的注册有两种方式:动态注册静态注册。动态注册就是在代码中注册,当你需要用到广播接收器的时候直接在代码中注册;而静态注册则是在AndroidManifest.xml文件中进行注册。

1、动态注册广播接收器

动态注册广播接收器只需要创建一个继承BroadcastReceiver的类,并且重写父类onReceive()方法即可。当广播接收器接收到相应广播时,就会执行onReceive()方法。

在这里我注册一个监听网络状态变化的广播接收器做例子

首先,Android系统为了保护用户的安全和隐私,对App申请敏感权限有着严格的规定,必须在配置文件中声明权限,否则会导致App的报错。而我们需要访问系统的网络状态也属于敏感权限,是需要声明权限的。

打开AndroidManifest.xml文件,在<manifest>标签里加入以下代码即可:

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

然后到注册广播接收器:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        IntentFilter filter = new IntentFilter();
        //设置接收广播类型
        filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        NetworkStateReceiver networkStateReceiver = new NetworkStateReceiver();
        //调用Context的registerReceiver()方法进行动态注册
        registerReceiver(networkStateReceiver, filter);
    }	
	
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在注册完广播接收器之后记得在对应的生命周期中注销广播接收器
        unregisterReceiver(networkStateReceiver);
    }
	
    class NetworkStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent){
        //判断网络状态
        ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectionManager.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();
          }
        }
    }
}

在onReceive()方法中,首先通过getSystemService()方法得到了ConnectivityManager的实例,这是一个系统服务类,专门用于管理网络连接,然后调用它的getActiveNetworkInfo()方法可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable()方法,就可以判断当前是否有网络了。

需要注意的是,应该养成一个习惯,在注册好广播接收器之后,应该在对应的生命周期中注销广播接收器,如在onCreate()中注册广播接收器,那就应该在onDestroy()中注销广播接收器,onStart()则对应onStop(),onResume()对应onPause()。如果忘记了注销广播接收器可能会导致内存泄漏问题。

2、静态注册广播接收器

既然可以动态注册广播接收器了,而且动态注册广播接收器也比较灵活,为什么还需要静态注册广播接收器,特意跑到AndroidManifest.xml中去注册呢?这是因为动态注册广播接收器的缺点:动态注册的广播接收器受组件的生命周期影响,只能在程序启动之后才能接收到广播。

所以当我们需要程序在未启动的情况下接收广播的时候,比如我们希望程序能接收一条开机广播的时候,就需要静态注册广播接收器了。

下面我们来创建一个开机启动的广播接收器。

首先在MainActivity中创建广播接收器:

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, “BootCompleteReceiver”, Toast.LENGTH_LONG).show();
    }
}

然后在AndroidManifest.xml文件中注册这个广播接收器:

在<application>标签中添加以下代码:

<receiver
    android:name=”.BootCompleteReceiver”
    android:enabled=”true”
    android:exported=”true”>
    <intent-filter>
        <action android:name=”android.intent.action.BOOT_COMPLETED” />
    </intent-filter>
</receiver>

其中name属性是MainActivity中继承BroadcastReceiver子类的类名,enabled属性表示是否启用这个广播接收器,exported属性则表示是否允许这个广播接收器接收本程序以外的广播。标签<intent-filter>用于指定广播接收器可以接收的广播类型。

另外,因为监听系统开机广播也需要声明权限,所以还需要在<manifest>标签内加入权限声明:

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

这样静态注册广播接收器就完成了。

3.两种注册方式的区别

注册方式特点应用场景
静态注册(常驻广播)

不受组件的生命周期影响(程序关闭后。如果有信息广播来,程序依旧会被系统调用)

缺点:耗电、占内存

需要时刻监听广播

(需要在程序还没有启动的时候也需要监听广播时)

动态广播(非常驻广播)

灵活,跟随组件的生命周期变化

(组件结束=广播结束,在组件结束前需要注销广播接收器)

只需要在特定时刻监听广播

(在程序运行的时候监听广播)

四、发送广播

广播主要分为 标准广播和有序广播。

1、发送标准广播

发送标准广播比较简单,只需要在想要发送广播的地方添加以下代码即可:

Intent intent = new Intent("android.intent.action.BOOT_COMPLETED");
sendBroadcast(intent);

2、发送有序广播

广播是一种可以跨进程的通信方式,就像前面提到的接收系统网络状态变化的广播,我们应用程序内发送的广播在其他应用也是可以接收到的。

而有序广播则提供了一个更加灵活的发送广播的方式给我们。

有序广播的接收器可以通过设定优先级来决定哪个广播接收器先接收广播。优先级的数值越大优先级越高(取值范围-1000~1000),优先级高的广播接收器在接收到广播并进行相应逻辑处理后可将广播传递给下一个优先级高的接收器或者直接截断广播的传递;若优先级相同,那么注册时间较早的广播接收器会优先收到广播。

由此可见,相对于标准广播,有序广播在灵活性高的同时的效率就比较低了。

我们安卓手机中一个较为常见的功能:短信拦截,就可以通过有序广播来实现。

短信拦截原理:系统收到短信的时候,会发出一个有序广播,然后短信拦截程序可以通过设置其本身短信广播接收器的优先级来优先获取到短信,对内容进行识别,若判定为骚扰短信则截断广播的传递。

发送有序广播和发送标准广播基本一样:

Intent intent = new Intent(“android.net.conn.CONNECTIVITY_CHANGE”);
sendOrderedBroadcast(intent, null);

sendOrderedBroadcast(intent, receiverPermission) 方法接收两个参数:

  1. Intent:打算发出的广播,与此广播匹配的广播接收器将会响应
  2. reveiverPermission:权限字符串,可以引用系统值也可以自定义。若设定权限字符串后,则接收器也必须声明该权限才可以接收到该广播;若为null即不需要权限即可接收。

但是只是发送有序广播的话还不能达到有序的目的,如果不对相应的广播接收器进行优先级的设定,那么和标准广播是一样的,所有的广播接收器会同时接收到这条广播。

对广播接收器设定优先级:

1)动态注册广播接收器:

public class MainActivity extends AppCompatActivity {

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

        IntentFilter filter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
        //设置优先级    
        filter.setPriority(1000);
        AnotherReceiver anotherReceiver = new AnotherReceiver();
        registerReceiver(anotherReceiver, filter);
    }

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

    class AnotherReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent){
            Toast.makeToast(context, “AnotherReceiver”, Toast.LENGTH_SHORT).show();
    	    //截断广播的传递
            abortBroadcast();
        }
    }
}

动态注册广播接收器的话可以通过IntentFilter.setPriority()方法来设置优先级。如果想在这个广播接收器接收到广播后截断广播的传递,则可以在onReceive()里添加abortBroadcast()方法。

2)静态注册广播接收器:

在AndroidManifest.xml文件<application>标签中添以下代码:

<receiver>
    android:name=”.AnotherReceiver”
    android:enabled=”true”
    android:exported=”true”>
    <intent-filter android:priority=”100”>
        <action android:name=”android.intent.action.BOOT_COMPLETED” />
    </intent-filter>
</receiver>

静态注册广播接收器的话需要在<intent-filter>标签里添加android:priority=“100”的属性,

另外如果需要截断广播的传递,同样也是在onReceive()里添加abortBroadcast()方法:

class AnotherReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent){
        Toast.makeToast(context, “AnotherReceiver”, Toast.LENGTH_SHORT).show();
        abortBroadcast();
	}
}

 

五、本地广播

以上说到的标准广播有序广播都是全局广播,发出的广播任意一个程序都可以接收到,这就很容易导致安全性问题,比如有时候发送的广播携带有关键性数据,有可能会被别的应用程序拦截。

为了解决全局广播的安全性问题,Android引进了本地广播机制。本地广播发出后只能够在应用程序内部传递,也只有应用程序内部的接收器能接收到本地广播,这样广播的安全性问题就能得到解决了。

本地广播和全局广播不同的地方在于本地广播主要使用LocalBroadcastManager对广播的发送、注册、注销进行管理。

下面写一个本地广播的例子:(界面设置一个按钮,点击按钮即可发送本地广播)

public class MainActivity extends AppCompatActivity {

    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        //获取localBroadcastManager实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);

        button.setOnClickListener (new View.onClickListener) {
            @Override
            public void onClick (View v){
                //发送本地广播
                Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
                localBroadcastManager.sendBroadcast(intent);
            }
        }
        //注册本地广播接收器
        IntentFilter filter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, filter);    
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销本地广播接收器
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    public class LocalReceiver extents BroadcastReceiver {

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

从上面的例子可以看出,本地广播的应用和前面的动态注册广播接收器和发送标准广播差别并不大,只是本地广播需要先通过LocalBroadcastManager.getInstance(this)方法来获取一个LocalBroadcastManager的实例,然后在发送本地广播、注册广播接收器和注销广播接收器的时候都是通过这个实例进行操作。

另外需要注意的是,本地广播无法静态注册,因为本地广播是只需要在程序内运行,如果静态注册的话(开机注册)其实是完全没有必要的。

本地广播的优势:

  1. 本地广播只会在程序内发送,不会导致数据泄露等问题;
  2. 本地广播接收器不会接收到其他程序发送的广播,不会导致安全性问题;
  3. 本地广播相对于标准广播更加高效。

六、系统广播

Android中内置了多个系统广播,只要是涉及到手机的基本操作,都会发出相应的广播。

每个系统广播都有特定的Intent-Filter(包括具体的action),Android系统广播action如下:

系统操作action
监听网络变化android.net.conn.CONNECTIVITY_CHANGE
飞行模式状态改变Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化

Intent.ACTION_BATTERY_CHANGED

电池电量低Intent.ACTION_BATTERY_LOW
电池电量充足(从电量低变化到充足会发出广播)Intent.ACTION_BATTERY_OKAY
系统启动完成(只发一次)Intent.ACTION_BOOT_COMPLETED
按下拍照按钮(硬件按钮)Intent.ACTION_CAMERA_BUTTON
屏幕锁屏Intent.ACTION_CLOSE_SYSTEM_DIALOGS
设备当前设置被改变(界面语言、设备方向等)Intent.ACTION_CONFIGURATION_CHANGED
插入耳机

Intent.ACTION_HEADSET_PLUG

插入外部储存装置(如SD卡)Intent.ACTION_MEDIA_CHECKING
未正确移除SD卡但已经取出来时(正确移除方法:设置--SD卡和设备内存--卸载SD卡)Intent.ACTION_MEDIA_BAD_REMOVAL
成功安装apkIntent.ACTION_PACKAGE_ADDED
成功删除apkIntent.ACTION_PACKAGE_REMOVED
屏幕被关闭Intent.ACTION_SCREEN_OFF
屏幕被打开Intent.ACTION_SCREEN_ON
关闭系统Intent.ACTION_SHUTDOWN
重启设备Intent.ACTION_REBOOT

以上这些广播均是系统在做出这些动作的时候会自动发送,如果我们需要使用到这些广播,只需要在注册广播接收器时定义相关的action即可。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值