Android开发基础——广播实践

在一些社交帐号中,强制下线是一个比较常见的功能,比如异地登陆。而实现强制下线功能的思路其实只是在界面上弹出一个对话框,让用户无法进行任何操作,比如点击对话框回到登录界面。这种功能就可以借助广播功能来实现。

强制下线功能需要先关闭所有的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。程序运行后的结果为:

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

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/127040820