Android学习笔记(5)——详解广播机制

第五章 全集大喇叭——详解广播机制

5.1 广播机制简介

  1. 概述:Android中的每个应用程序都可以对自己感兴趣的广播进行注册, 这样该程序就只会接收到自己所关心的广播内容,这些广播可能是
    来自于系统的,也可能是来自于其他应用程序的。Android 提供了一套完整的API,允许应用程
    序自由地发送和接收广播。

  2. 广播的分类:

    1. 标准广播:**( Normal broadcasts)**是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的 效率会比较高,但同时也意味着它是 无法被截断的
      工作流程:
      在这里插入图片描述

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

      工作流程:
      在这里插入图片描述

5.2 接收系统广播

5.2.1 动态注册监听网络变化
  1. 概述:广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。

  2. 实现广播接收器:

    package com.example.broadcasttest;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;
    import android.os.Bundle;
    import android.widget.Toast;
    
    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 = new IntentFilter();	//创建IntentFilter实例
            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");		//为IntentFilter实例添加此action
            networkChangeReceiver = new NetworkChangeReceiver();	//创建networkChangeReceiver实例
            registerReceiver(networkChangeReceiver, intentFilter);	//调用registerReceiver进行注册,使networkChangeReceiver能收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播。
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unregisterReceiver(networkChangeReceiver);	//注意动态注册的广播接收器一定都要取消注册才行
        }
    
        class NetworkChangeReceiver extends BroadcastReceiver {		//此类继承BroadcastReceiver,每当网络状态发生变化时,onReceive()方法将被执行
            @Override
            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, "network is available", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT).show();
            }
            }
        }
    }
    

    访问权限的声明:(在注册文件中加入此代码)

        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
5.2.2 静态注册实现开机启动
  1. 用Android Studio提供的快捷方式来创建一
    个广播接收器,右击com.example.broadcasttest包建投New→Other→Broadcast Receiver,会弹出如下页面:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85Dhi0iT-1628472965688)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210731111655789.png)]
    勾选这两个属性,完成创建。

  2. 修改BootCompleteReceiver中代码

    package com.example.broadcasttest;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.Toast;
    
    public class BootCompleteReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();		//显示提示消息
        }
    }
    
  3. 修改注册代码

    <?xml version="1.0" encoding="utf-8"?>
    <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/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"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

5.3 发送自定义广播

5.3.1 发送标准广播
  1. 补充:Android8.0及以上除系统自发的广播如:开关机,网络权限,接收短信等可静态注册,别的BroadcastReceiver都得动态注册

  2. 定义一个广播接收器

    package com.example.broadcasttest;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.Toast;
    
    public class MyBroadcastReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();	//接收到广播后发出弹窗回应
        }
    }
    
  3. 创建一个按钮,用作发送广播的触发点

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/send_broadcast"
            android:id="@+id/button"/>
    
    </LinearLayout>
    
  4. 修改主活动代码为

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
         	
            //动态为广播接收器注册相应,当系统发出值为  com.example.broadcasttest.MY_BROADCAST  的广播时,此广播接受器做出响应
            registerReceiver(new MyBroadcastReceiver(), new IntentFilter("com.example.broadcasttest.MY_BROADCAST"));
            /*intentFilter = new IntentFilter();
            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
            networkChangeReceiver = new NetworkChangeReceiver();
            registerReceiver(networkChangeReceiver, intentFilter);*/
            Button button = (Button) findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");	//点击按钮,发出值为  com.example.broadcasttest.MY_BROADCAST  的广播。
                    sendBroadcast(intent);
                }
            });
        }
    
  5. 结果为,点击按钮,界面弹出 received in MyBroadcastReceiver 的弹窗

5.3.2 发送有序广播
  1. 发送有序广播秩序秩序要更改广播的发送方法:

      sendOrderedBroadcast(intent, null);	//ordered有序的
    
  2. 为广播接收器设置优先级:(由于安卓8.0以上不支持静态注册,所以创建IntentFilter对象用于广播对的动态注册,让此程序对什么广播值响应,还有设置接收器的优先级)

    		IntentFilter intentFilter = new IntentFilter("com.example.broadcasttest.MY_BROADCAST");
            intentFilter.setPriority(100);	//设置优先级为100
            registerReceiver(new MyBroadcastReceiver(), intentFilter);
    
  3. 将广播截断,使用方法 abortBroadcast(); 将广播截断,使后面能对此广播值响应的广播接收器无法接受此条广播。

