Android初体验——全局大喇叭(广播)

广播简介

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

广播主要分为两种类型:标准广播有序广播

标准广播

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

有序广播

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

接收系统广播

Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统状态的信息。如果想要接收到这些广播,就需要使用广播接收器

动态注册监听网络变化

广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有广播发出时,广播接收器就能接收到该广播,并在内部处理相应的逻辑。注册广播的方式有两种,在代码中注册和在AndroidMainfest.xml中注册,其中前者也被称为动态注册,后者被称为静态注册
创建广播接收器的方法,只需要创建一个类,让它继承BroadcastReceiver,并重写父类的onReceive()方法。这样当有广播时,onReceive()方法就会得到执行,具体的逻辑就会在这个方法中处理。
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 = new IntentFilter();
        //创建了一个intentFilter的实例 给它添加了一个action 
        //想要监听什么广播,就添加相应的action
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        //创建一个NetworkChangeReceiver实例 
        networkChangeReceiver = new NetworkChangeReceiver();
        //进行注册 将networkChangeReceiver,intentFilter传入
        //这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播
        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();
        }
    }
}

运行程序,当网络发生变化时Toast会进行提醒。
优化代码,使它可以准确告诉用户当前是有网络还是没有网络。
MainActivity

    class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        //每当网络状态发生变化时 这个方法就会执行
        public void onReceive(Context context, Intent intent) {
            //通过getSystemService()方法得到ConnectivityManager实例
            //这是一个系统服务类 专门用于管理网络连接的
            ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            //调用getActiveNetworkInfo()方法得到NetworkInfo实例
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            //调用NetworkInfo的isAvailable()方法 可以判断当前是否有网络
            if(networkInfo != null && networkInfo.isAvailable()) {
                Toast.makeText(context, "有网啦!!", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(context, "没网了...", Toast.LENGTH_SHORT).show();
            }
        }
    }

Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权限才可以,否则程序将会直接崩溃。

比如这里访问系统的网络状态就是需要声明权限的。打开AndroidManifest.xml文件,在里面加入如下权限就可以访问系统网络状态了:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Android中有许多操作都需要声明权限才可以进行
运行程序

在这里插入图片描述
在这里插入图片描述

静态注册实现开机启动

动态注册的广播接收器可以自由的控制注册和注销,在灵活性方面有很大的优势,但是只能在程序启动后才能接收到广播,因为逻辑是写在onCreate()方法中。使用静态注册就可以在程序没有启动的前提下接收到广播。
这里我们准备让程序接收一条开机广播,当收到这条广播时就可以在onReceive()方法里执行相应的逻辑,从而实现开机启动的功能。可以使用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) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        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/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"></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>

<application>标签内出现了一个新的标签<receiver>所有静态的广播接收器都是在这里进行注册的。它的用法其实和标签非常相似,也是通过android:name来指定具体注册哪一个广播接收器,而enabledexported属性则是根据我们刚才勾选的状态自动生成的。
不过目前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/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>

由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED的广播,因此我们在标签里添加了相应的action。另外,监听系统开机广播也是需要声明权限的,可以看到,我们使用<uses-permission>标签又加入了一条"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();
    }
}

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/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </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>

可以看到,这里让MyBroadcastReceiver 接收一条值为"com.example.broadcasttest.MY_BROADCAST"的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广播。接下来修改activity_ main.xml 中的代码

<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:id="@+id/button"
       android:text="Send Broadcast" />

</LinearLayout>

在该布局中定义一个按钮,用于作为发送广播的触发点。
MainActivity

    @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 view) {
                Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }

我们在按钮的点击事件里面加入了发送自定义广播的逻辑。首先构建出了一个Intent对象,并把要发送的广播的值传人,然后调用了Context 的sendBroadcast()方法将广播发送出去,这样所有监听"com.example.broadcasttest.MY_BROADCAST" 这条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
重新运行程序,并点击一下Send Broadcast按钮
在这里插入图片描述
另外,由于广播是使用Intent进行传递的,因此你还可以在Intent中携带一些数据传递给广播接收器。

发送有序广播

广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看出来了。因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。

AnotherBroadcastReceiver

public class AnotherBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml

<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:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastTest2">
        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </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>

AnotherBroadcastReceiver 同样接收的是"com.example.broadcasttest.MY_BROADCAST"这条广播。现在运行BroadcastTest2项目将这个程序安装到模拟器上,然后重新回到BroadcastTest项目的主界面,并点击一下Send Broadcast 按钮,就会分别弹出两次提示信息
在这里插入图片描述

在这里插入图片描述
发送有序广播
MainActivity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //intentFilter = new IntentFilter();
        //创建了一个intentFilter的实例 给它添加了一个action
        //想要监听什么广播,就添加相应的action
        //intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        //创建一个NetworkChangeReceiver实例
        //networkChangeReceiver = new NetworkChangeReceiver();
        //进行注册 将networkChangeReceiver,intentFilter传入
        //这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播
        //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");
                //sendBroadcast(intent);
                sendOrderedBroadcast(intent, null);
            }
        });
    }

将sendBroadcast改为sendOrderedBroadcast,sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是Intent,第二个参数是一个与权限相关的字符串,这里传人null就行了。这个时候的广播接收器是有先后顺序的,而且前面的广播接收器还可以将广播截断,以阻止其继续传播。
如何设定广播接收器的先后顺序
在注册的时候进行设定的了,修改AndroidManifest.xml中的代码

            <intent-filter android:priority="100">
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>

