第五章:详解广播机制

安卓中每个应用程序都可以对自己感兴趣的广播进行注册,广播可来自系统也可来自应用程序。

  • 标准广播(Normal broadcasts)

    • 一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的
  • 有序广播(Ordered broadcasts)

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

尝试接收系统广播

动态注册监听网络变化

  • 新建BroadcastTest项目
  • 在AndroidManifest.xml中添加代码注册网络权限
1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  • 修改MainActivity中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class  extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}


protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {


public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
}
}
}

这里定义了内部类NetworkChangeReceiver继承自BroadcastReceiver然后重写了onReceive函数,在onCreate中创建了一个IntentFilter的实例,并且添加了android.net.conn.CONNECTIVITY_CHANGE这个action,这是网络改变的时候系统会发出的一条广播的值,调用registerReceiver将两个实例传入就可以接收到这条广播了

  • 添加对是否有网络的判断,修改MainActivity中NetworkChangeReceiver的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class  extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}


protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {
public void onReceive(Context context,Intent intent){
ConnectivityManager connectivityManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();

if(networkInfo!=null && networkInfo.isAvailable()){
Toast.makeText(context, "网络已连接", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context, "网络已断开", Toast.LENGTH_SHORT).show();
}

}
}
}

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

静态注册实现开机启动

动态注册的广播比较自由,但是必须在程序启动以后才能收到广播,要让程序在未启动的时候收到广播就要用静态注册的方法

  • 新建一个广播接收器
    右键点击包名,新建,选择Other-Broadcast Receiver,这里命名为BootCompleteReceiver,Exported属性表示是否允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器,都勾选上
  • 修改BootCompleteReceiver的代码
1
2
3
4
5
6
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
}
}
  • 在AndroidManifest.xml文件中注册静态广播(一定要)
1
2
3
4
5
6
7
</application>
···
<receiver android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
</receiver>
</application>

在application标签内添加receiver标签注册静态广播,用法和 标签非常相似,也是通过android:name来指定具体注册哪一个广播接收器,而enabled和exported属性则是根据我们刚才勾选的状态自动生成的

  • 添加接收开机广播的权限,同样修改AndroidManifest.xml
1
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
1
2
3
4
5
6
7
8
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>

发送自定义广播

发送标准广播

  • 定义一个广播接收器,准备接收自己的广播,新建一个MyBroadcastReceiver
1
2
3
4
5
6
7
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "收到自己的广播了!", Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
  • 修改AndroidManifest.xml
1
2
3
4
5
6
<receiver android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter></receiver>

这里让MyBroadcastReceiver接收一条com.example.broadcasttest.MY_BROADCAST的广播,也就是我们要发出的广播

  • 修改activity_main.xml的代码
1
2
3
4
5
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="按钮"/>
  • 修改MainActivity中的代码
1
2
3
4
5
6
7
8
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent);
}
});

为按钮添加事件,发送一个广播

发送有序广播

广播是跨进程的通信方式,创建一个BroadcastTest2项目

  • 新建一个AnotherBroadcastReceiver继承自BroadcastReceiver
1
2
3
4
5
6
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "在另外的软件里收到了广播", Toast.LENGTH_SHORT).show();
}
}
  • 修改AndroidManifest.xml文件
1
2
3
4
5
6
7
<receiver android:name=".AnotherBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>

这里AnotherBroadcastReceiver同样接收com.example.broadcasttest.MY_BROADCAST广播

  • 在BroadcastTest项目中点击按钮就会弹出两次提示,说明两个程序都收到了广播

  • 发送有序广播,修改BroadcastTest项目中MainActivity中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class  extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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");
sendOrderedBroadcast(intent, null);
}
});
...
}
...
}

发送有序广播只需要改动一行代码,即将sendBroadcast()方法改成sendOrderedBroadcast()方法。sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是Intent,第二个参数是一个与权限相关的字符串,这里传入null就行了

  • 设定有序广播的接收顺序

修改AndroidManifest.xml中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest2">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<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>
</application>
</manifest>

通过android:priority属性给广播接收器设定了优先级

  • 选择是否允许广播继续传播

修改MyBroadcastReceiver中的代码

1
2
3
4
5
6
7
8
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver",
Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}

这里表示将这条广播截断

使用本地广播

前面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播。这样就很容易引起安全性的问题,比如说我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序不停地向我们的广播接收器里发送各种垃圾广播。
为了能够简单地解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。

