当前位置: 首页 > news >正文

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。程序运行后的结果为:

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

相关文章:

  • opencv 深度学习
  • Windows取证——基本网络命令
  • CDH 09Cloudera Manager Kerberos安装配置(markdown新版二)
  • 小米面试——案例总结
  • 电源硬件设计----升降压变换器(负压输出)基础
  • Nodejs系列之模块加载机制
  • MyBatis 查询数据库入门
  • LQ0026 修剪灌木【数学】
  • 重识Nginx - 02 手把手教你编译适合自己的nginx 1.22.0
  • Java泛型详解
  • opencv连通域标记 connectedComponentsWithStats()函数
  • 【C#在资源管理器中显示自定义文件格式的缩略图】
  • 【NLP】第2章 开始使用 Transformer 模型的架构
  • 电容的分类
  • MYBatis-Plus常用注解@TableName、@TableId、@TableField、@TableLogic
  • Angular数据绑定机制
  • CAP理论的例子讲解
  • javascript数组去重/查找/插入/删除
  • Java基本数据类型之Number
  • nodejs:开发并发布一个nodejs包
  • 翻译--Thinking in React
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 区块链共识机制优缺点对比都是什么
  • 全栈开发——Linux
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 小而合理的前端理论:rscss和rsjs
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • #1015 : KMP算法
  • #android不同版本废弃api,新api。
  • #mysql 8.0 踩坑日记
  • #NOIP 2014#Day.2 T3 解方程
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (12)Linux 常见的三种进程状态
  • (175)FPGA门控时钟技术
  • (26)4.7 字符函数和字符串函数
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (C#)一个最简单的链表类
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (多级缓存)多级缓存
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (汇总)os模块以及shutil模块对文件的操作
  • (数据结构)顺序表的定义
  • (转)ORM
  • (转)一些感悟
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .net Stream篇(六)
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .NET下ASPX编程的几个小问题
  • .Net中ListT 泛型转成DataTable、DataSet