可以看到,我们通过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();
    }
}

abortBroadcast()截断广播
现在重新运行程序,并点击一下Send Broadcast按钮,只有MyBroadcastReceiver中的Toast信息能够弹出,说明这条广播经过MyBroadcast-Receiver之后确实是终止传递了。

使用本地广播

前面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播。这样就很容易引起安全性的问题,
为了能够简单地解决广播的安全性问题,Android引人了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。
本地广播的用法并不复杂,主要就是使用了一个LocalBroadcastManager,来对广播进行管理,并提供了发送广播和注册广播接收器的方法。
修改MainActivity中的代码

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);
        //获取实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        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");
                //发送广播
                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 void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
        }
    }

    class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        //每当网络状态发生变化时 这个方法就会执行
        public void onReceive(Context context, Intent intent) {
            //通过getSystemService()方法得到ConnectivityManager实例
            //这是一个系统服务类 专门用于管理网络连接的
            ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            //调用getActiveNetworkInfo()方法得到NetworkInfo实例
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            //调用NetworkInfo的isAvailable()方法 可以判断当前是否有网络
            if(networkInfo != null && networkInfo.isAvailable()) {
                Toast.makeText(context, "有网啦!!", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(context, "没网了...", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

这里我们在按钮的点击事件里面发出了一条"com.example.broadcasttest.LOCAL_BROADCAST"广播,然后在LocalReceiver里去接收这条广播。重新运行程序,并点击SendBroadcast按钮

在这里插入图片描述
如果在BroadcastText2中也接收这条广播是接收不到的,因为此条广播只会在BroadcastText中传播。

本地广播无法通过静态注册的方式接收,因为静态注册主要就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,我们的程序已将启动了,因此完全不需要静态注册。

使用本地广播的优势

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

实现强制下线功能

思路:只需要在界面上弹出一个对话框,让用户无法进行其他操作,必须点击对话框中的确定按钮,然后回到登录界面即可。
借助广播,可使不论用户在哪个界面都可弹出该对话框。

创建一个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 savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

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

首先需要创建一个登录界面的活动,新建LoginActivity,并让Android Studio 帮我们自动生成相应的布局文件。然后编辑布局文件activity_ login.xml

<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="Account:"/>

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/account"
            android:layout_weight="1"
            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="Password:"/>

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/password"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>

    </LinearLayout>

    <Button
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:id="@+id/login"
        android:text="Login"/>


</LinearLayout>

这里我们使用LinearLayout 编写出了一个登录布局,最外层是一个纵向的LinearLayout,里面包含了3行直接子元素。第一行是一个横向LinearLayout,用于输人账号信息;第二行也是一个横向的LinearLayout,用于输人密码信息;第三行是一个登录按钮。
接下来修改LoginActivity中的代码

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 = (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();
                //如果账号是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();
                }
            }
        });
    }
}

可以将MainActivity作为登录成功后的主页面,在主页面添加强制下线功能
activity_main.xml设置按钮强制下线

<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:id="@+id/force_offline"
        android:text="Send force offline broadcast"/>
    
</LinearLayout>

MainActivity

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);
            }
        });
    }
}

我们在按钮的点击事件里面发送了一条广播,广播的值为com.example.broadcastbestbestpractice.FORCE_OFFLINE, 这条广播就是用于通知程序强制用户下线的。也就是说强制用户下线的逻辑并不是写在MainActivity里的,而是应该写在接收这条广播的广播接收器里面,这样强制下线的功能就不会依附于任何的界面,不管是在程序的任何地方,只需要发出这样一条广播,就可以完成强制下线的操作了。
接下来我们就需要创建一个广播接收器来接收这条强制下线广播,由于广播接收器里面需要弹出一个对话框来阻塞用户的正常操作,但如果创建的是一个静态注册的广播接收器,是没有办法在onReceive()方法 里弹出对话框这样的UI控件的,而我们显然也不可能在每个活动中都去注册一个动态的广播接收器。只需要在BaseActivity中动态注册一个广播接收器就可以了,因为所有的活动都是继承自BaseActivity 的。
修改BaseActivity中的代码

public class BaseActivity extends AppCompatActivity {
    private ForceOfflineReceiver receiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    //在onResume(),onPause()方法中注册和取消注册广播接收器保证只有处于栈顶的活动才能接收到这条强制下线的广播
    //非栈顶的活动没有必要接收这条广播
    //当一个活动失去栈顶位置时就会自动取消广播接收器的注册
    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE");
        //注册ForceOfflineReceiver
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(receiver != null) {
            //取消注册ForceOfflineReceiver
            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
            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 dialogInterface, int i) {
                    //销毁所有活动
                    ActivityCollector.finishAll();
                    //重新启动LoginActivity这个活动
                    Intent intent = new Intent(context, LoginActivity.class);
                    //重新启动LoginActivity
                    context.startActivity(intent);
                }
            });
            builder.show();
        }
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcastbestpractice">

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

</manifest>

将主活动设置为LoginActivity登录页面。
运行程序
登录成功后会进入主页面,点击按钮会发出一条强制下线的广播,ForceOfflineReceiver里收到这条广播后会弹出一个对话框提示用户已被强制下线。
在这里插入图片描述
在这里插入图片描述
这时用户将无法再对界面的任何元素进行操作,只能点击确定按钮,然后会重新回到登录界面。这样,强制下线功能就已经完整地实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值