本地广播使用LocalBroadcastManager来管理广播

  • 修改MainActivity中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest.LOCAL_
BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 发送本地广播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注
册本地广播监听器
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public 大专栏  第五章:详解广播机制="keyword">void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).
show();
}
}
}

广播的最佳实践-实现强制下线功能

创建ActivityCollector类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
}
}
activities.clear();
}
}

创建BaseActivity类作为所有活动的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

新建LoginActivity,这里选择空白活动

  • 编辑activity_login.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".LoginActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_marginLeft="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="账号"/>

<EditText
android:id="@+id/EditText_UserName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="输入账号"
android:maxLines="1"
android:layout_weight="1"
android:layout_marginRight="30dp"/>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_marginLeft="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码"/>

<EditText
android:id="@+id/EditText_Password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="输入密码"
android:maxLines="1"
android:layout_weight="1"
android:inputType="textPassword"
android:layout_marginRight="30dp"/>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:id="@+id/Button_Login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登陆"/>
</LinearLayout>

</LinearLayout>
  • 修改LoginActivity中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class LoginActivity extends BaseActivity {
private EditText userNameEdit;
private EditText passwordEdit;


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

Button loginButton=findViewById(R.id.Button_Login);
userNameEdit=findViewById(R.id.EditText_UserName);
passwordEdit=findViewById(R.id.EditText_Password);

loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName=userNameEdit.getText().toString();
String password=passwordEdit.getText().toString();
if(userName.equals("admin")){
if(password.toString().equals("admin")){
Intent intent=new Intent(LoginActivity.this,MainActivity.class);
startActivity(intent);
}
}else{
Toast.makeText(LoginActivity.this, "输入有误!", Toast.LENGTH_SHORT).show();
}
}
});
}
}

修改MainActivity

  • 修改activity_main.xml的代码添加一个按钮
  • 修改MainActivity中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class  extends BaseActivity {

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

Button button=findViewById(R.id.Button_Main);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent("com.zanecode6574.cquclass.broadcastpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}

由于广播接收器里面需要弹出一个对话框来阻塞用户的正常操作,但如果创建的是一个静态注册的广播接收器,是没有办法在onReceive()方法里弹出对话框这样的UI控件的,而我们显然也不可能在每个活动中都去注册一个动态的广播接收器。
那么只需要在BaseActivity中动态注册一个广播接收器就可以了,因为所有的活动都是继承自BaseActivity的。

修改BaseActivity中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class BaseActivity extends AppCompatActivity {
private ForceOfflineReceiver receiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}

@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.zanecode6574.cquclass.broadcastpractice.FORCE_OFFLINE");
receiver=new ForceOfflineReceiver();
registerReceiver(receiver,intentFilter);
}

@Override
protected void onPause() {
super.onPause();
if(receiver!=null){
unregisterReceiver(receiver);
receiver=null;
}
}

@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}


class ForceOfflineReceiver extends BroadcastReceiver{
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder builder=new AlertDialog.Builder(context);
builder.setTitle("警告");
builder.setMessage("强制下线!重新登录!");
builder.setCancelable(false);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();
Intent intent=new Intent(context,LoginActivity.class);
context.startActivity(intent);
}
});
builder.show();
}
}
}

onReceive()方法里使用AlertDialog.Builder来构建一个对话框,注意这里一定要调用setCancelable()方法将对话框设为不可取消,否则用户按一下Back键就可以关闭对话框继续使用程序了。然后使用setPositiveButton()方法来给对话框注册确定按钮,当用户点击了确定按钮时,就调用ActivityCollector的finishAll()方法来销毁掉所有活动,并重新启动LoginActivity这个活动。
注册ForceOfflineReceiver这个广播接收器的方法,可以看到,这里重写了onResume()和onPause()这两个生命周期函数,然后分别在这两个方法里注册和取消注册了ForceOfflineReceiver。
之前都是在onCreate()和onDestroy()方法里来注册和取消注册广播接收器的,因为我们始终需要保证只有处于栈顶的活动才能接收到这条强制下线广播,非栈顶的活动不应该也没有必要去接收这条广播,所以写在onResume()和onPause()方法里就可以很好地解决这个问题,当一个活动失去栈顶位置时就会自动取消广播接收器的注册。

修改AndroidManifest.xml

这里将主活动设置为LoginActivity即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zanecode6574.cquclass.broadcastpractice">

<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />

<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">
<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>

</manifest>

猜你喜欢

转载自www.cnblogs.com/lijianming180/p/12347500.html