5.4 使用本地广播

  1. 概述:为了能够简单地解决广播的安全性问题,Android 引人了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。

  2. 使用LocBroadcastManager对象来对广播进行管理,并提供了发送广播和注册广播接收器的方法。

    package com.example.broadcasttest;
    
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.localbroadcastmanager.content.LocalBroadcastManager;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        private IntentFilter intentFilter;
        private LocalReceiver localReceiver;
        private LocalBroadcastManager localBroadcastManager;
        //private  NetworkChangeReceiver networkChangeReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //为广播注册
            /*intentFilter = new IntentFilter("com.example.broadcasttest.MY_BROADCAST");
            intentFilter.setPriority(100);
            registerReceiver(new MyBroadcastReceiver(), intentFilter);*/
    
            localBroadcastManager = LocalBroadcastManager.getInstance(this); //获取实例
    
            /*intentFilter = new IntentFilter();
            intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
            networkChangeReceiver = new NetworkChangeReceiver();
            registerReceiver(networkChangeReceiver, intentFilter);*/
            Button button = (Button) findViewById(R.id.button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                    //sendBroadcast(intent);
                    //sendOrderedBroadcast(intent, null);
                    localBroadcastManager.sendBroadcast(intent);
                }
            });
            intentFilter = new IntentFilter("com.example.broadcasttest.LOCAL_BROADCAST");	//动态注册广播接收器
            localReceiver = new LocalReceiver();	
            localBroadcastManager.registerReceiver(localReceiver,intentFilter);		//使用localBroadcastManager类的方法为广播接收器注册。
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //unregisterReceiver(networkChangeReceiver);
            localBroadcastManager.unregisterReceiver(localReceiver);	//为动态注册的广播接收器取消注册
        }
    
        class NetworkChangeReceiver extends BroadcastReceiver {
            @Override
            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, "network is available", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT).show();
                }
            }
        }
    
        class LocalReceiver extends BroadcastReceiver {
    
            @Override
            public void onReceive(Context context, Intent intent) {
                Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
  3. 小结使本地广播的优势:

    1. 可以明确地知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄漏。
    2. 其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患。
    3. 发送本地广播比发送系统全局广播将会更加高效。

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

  1. **强制下线功能需要先关闭掉所有的活动,然后回到登录界面。**如果你的反应足够快的话,应该会想到我们在第2章的最佳实践部分早就已经实现过关闭所有活动的功能了,因此这里只需要
    使用同样的方案即可。

  2. 先实现一次性关闭所有活动功能:

    创建ActivityCollector类用于将所有活动储存起来,便于一起操作

    package com.example.broadcastbestpractice;
    
    
    import android.app.Activity;
    
    import java.util.ArrayList;
    import java.util.List;
    
    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类,使器程为所有活动的父类,通过此类将新开的活动添加进ActivityCollector类进行存储:

    package com.example.broadcastbestpractice;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.os.Bundle;
    
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AlertDialog;
    import androidx.appcompat.app.AppCompatActivity;
    
    public class BaseActivity extends AppCompatActivity {
    
        private ForceOfflineReceiver receiver;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {	//在活动创建时将活动添加
            super.onCreate(savedInstanceState);
            ActivityCollector.addActivity(this);
        }
    
        @Override	//进入可见时注册广播
        protected void onResume() {
            super.onResume();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("com.example.broadcastbestpractice.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(Context context, Intent intent) {
                AlertDialog.Builder builder = new AlertDialog.Builder(context);		//创建弹窗
                builder.setTitle("Warning");	//设置弹窗标题为Warning
                //设置弹窗内容
                builder.setMessage("You are force to be offline. Please try to login again.");
                builder.setCancelable(false);	//设置弹窗权限,使其无法通过back键退出
                builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {	//在下部设置按钮,名字为OK
                    @Override	//设置按钮响应事件
                    public void onClick(DialogInterface dialogInterface, int i) {
                        ActivityCollector.finishAll();	//结束所有活动	
                        Intent intent = new Intent(context, LoginActivity.class);	//从此活动跳转至登录页面
                        context.startActivity(intent);	//开启登录页面活动
                    }
                });
                builder.show();
            }
        }
    }
    
    
  3. 创建登录界面活动,修改注册代码,使此活动为主活动

    package com.example.broadcastbestpractice;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Toast;
    
    public class LoginActivity extends AppCompatActivity {
    
        private EditText accountEdit;
    
        private EditText passwordEdit;
    
        private Button login;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
    
            accountEdit = (EditText) findViewById(R.id.account);
            passwordEdit = (EditText) findViewById(R.id.password);
            login = (Button) findViewById(R.id.login);
            login.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String account = accountEdit.getText().toString();
                    String password = passwordEdit.getText().toString();
                    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();	//密码或账号错误进行弹窗提示
                    }
                }
            });
        }
    }
    
  4. 设置登录活动的布局:

    一个竖的线性布局里嵌套两个横的线性布局:

    横的布局用于存放提示信息和输入数据

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <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="@string/account"/>
    
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/account"
            android:layout_gravity="center_vertical"/>
        </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="@string/password"/>
        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>
        </LinearLayout>
        <Button	
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:text="@string/login"
            android:id="@+id/login"/>
    
    </LinearLayout>
    

    页面展示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVCKBq61-1628472965691)(C:\Users\过客\AppData\Roaming\Typora\typora-user-images\image-20210801213148434.png)]

  5. 修改主活动代码:

    package com.example.broadcastbestpractice;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    
    public class MainActivity extends BaseActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Button forceOffline = (Button) findViewById(R.id.force_offline);
            forceOffline.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {	//点击按钮,发送广播
                    Intent intent = new Intent("com.example.broadcastbestpractice.FORCE_OFFLINE");
                    sendBroadcast(intent);
                }
            });
         }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值