广播机制简介
Android提供了一条完整的API,允许应用程序自由的发送和接收广播。
一、广播的类型:标准广播和有序广播
**标准广播:**是一种完全异步执行的广播,在广播发出之后,所有的广播接收器(Broadcast Receiver)几乎都会在同一时刻接收到这条广播消息,没有先后顺序,效率高,但是无法被截断。
1.标准广播的工作流程。
有序广播:是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先接收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样接收器就无法收到广播消息了。
1、有序广播的工作流程:
二、接收系统广播
动态注册监听网络变化
1、注册广播的方式有两种:在代码中注册,在AndroidManifest.xml中注册,前者称为动态注册,后者被称为静态注册。
2、动态注册的广播接收器可以自由地控制注册与注销,但是他必须在程序启动之后才能接收到广播,因为注册的逻辑在onCreate()方法中。
3、创建一个广播接收器的方法,只需要新建一个类,继承BroadcastReceiver,并重写父类的onReceive()方法就行了,当有广播到来时,onReceive()就会被执行,具体逻辑就在这个方法中。
4、动态注册的方式编写一个能够监听网络变化的程序。新建一个项目,然后修改MainActivity代码。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建IntentFilter实例,并添加了android.net.com.CONNECTIVITY_CHANGE的action
//当网络状态发生变化时,系统发出的正是一条之为android.net.com.CONNECTIVITY_CHANGE、的广播
//广播监听器想要监听的广播就在这里添加相应的avtion
intentFilter=new IntentFilter();
intentFilter.addAction("android.net.com.CONNECTIVITY_CHANGE");
//创建networkChangeReceiver实例,调用registerReceiver()进行注册
// networkChangeReceiver就会接收到所有值为android.net.com.CONNECTIVITY_CHANGE的广播
networkChangeReceiver=new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver,intentFilter);
}
/**
* 动态注册的广播监听器一定都要取消注册,通过onDestory的unregisterReceiver()方法。
*/
protected void onDestory(){
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
/**
* 注册内部类NetworkChangeReceiver继承BroadcastReceiver,重写onReceive()
* 每当网络状态发生变化时,onReceive()就会得到执行
*/
class NetworkChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"network changes",Toast.LENGTH_SHORT);
}
}
}
《1》、注册内部类NetworkChangeReceiver继承BroadcastReceiver,重写onReceive(),每当网络状态发生变化时,onReceive()就会得到执行。通过Toast提醒网络发生变化。
《2》、 创建IntentFilter实例,并添加了android.net.com.CONNECTIVITY_CHANGE的action,当网络状态发生变化时,系统发出的正是一条值为ndroid.net.com.CONNECTIVITY_CHANGE、的广播,我们广播监听器想要监听的广播,就在这里添加相应的avtion。
《3》、创建networkChangeReceiver实例,调用registerReceiver()进行注册,将networkChangeReceiver和IntentFilter实例传入,networkChangeReceiver就会接收到所有值为android.net.com.CONNECTIVITY_CHANGE的广播。
《4》、动态注册的广播监听器一定都要取消注册,通过onDestory的unregisterReceiver()方法。
《5》、优化 提示网络发生变化
通过getSystemService()得到ConnectivityManager实例,这是一个系统服务,专门用于管理网络连接。调用getActiveNetworkInfo()可以得到NetworkInfo,调用NetworkInfo的isAvailable()就可以判断出当前是否有网络。
class NetworkChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
/**
* 通过getSystemService()得到ConnectivityManager实例,这是一个系统服务,专门用于管理网络连接。
* 调用getActiveNetworkInfo()可以得到NetworkInfo,调用NetworkInfo的isAvailable()就可以判断出当前是否有网络。
*/
ConnectivityManager connectivityManager= (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();
if(networkInfo !=null &&networkInfo.isAvailable()){
Toast.makeText(context,"network is available",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context,"network is unavailble",Toast.LENGTH_SHORT).show();
}
}
}
《6》、重点说明:Android系统为了保护用户设备的安全和隐私,做了严格规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序直接崩溃。
访问的网络状态就是需要声明权限:打开AndroidManifest.xml文件,在里面加入如下权限可以访问系统网络状态。
静态注册实现开机启动
静态注册可以让程序在未启动的情况下就你能接收到广播。
1、使用Android Studio提供的快捷方式来创建一个广播接收器。右击com.example.broadcasttest 包—》New–》Other—》Broadcast Receiver,会弹出一个窗口。
2、将广播接收器命名为BootCompleteReceiver,Exported属性表示是允许这个广播接收器接受本程序以外的广播,Enabled属性表示是否启用这个广播接收器,勾选这两个属性,点击完成创建。
3、修改BootCompleteReceiver中的代码。
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show();
}
}
注意:静态广播接收器一定要在AndroidManifest.xml中注册才可以用,由于用Android Studio快捷提供的广播接收器,内部自动创建。
4、在标签内出现一个新的标签,所有的静态广播接收器都是在这里进行注册的。跟标签相似,也是通过 android:name=".BootCompleteReceiver"来指定具体注册哪一广播接收器。
5、BootCompleteReceiver还是不能接收到开机广播的,对AndroidManifest.xml文件进行修改进行。Andriod系统启动之后完成之后会发出一条值为android.intent.action.BOOT_COMPLETED的广播,在添加相应的action,另外监听系统开机广播也需要权限。
<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/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
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>
<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>
需注意:不要在onReceive()方法中添加过多的逻辑或者进行任何耗时操作,因为在广播接收器中不允许开线程,当onReceive()运行了较长时间而没有结束,程序就会报错。
发送自定义广播
发送标准广播
1、新建一个项目,定义一个广播接受器MyBroadcastReceiver 来准备接收此广播才行。
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
}
}
2、在AndroidManifest.xml中对这个广播接收器进行修改,让MyBroadcastReceiver 接收到一条值为com.example.broadcasttest.MY_BROADCAST的广播。
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
3、在布局文件夹中定义一个按钮,用于作为发送广播的触发点。
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button_1"
android:text="Send Broadcast" />
4、修改MainActivity中的代码,创建Intent 对象,把要发送的广播得值传入,调用了Context的sendBroadcast()将广播发送出去。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 把要发送的广播得值传入 调用了Context的sendBroadcast()将广播发送出去
*/
Intent intent=new Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
}
});
}
发送有序广播
广播是一种可以跨进程的通信方式,在应用程序内发出的广播,其他的应用程序应该也是可以收到的。
1、新建一个项目,点击Android Studio导航栏----》File—》New—》New Project进行创建。
2、定义一个广播接收器,用于接收上一小节中的自定义广播。新建,在弹出消息,在AndroidManifest.xml中对这个广播接收器进行修改。
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"received in MyReceiver",Toast.LENGTH_SHORT).show();
}
}
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
3、把这个项目运行安装到模拟器中,然后重新回到BroadcastText项目的主界面,并点击一下Send Broadcast按钮,就会分别弹出两次提示信息。这样证明我们的应用程序发出的广播是可以被其他的应用程序接收到的.(不过现在还是发送的标准广播)
4、接下来发送有序广播。重新回到BroadcastTest项目,修改MainActivoty中的代码。发送有序广播只需要该一行代码,即将sendBroadcast()方法改成sendOrderBroadcast()方法,在这个方法接受两个参数,第一个参数任然是Intent,第二个参数是一个与权限相关的字符串。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 把要发送的广播得值传入 调用了Context的sendBroadcast()将广播发送出去
**/
Intent intent=new Intent("com.example.broadcasttest.MY_BROADCAST");
/**
* 将标准广播变成有序广播 第一个参数是intert 第二个参数是权限有观的
**/
sendOrderedBroadcast(intent,null);
}
});
}
}
5、设定广播接收器的先后顺序,在注册时进行设定,修改AndroidManifest.xml中的代码。通过android:priority 属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先接收到广播。
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
6、获得接收器的优先权,那么MyBroadcastReceiver就可以选择是否允许广播继续传递了。在onReceiver()调用abroadBroadcast()方法,就表示将这条广播截断,后面的广播接收器将无法在接受到这条广播。
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
/**
* 将广播截断 不允许下一个广播接收器接受这条广播。
*/
abortBroadcast();
}
}
使用本地广播
1、前面发送和接受的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,并且可以接收到其他应用程序的广播。
2、本地广播是Android引入的一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接受来自本地应用程序发出的广播。解决安全性问题。
3、本地广播是无法通过静态注册的方式来接收的。因为静态注册是在程序未启动时就能够接收到广播,而发送本地广播时,是我们的程序已启动。
4、本地广播使用:主要是使用了一个LocalBroadcastManager来对广播进行管理,并提供发送广播和注册广播接收器的方法。修改ManiActivity中的代码。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager=LocalBroadcastManager.getInstance(this);
Button button=findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 把要发送的广播得值传入 调用了Context的sendBroadcast()将广播发送出去
*/
Intent intent=new Intent("com.example.broadcasttest.MY_BROADCAST");
// sendBroadcast(intent);
/**
* 发送本地广播
*/
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter=new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.MY_BROADCAST");
localReceiver=new LocalReceiver();
//注册本地广播监听器
localBroadcastManager.registerReceiver(localReceiver,intentFilter);
}
@Override
public void onDestroy(){
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"received local broadcast",Toast.LENGTH_SHORT).show();
}
}
}
5、使用本地广播的几点优势:
《1》、可以明确知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄露。
《2》、其他的程序无法将广播发送到我们程序的内部,因此不需要但心会有安全泄露的隐患。
《3》、发送本地广播比发送系统全局广播将会更加高效。
广播的最佳实践—实现强制下线功能
1、强制下线功能需要先关闭所有的活动,然后回到登录界面。先创建一个ActivityCollector类用于管理所有活动的功能。
public class ActivityCollector {
private 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();
}
}
}
}
2、然后创建BaseActivity类作为所有活动的父类。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
3、创建一个登录界面的活动,新建LoginActivity,并让Android Studio自动生成相应的布局,然后编写布局文件activity_login.xml,使用LinearLayout编写出一个登陆布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Account:"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:id="@+id/account"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Password"/>
<EditText
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:id="@+id/password"
android:inputType="textPassword"/>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/login"
android:text="Login"/>
</LinearLayout>
4、修改LoginActivity中的代码。首先将LoginActivity的继承结构改成继承自BaseActivity,然后调用方法获取到账号输入框、密码框以及登录按钮的实例。进行判断,登陆成功后跳转到MainActivity,否则就提示用户账号或密码错误。
public class LoginActivity extends BaseActivity {
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
accountEdit = findViewById(R.id.account);
passwordEdit = findViewById(R.id.password);
login = findViewById(R.id.login);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
//如果账号为admin且密码是123456就认为登录成功
if (account.equals("admin") && password.equals("123456")) {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
}else{
Toast.makeText(LoginActivity.this,"account or password is invalid",Toast.LENGTH_SHORT).show();
}
}
});
}
}
5、修改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/force_offine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send force offline broadcast"/>
</LinearLayout>
6、修改MainActuivity中的代码。注重点:在按钮的点击事件中发送一条广播,广播的值为,这条广播用于通知程序强制用户下线的。强制用户下线的逻辑写在接受这条广播的广播接收器里面。
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button forceOffline=findViewById(R.id.force_offine);
forceOffline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 点击按钮事件发送一条广播
*/
Intent intent=new Intent("com.exapmle.broadcastBestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
7、创建广播接收器,来接收这条强制下线的广播。在BaseActivity中动态注册一个广播接收器就可以,因为所有的活动都是继承BaseActivity。
《1》、创建对话框,将对话框设为不可取消,使用setPostiveButton()方法来给对话框注册确定按钮,当用户点击确定按钮,就会调用ActivityCollector的finishAll()方法来注销掉所有的活动并重新启动LoginActivity这个活动。
《2》、注册广播接收器,重写onResume()和onPause()方法,需要保证只有处于栈顶的活动才能接收到这条强制下线的广播,非栈顶的活动不应该也没有必要去接受这条广播。
public class BaseActivity extends AppCompatActivity {
private ForceOfflineReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.exapmle.broadcastBestpractice.FORCE_OFFLINE");
receiver = new ForceOfflineReceiver();
registerReceiver(receiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}
class ForceOfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
AlertDialog.Builder builder=new AlertDialog.Builder(context);
builder.setTitle("Warning");
builder.setMessage("You are forced to be offline Please try to login again.");
builder.setCancelable(false);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();//销毁所欲活动
Intent intent1=new Intent(context,LoginActivity.class);
context.startActivity(intent);//重新启动登陆界面
}
});
builder.show();
}
}
}
8、需要对AndroidManifest.xml文件进行修改。需要将主活动设置为LoginActivity。
<activity android:name=".MainActivity"></activity>
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>