Android提供了一套完整的API,允许应用程 序自由地发送和接收广播。
Android 中的广播主要可以分为两种类型:标准广播和有序广播。
动态注册监听网络变化
注册广播的方式一般有两种,在代码中注册和AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
那么该如何创建一个广播接收器呢?其实只需要新建一个类,让它继承自BroadcastReceiver, 并重写父类的onReceive()方法就行了。这样当有广播到来时,onReceive()方法 就会得到执行,具体的逻辑就可以在这个方法中处理。
动态注册:新建一个BroadcastTest项目,然后修改MainActivity中的代码
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activitymain);
intentFilter = new IntentFilterd;
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) {
Toast.makeText(context, "network changes",
Toast.LENGTH_SHORT).show(); }
}
}
我们在MainActivity中定义了一个内部类NetworkChangeReceiver,这个类是 继承自BroadcastReceiver的,并重写了父类的onReceive()方法。这样每当网络状态发生变 化时,onReceive ()方法就会得到执行,这里只是简单地使用Toast提示了一段文本信息。
然后观察onCreate()方法,首先我们创建了一个IntentFilter的实例,并给它添加了一 个值为android.net.conn.CONNECTIVTY_CHANGE的action,为什么要添加这个值呢?因为当 网络状态发生变化时,系统发出的正是一条值为android. net. conn.CONNECTIVITY_CHANGE的 广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action。接下来创建 了一个NetworkChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver 的实例和 IntentFilter 的实例都传了进去,这样 NetworkChangeReceiver 就会收到所有值为android.net.conn.CONNECTIVTY_CHANGE的广播,也就实现了 监听网络变化的功能。
最后要记得,动态注册的广播接收器一定都要取消注册才行,这里我们是在onDestroyO 方法中通过调用unregisterReceiver()方法来实现的。
不过,只是提醒网络发生了变化还不够人性化,最好是能准确地告诉用户当前是有网络还是 没有网络,因此我们还需要对上面的代码进行进一步的优化。
public class MainActivity extends AppCompatActivity (
class NetworkChangeReceiver 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.isAvailableC)) {
Toast.makeText(context, "network is available11,
Toast.LENGTH_SHORT). show();
} else {
Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT). show();
}
}
}
}
在 onReceive()方法中,首先通过 getSystemService()方法得到了 ConnectivityManager 的实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的getActiveNetwork- Info()方法可以得到Networkinfo的实例,接着调用Networkinfo的isAvailable()方法, 就可以判断出当前是否有网络了
这里访问系统的网络状态就是需要声明权限的。打开 AndroidManifest.xml文件,在里面加入如下权限就可以访问系统网络状态了
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name=ilandroid.permission.ACCESS_NETWORK_STATE" />
</manifest>
静态注册实现开机启动
动态注册的广播接收器必须要在程序启动之后才能接收到广播。
静态注册可以让程序在未启动的情况下就能接收到广播
可以使用Android Studio 的快捷方式来创建一 个广播接收器,右击 com.example.broadcasttest >New—>Other->Broadcast Receiver
我们将广播接收器命名为BootCompleteReceiver, Exported属性表示是否 允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器。勾选 这两个属性,点击Finish完成创建。
然后修改BootCompleteReceiver中的代码
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) (
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
}
}
静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,不过由于我 们是使用Android Studio的快捷方式创建的广播接收器,因此注册这一步已经被自动完成了。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<uses-permission android:name="android.permission.ACCESS NETWORK STATE" />
<application
android:allowBackup="true"
android: icon="@mipmap/ic_launcher"
android:label="@string/appname"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
</application>
</manifest>
可以看到,标签内出现了一个新的标签,所有静态的广播接收 器都是在这里进行注册的。
不过目前BootCompleteReceiver还是不能接收到开机广播的,我们还需要对AndroidManifest. xml文件进行修改才行,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<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/appname"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT__COMPLETED" />
</intent-filter>
</receiver>
</application>
</manifest>
由于Android系统启动完成后会发出一条值为android. intent. action. BOOT COMPLETED 的广播,因此我们在标签里添加了相应的action.另外,监听系统开机广播也 是需要声明权限的,可以看到,我们使用标签又加入了一条android.permission.RECEIVE_BOOT_COMPLETED 权限。
发送标准广播
在发送广播之前,我们还是需要先定义一个广播接收器来准备接收此广播才行,不然发出去 也是白发。因此新建一个MyBroadcastReceiver
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 MyBroadcast- Receiver”的提示。然后在AndroidManifest.xml中对这个广播接收器进行修改:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<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/appname"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
</application>
</manifest>
可以看到,这里让 MyBroadcastReceiver 接收一条值为 com.example.broadcasttest.
MY_BROADCAST的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广播。
接下来修改activity main.xml中的代码,如下所示:
<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:id="@+id/button"
and roid: layout_width=,,match_pa rent"
android:layout_height="wrap_content" android:text=uSend Broadcast"
/>
</LinearLayout>
这里在布局文件中定义了一个按钮,用于作为发送广播的触发点。然后修改MainActivity中 的代码,如下所示:
public class MainActivity extends AppCompatActivity {
©Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activitymain);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Intent intent = new
Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
}
});
}
可以看到,我们在按钮的点击事件里面加入了发送自定义广播的逻辑。首先构建岀了一个 Intent对象,并把要发送的广播的值传入,然后调用了 Context的sendBroadcast()方法将广 播发送出去,这样所有监听com.example.broadcasttest.MY_BROADCAST这条广播的广播接收 器就会收到消息。此时发岀去的广播就是一条标准广播。
发送有序广播
新建一个 BroadcastTest2 项目,将项目创建好之后,还需要在这个项目下定义一个广播接收器,用于接收上一小节中的自定 义广播。新建AnotherBroadcastReceiver
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中对这个广播接收器进行修改
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<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/appname"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
</application>
</manifest>
可以看到,AnotherBroadcastReceiver 同样接收的是 com.example. broadcasttest.MY_BROADCAST这条广播。现在运行BroadcastTest2项目将这个程序安装到模拟器上,然后重新回到BroadcastTest项目的主界面,并点击一下Send Broadcast按钮,就会分别弹出两次提示信息
这样就强有力地证明了,我们的应用程序发出的广播是可以被其他的应用程序接收到的。 不过到目前为止,程序里发出的都还是标准广播,现在我们来尝试一下发送有序广播。重新
回到BroadcastTest项目,然后修改MainActivity中的代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedlnstanceState) {
super.onCreate(savedlnstanceState);
setContentView(R.layout.activitymain);
Button button = (Button) findViewByld(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@0verride public void onClick(View v) {
Intent intent = new
Intent("com.example.broadcasttest.MYBROADCAST");
sendOrderedBroadcast(intent, null);
}
});
}
}
发送有序广播只需要改动一行代码,即将sendBroadcastO方法改成sendOrderedBroadcast () 方法。sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是 Intent,第二个参数是一个与权限相关的字符串,这里传入null就行了。现在重新运行程序, 并点击Send Broadcast按钮,你会发现,两个应用程序仍然都可以接收到这条广播
这个时候的广播接收器是有先后顺序的, 而且前面的广播接收器还可以将广播截断,以阻止其继续传播。
那么该如何设定广播接收器的先后顺序呢?当然是在注册的时候进行设定的了。修改AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<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/appname"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<receiver
android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
</application>
</manifest>
可以看到,我们通过android:priority属性给广播接收器设置了优先级,优先级比较高 的广播接收器就可以先收到广播。这里将MyBroadcastReceiver的优先级设成了 100,以保证它一 定会在AnotherBroadcastReceiver之前收到广播。
既然已经获得了接收广播的优先权,那么MyBroadcastReceiver就可以选择是否允许广播继 续传递了。修改MyBroadcastReceiver中的代码
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver",
Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
如果在onReceive()方法中调用了 abortBroadcast ()方法,就表示将这条广播截断,后面
的广播接收器将无法再接收到这条广播。现在重新运行程序,并点击一下Send Broadcast按钮, 你会发现,只有MyBroadcastReceiver中的Toast信息能够弹出,说明这条广播经过MyBroadcast- Receiver之后确实是终止传递了。
前面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序 接收到,并且我们也可以接收来自于其他任何应用程序的广播。
为了能够简单地解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制 发岀的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出 的广播
修改MainActivity中的代码
首先是通过LocalBroadcastManager的getlnstance()方 法得到了它的一个实例,然后在注册广播接收器的时候调用的是LocalBroadcastManager的 registerReceiver ()方法,在发送广播的时候调用的是 LocalBroadcastManager 的 sendBroadcast () 方法
本地广播是无法通过静态注册的方式来接收的
新建一个 BroadcastBestPractice项目,先创建一个Activitycollector类用于管理所有的活动
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
然后创建BaseActivity类作为所有活动的父类
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedlnstanceState) {
super・ onCreate(savedlnstanceState);
ActivityCollector.addActivity(this);
}
@0verride
protected void onDestroy() {
super.onDestroyO;
Activitycollector.removeActivity(this);
)
}
首先需要创建一个登录界面的活动,新建LoginActivity,并让Android Studio帮我们自 动生成相应的布局文件。然后编辑布局文件activityjogin.xml
修改LoginActivity中的代码
这里我们模拟了一个非常简单的登录功能。首先要将LoginActivity的继承结构改成继承自 BaseActivity,然后调用findViewById()方法分别获取到账号输入框、密码输入框以及登录按钮 的实例。接着在登录按钮的点击事件里面对输入的账号和密码进行判断,如果账号是admin并且 密码是123456,就认为登录成功并跳转到MainActivity,否则就提示用户账号或密码错误。
因此,你就可以将MainActivity理解成是登录成功后进入的程序主界面了,这里我们并不需 要在主界面里提供什么花哨的功能,只需要加入强制下线功能就可以了,修改aCtivity_main.xml 中的代码,如下所示:
然后修改MainActivity中的代码
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedlnstanceState) (
super.onCreate(savedlnstanceState);
setContentView(R.layout.activitymain);
Button forceOffline = (Button) findViewByld(R.id.force_offline);
forceOffline.setOnClickListener(new View.OnCtickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcastbestpractice.
FORCE_OFFLINEH");
sendBroadcast(intent);
}
});
}
}
不过这里有个重点,我们在按钮的点击事件里面发送了一条广播,广播的值 为 com.example.broadcastbestpractice.FORCE OFFLINE,这条广播就是用于通知程序强制 用户下线的。也就是说强制用户下线的逻辑并不是写在MainActivity里的,而是应该写在接收这 条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何 地方,只需要发出这样一条广播,就可以完成强制下线的操作了。
只需要在BaseActivity中动态注册一个广播接收 器就可以了,因为所有的活动都是继承自BaseActivity的。
先来看一下ForceOfflineReceiver中的代码,这次onReceive()方法里可不再是仅仅弹出一 个Toast 了,而是加入了较多的代码,那我们就来仔细地看看吧。首先肯定是使用 AlertDialog.Builder来构建一个对话框,注意这里一定要调用setCancelable()方法将对话 框设为不可取消,否则用户按一下Back键就可以关闭对话框继续使用程序了。然后使用 setPositiveButton()方法来给对话框注册确定按钮,当用户点击了确定按钮时,就调用 ActivityCollector的finishAll()方法来销毁掉所有活动,并重新启动LoginActivity这个活动。
再来看一下我们是怎么注册ForceOfflineReceiver这个广播接收器的,可以看到,这里重写了 onResumeO和onPause()这两个生命周期函数,然后分别在这两个方法里注册和取消注册了 ForceOfflineReceiver .
.
接下来我们还需要对AndroidManifest.xml进行修改