在一些社交帐号中,强制下线是一个比较常见的功能,比如异地登陆。而实现强制下线功能的思路其实只是在界面上弹出一个对话框,让用户无法进行任何操作,比如点击对话框回到登录界面。这种功能就可以借助广播功能来实现。
强制下线功能需要先关闭所有的Activity,然后回到登录界面。在此之前,先创建一个ActivityCollector类用于管理所有的Activity:
- package com.example.broadcastbestpractice
-
- import android.app.Activity
-
- object ActivityCollector {
- private val activities = ArrayList
() -
- fun addActivity(activity: Activity) {
- activities.add(activity)
- }
-
- fun removeActivity(activity: Activity) {
- activities.remove(activity)
- }
-
- fun finishAll() {
- for (activity in activities) {
- if (!activity.isFinishing) {
- activity.finish()
- }
- }
- activities.clear()
- }
- }
然后创建BaseActivity类作为所有Activity的父类:
- open class BaseActivity :AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
- super.onCreate(savedInstanceState, persistentState)
- ActivityCollector.addActivity(this)
- }
-
- override fun onDestroy() {
- super.onDestroy()
- ActivityCollector.removeActivity(this)
- }
- }
再创建一个LoginActivity作为登录界面,并编写对应的布局activity_login.xml:
- "1.0" encoding="utf-8"?>
- <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="match_parent"
- 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:id="@+id/accountEdit"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"/>
- LinearLayout>
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="60dp">
- <TextView
- android:layout_width="90dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:textSize="18sp"
- android:text="Password:"/>
-
- <EditText
- android:id="@+id/passwordEdit"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"
- android:inputType="textPassword"/>
- LinearLayout>
-
- <Button
- android:id="@+id/login"
- android:layout_width="200dp"
- android:layout_height="60dp"
- android:layout_gravity="center_vertical"
- android:text="Login"/>
-
- LinearLayout>
这里编写了一个登录界面,也很好理解。然后修改LoginActivity中的代码:
- class LoginActivity : BaseActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_login)
-
- login.setOnClickListener {
- val account = accountEdit.text.toString()
- val password = passwordEdit.text.toString()
-
- if (account == "admin" && password == "123456") {
- val intent = Intent(this, MainActivity::class.java)
- startActivity(intent)
- finish()
- } else {
- Toast.makeText(this, "account and password is invalid", Toast.LENGTH_SHORT).show()
- }
- }
- }
- }
这里的代码也很好理解,就是获取布局中的账号和密码,然后匹配一致就启动MainActivity,否则就提示错误。
这里在MainActivity中加入强制下线功能,修改activity_main.xml:
- "1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android" - android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
-
- android:id="@+id/forceOffline"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Send force offline broadcast" />
-
上面代码也很好理解,只是加入了一个按钮。
修改MainActivity,添加对应的点击事件:
- class MainActivity : BaseActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- forceOffline.setOnClickListener {
- val intent = Intent("com.example.broadcastbestpractice.FORCE_OFFLINE")
- sendBroadcast(intent)
- }
- }
- }
同样很好理解,在点击事件触发后发了一条广播。而对应接收广播的逻辑并不需要添加到MainActivity中,因为不管什么时候接收了该广播,都要做强制下线处理。
这里创建BroadcastReceiver来接收该广播,而应该在哪里创建呢?首先BroadcastReceiver接收到广播后是需要弹出对话框来阻塞用户的操作的,但如果是静态注册,是没有办法在onReceiver方法中弹出对话框这种UI控件的。
因此这里的BroadcastReceiver需要放在BaseActivity中进行动态注册,因为所有的Activity都继承自BaseActivity。修改BaseActivity:
- open class BaseActivity :AppCompatActivity() {
-
- lateinit var receiver: ForceOfflineReceiver
-
- override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
- super.onCreate(savedInstanceState, persistentState)
- ActivityCollector.addActivity(this)
- }
-
- override fun onResume() {
- super.onResume()
- val intentFilter = IntentFilter()
- intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE")
- receiver = ForceOfflineReceiver()
- registerReceiver(receiver, intentFilter)
- }
-
- override fun onPause() {
- super.onPause()
- unregisterReceiver(receiver)
- }
-
- override fun onDestroy() {
- super.onDestroy()
- ActivityCollector.removeActivity(this)
- }
-
- inner class ForceOfflineReceiver:BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent?) {
- AlertDialog.Builder(context).apply {
- setTitle("Warning")
- setMessage("You are forced to be offline, please try to login again.")
- setCancelable(false)
- setPositiveButton("OK") { _, _ ->
- ActivityCollector.finishAll()
- val intent = Intent(context, LoginActivity::class.java)
- context.startActivity(intent)
- }
- show()
- }
- }
- }
- }
首先是ForceOfflineReceiver中的onReceiver方法中使用AlertDialog.Builder构建了一个对话框,然后使用setCancelable使之不可取消。然后使用setPositiveButton方法注册了确定按钮,当用户点击OK按钮时,就调用ActivityCollector的finishAll方法销毁所有Activity,并重新启动LoginActivity。
同时在onResume和onPause方法中分别注册和去注册了ForceOfflineReceiver,这是因为需要始终保证只有处于栈顶的Activity才能接收强制下线广播,非栈顶的Activity没有必要接收该广播。
最后修改AndroidManifest.xml:
- "1.0" encoding="utf-8"?>
- <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"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- intent-filter>
- activity>
-
- <activity
- android:name=".MainActivity"
- android:exported="true">
-
- activity>
- application>
-
- manifest>
这里只是将主Activity设置为了LoginActivity,而不是MainActivity。程序运行后的结果为:


发送广播后被接收后,就会回到登录界面,也就实现了强制下线。