在一些社交帐号中,强制下线是一个比较常见的功能,比如异地登陆。而实现强制下线功能的思路其实只是在界面上弹出一个对话框,让用户无法进行任何操作,比如点击对话框回到登录界面。这种功能就可以借助广播功能来实现。
强制下线功能需要先关闭所有的Activity,然后回到登录界面。在此之前,先创建一个ActivityCollector类用于管理所有的Activity:
package com.example.broadcastbestpractice
import android.app.Activity
object ActivityCollector {
private val activities = ArrayList<Activity>()
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:
<?xml version="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:
<?xml version="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">
<Button
android:id="@+id/forceOffline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send force offline broadcast" />
</LinearLayout>
上面代码也很好理解,只是加入了一个按钮。
修改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:
<?xml version="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。程序运行后的结果为:
发送广播后被接收后,就会回到登录界面,也就实现了强制下线。