Broadcast
广播机制简介
Android中的每一个应用程序都可以对自己感兴趣的广播进行注册,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。Android提供了一整套完整的API,允许应用程序自由地发送和接受广播。发送就是intent,而接受广播的方法则需要引入新的概念——BroadcastReceiver。
Android只的广播主要分为两种类型:标准广播和有序广播。
- 标准广播(normal broadcast)是一种完全异步执行的广播,在广播发出后 ,所有的broadcastreceiver几乎会在同一时刻受到这条广播消息,因此它们之间没有任何先后顺序可言。是不可中断广播
- 有序广播(ordered broadcast)则是一种同步执行的广播,在广播发出后,同一时刻只会有一个broadcastreceiver能够受到这条广播消息,当这个broadcastreceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的broadcastreceiver还可以阶段正在传递的广播,后面的broadcastreceiver就无法受到广播消息了。
接受系统广播
动态注册监听时间变化
注册broadcastreceiver的方式一般有两种:
- 静态注册:在AndroidManifest.xml中注册
- 动态注册:在代码中注册
- 新建一个类继承broadcastreceiver
- 重写onReceiver方法,当接收到广播的时候的回调函数。
- 重写父类方法onreceive方法
- 首先创建一个intentFilter实例,并给他添加一个值为
android.intent.action.TIME_TICK
的action。当系统时间发生变化时,系统发出的就是一条值为android.intent.action.TIME_TICK
的广播。 - Android系统还会在亮屏熄屏、电量变化、网络变化等场景下发出广播。文件下
<Android SDK>/platforms/<任意android api版本>/broadcast_actions.txt
中,如下图。
- 首先创建一个intentFilter实例,并给他添加一个值为
- 重写父类方法onDestory方法:动态注册的broadcastreceiver一定要取消注册。通过调用unregisterReceiver方法来实现。
- 这个地方可能会出现一个问题,就是当第一次还没有注册的时候就退出了,那么:Receiver not registered: null
- 新建一个类继承broadcastreceiver
静态注册实现开机启动
动态启动的缺点是必须在程序启动之后才能接受广播,因为注册的逻辑是写在oncreate方法中的。其实从理论上来说,动态注册能监听到的系统广播,静态注册也可以监听到,但是由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册broadcastreceiver的功能。
现在所有的隐式广播都不允许使用静态注册的方式来接受了。静态广播指的是那么没有具体指定发送给那个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。
-
创建一个broadcast类:说实话这个地方生成的抽象类的具体实现方式,默认的生成十分规范!! 当还没有实现,应该抛出异常,很好看!
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. throw new UnsupportedOperationException("Not yet implemented"); } }
-
在Androidmanifest.xml文件中注册。实际上当你创建的时候是通过反键的时候,默认会给在其中创建相关tag。
- 但是你需要修改成接受开机广播的形式。
- 添加
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ssozh.broadcasttest"> <--添加权限声明 --> <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.BroadcastTest"> <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>
真正在onreceiver方法的时候可以在里面编写自己的逻辑,值得注意的是不要在方法中添过多的逻辑或者进行任何耗时操作,因为broadcastreceiver中是不允许开启线程的,当onreceiver方法运行了较长时间还没有结束的时候,程序就会出现错误。
发送自定义广播
发送标准广播
通过intent的发送广播:
- 构建intent对象,并且把要发送的广播的值传入,
- 调用Intent的setPackage方法,并传入当前应用程序报名。【由隐式变成显示广播】
- 发送广播
- 发送有序广播
sendBroadcast(intent)
=>sendOrderedBroadcast(intent,null)
- 第二个参数是一个与权限相关的字符串。
- 接受是可以在
<intent-filter android:priority="100>
设置接受优先级
@Override
public void onClick(View v) {
Intent intent = new Intent("com.ssozh.broadcasttest.MY_BROADCAST");
intent.setPackage(getPackageName());
sendBroadcast(intent);
}
广播的最佳事件:实现强制下线功能
实现强制下线功能:只需要在界面上弹出一个对话框,让用户无法进行任何其他操作,必须点击对话框中的“确定”按钮,然后回到登陆界面即可。
可是存在一个问题:当用户被通知需要强制下线时,可能正处于任何一个界面,难道要在每个界面都编写一个弹出对话框的逻辑?
-
强制下线功能需要先关闭所有的activity,然后回到登陆界面。【见2点最佳实践】
-
编写登陆界面(活动):
- 创建新的Loginactivity并且继承baseActivity
- 注意使用textView + editVIew完成,其中密码使用inputType来完成。
-
编写强制退出的主界面(活动):
- 其实就是MainActivity。只包括一个按钮就是强制下线
- 该按钮的效果就是发送一条值为强制下线的广播,但不执行强制下线的操作
-
编写强制下线的broadcastreceiver:
- 很显然是一个动态的广播接收器,因此不需要再manifest.xml中注册(静态才需要)
- 可以直接写成baseActivity的内部类
- 使用AlertDialog控件,强制退出。
- 使用ActivityController销毁全部活动。
-
注册ForceOfflineReceiver:
- 重写onResume和onPause这两个生命周期函数,然后分别在里面注册和取消注册了
- 之所以要这么写是因为我们始终需要保证只有处于栈顶的活动才能接收到这个强制下线广播,非栈顶的活动不应该接受到这条广播。
-
需要修改manifest.xml,把主活动设置为loginActivity
<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.BroadcastBestPractice"> <activity android:name=".LoginActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MainActivity"></activity> </application>