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

【Android入门】5、Broadcast 广播、Kotlin 的高阶函数、泛型、委托

六、BroadCast 广播

广播用于在Android系统内实现通知,概念较为简单

请添加图片描述

为了实现上述效果, 代码如下

  • 基础类如下, 定义了receiver, 当收到消息时, 触发receiver逻辑(弹窗, 关闭所有activities, 跳转到loginActivity)

其中注册和反注册BroadcastReceiver, 是写在onResumeonPause内, 而不是onCreateonDestroy, 是因为我们希望只有栈顶的activity才可收到强制下线的广播, 其他非栈顶的activity不应该且没必要收此广播, 即当一个activity失去栈顶位置时自动取消BraodcastReceiver的注册

package com.example.broadcastbestpractice

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog

open class BaseActivity : AppCompatActivity() {
    lateinit var receiver: ForceOfflineReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        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() // 销毁所有Activity
                    val i = Intent(context, LoginActivity::class.java)
                    context.startActivity(i) // 重新启动LoginActivity
                }
                show()
            }
        }
    }
}
  • MainActivity类, 继承BaseActivity类, 当点击button时发出broadcast消息, 触发后续逻辑
package com.example.broadcastbestpractice

import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

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

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)
        }
    }
}
  • loginActivity, 当用户名密码输入正确是跳转到MainActivity, 否则弹出Toast
package com.example.broadcastbestpractice

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_login.*

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()
            // 如果账号是admin且密码是123456,就认为登录成功
            if (account == "admin" && password == "123456") {
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
                finish()
            } else {
                Toast.makeText(
                    this, "account or password is invalid",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }
}
  • 通过在AndroidManifest.xml中指定主activity, 可以从登录页开始
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.broadcastbestpractice">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BroadcastBestPractice"
        tools:targetApi="31">
        <activity
            android:name=".BaseActivity"
            android:exported="false" />
        <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" />
    </application>

</manifest>

七、Kotlin 语法

7.1 高阶函数

7.1.1 简化 SharedPreferences 的用法

通常我们若需向SharedPreferences存储数据, 需3个步骤

  • 调SharedPreferences.edit()方法获取SharedPreferences.Editor对象
  • 向SharedPreferences.Editor对象, 添加数据
  • 调apply()方法提交数据, 完成数据存储
    代码如下
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "Tom")
editor.putInt("age", 28)
editor.putBoolean("married", false)
editor.apply()

然而可通过高阶函数简化, 具体方式如下
通过新建一个SharedPreferences.kt文件, 加入如下代码, 其实是为SharedPreferences类添加了open函数, 其中open函数是高阶函数, 其参数为block函数
其中block是我们添加数据的业务逻辑,这个open函数做了一些模板工作, 它会帮我们拿到editor对象, 调我们的block()业务逻辑, 最终通过apply提交数据

fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
    val editor = edit()
    editor.block()
    editor.apply()
}

定义之后, 我们可按如下使用, 其中向open函数内传的block()函数就是如下3行业务逻辑

getSharedPreferences("data", Context.MODE_PRIVATE).open {
    putString("name", "Tom")
    putInt("age", 28)
    putBoolean("married", false)
}

其实AndroidStudioIDE在初始化项目时即自动引入了ktx库, 此库就会为我们做这些工作, 只不过将open函数名换成了edit函数名, 调用方式如下

getSharedPreferences("data", Context.MODE_PRIVATE).edit {
    putString("name", "Tom")
    putInt("age", 28)
    putBoolean("married", false)
}

7.1.2 简化 ContentValues 的用法

通常的用法是如下

val values = ContentValues()
values.put("name", "Game of Thrones")
values.put("author", "George Martin")
values.put("pages", 720)
values.put("price", 20.85)
db.insert("Book", null, values)

为了简化, 我们可定义一个ContentValues.kt文件, 在其中定义cvOf()方法

fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
    val cv = ContentValues()
    for (pair in pairs) {
        val key = pair.first
        val value = pair.second
        when (value) {
            is Int -> cv.put(key, value)
            is Long -> cv.put(key, value)
            is Short -> cv.put(key, value)
            is Float -> cv.put(key, value)
            is Double -> cv.put(key, value)
            is Boolean -> cv.put(key, value)
            is String -> cv.put(key, value)
            is Byte -> cv.put(key, value)
            is ByteArray -> cv.put(key, value)
            null -> cv.putNull(key)
        }
    }
    return cv
}

调用的时候, 就很简化了, 通过cvOf一次性就可以输入一堆数据

val values = cvOf(
    "name" to "Game of Thrones", "author" to "George Martin",
    "pages" to 720, "price" to 20.85
)
db.insert("Book", null, values)

其实AndroidStudioIDE自动引入的ktx库, 也有contentValuesOf方法, 我们可以直接使用, 它的调用方法如下

val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
 "pages" to 720, "price" to 20.85)
db.insert("Book", null, values)

7.2 泛型

通过<T>声明一个泛型类型, 使同时支持多种数据类型, 以复用代码

  • 泛型类
class MyClass<T> {
    fun method (param: T): T {
        return param
    }
}

fun main() {
    val myClass = MyClass<Int>()
    val result = myClass.method(123)
}
  • 泛型方法
class MyClass {
    fun <T> method (param: T): T {
        return param
    }
}
fun main() {
    val myClass = MyClass()
    val result = myClass.method(123)
}
  • 为泛型增加高阶函数
// 为泛型T 增加高阶函数
// 即手动实现了kotlin中的apply()函数
fun <T> T.build(block: T.()->Unit):T {
    block()
    return this
}

// 调用高阶函数, query()拿到数据后, 调用build()函数处理数据
contentResolver.query(uri, null, null, null, null)?.build {
	while (moveToNext()) {
	    ...
	}
	close()
}

7.3 委托

委托是一种设计模式, 其理念为: 操作对象自己不会处理某逻辑, 而是委托给某辅助对象去处理. C#语言也有此语法概念.

7.3.1 类委托

一个类的具体实现, 委托给另一个类去完成
例如Set是一个接口, 使用时需用其具体的实现类(如HashSet), 而借住委托, 我们可轻松定制自己的实现类

// 定制MySet类, 实现Set接口
// 在MySet类, 接受了HashSet辅助对象的参数, MySet的实现方法其实都是通过HashSet辅助对象完成的, 这就是委托
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
	override val size: Int
	get() = helperSet.size
	override fun contains(element: T) = helperSet.contains(element)
	override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
	override fun isEmpty() = helperSet.isEmpty()
	override fun iterator() = helperSet.iterator()
}

// kotlin还提供了语法糖, 下文是上段代码的简写形式, 即通过by关键词
// 重写了isEmpty方法, 增加了helloWorld方法
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
	fun helloWorld() = println("Hello World")
	override fun isEmpty() = false
}

7.3.2 属性委托

把一个属性(字段)的具体实现, 委托给另一个类去完成

class MyClass {
	// 当调p属性时, 会调Delegate类的getValue()方法
	// 当给p属性赋值时, 会调Delegate类的setValue()方法
	var p by Delegate()
}

class Delegate {
	var propValue: Any? = null
		operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
		return propValue
	}
	operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
		propValue = value
	}
}

getValue()方法要接收两个参数:

  • 第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里写成MyClass表示仅可在MyClass类中使用;
  • 第二个参数KProperty<*>是 Kotlin中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在方法参数上进行声明。
  • 另外,<*>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java中<?>的写法。
  • 至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行了,上述代码只是一种示例写法。

还存在一种情况可以不用在Delegate类中实现setValue()方法,那就是MyClass中的p属性是使用val关键字声明的。

  • 这一点也很好理解,如果p属性是使用val关键字声明的,那么就意味着p属性是无法在初始化之后被重新赋值的
  • 因此也就没有必要实现setValue()方法,只需要实现getValue()方法就可以了

7.3.3 实现一个自己的 lazy 函数

我们希望实现和kotlin内置的by lazy语法类似的功能, 实现懒加载, 当第一次调用时才初始化变量
我们定义了Later类, 并封装了later顶层函数

package com.example.atest

import kotlin.reflect.KProperty

fun <T> later(block: () -> T) = Later(block)

class Later<T> (val block:() -> T) {
    var value: Any? = null
    operator fun getValue(any: Any?, prop: KProperty<*>):T {
        if (value == null) {
            value = block()
        }
        return value as T
    }
}

调用方式如下

val uriMatcher by later {
	val matcher = UriMatcher(UriMatcher.NO_MATCH)
	matcher.addURI(authority, "book", bookDir)
	matcher.addURI(authority, "book/#", bookItem)
	matcher.addURI(authority, "category", categoryDir)
	matcher.addURI(authority, "category/#", categoryItem)
	matcher
}

相关文章:

  • clickhouse
  • 【周赛复盘】力扣第 312 场单周赛
  • QT通过QSS文件样式表设置改变窗体与按钮背景外观
  • kotlin基础知识
  • Keras学习记录之模型
  • LeetCode 0329. 矩阵中的最长递增路径
  • JavaEE:线程安全问题的原因和解决方案
  • Linux/CentOS 安装 flutter 与 jenkins 构建 (踩坑)
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • 随想录一期 day4 [24. 两两交换链表中的节点|19. 删除链表的倒数第 N 个结点|面试题 02.07. 链表相交|142. 环形链表 II]
  • iOS动画相关
  • LeetCode往完全二叉树添加节点
  • Linux、docker、kubernetes、MySql、Shell运维快餐
  • 基数(桶)排序算法详解之C语言版
  • 生成模型的中Attention Mask说明
  • IndexedDB
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • Java Agent 学习笔记
  • Nodejs和JavaWeb协助开发
  • Promise初体验
  • Python_OOP
  • React+TypeScript入门
  • 订阅Forge Viewer所有的事件
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 使用parted解决大于2T的磁盘分区
  • 移动端唤起键盘时取消position:fixed定位
  • 再谈express与koa的对比
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 从如何停掉 Promise 链说起
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • ​比特币大跌的 2 个原因
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (1)STL算法之遍历容器
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (8)STL算法之替换
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (三) diretfbrc详解
  • (未解决)macOS matplotlib 中文是方框
  • (转)Windows2003安全设置/维护
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • (转载)从 Java 代码到 Java 堆
  • .apk文件,IIS不支持下载解决
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET MVC之AOP
  • .NET 分布式技术比较
  • .net 怎么循环得到数组里的值_关于js数组
  • .NET 中各种混淆(Obfuscation)的含义、原理、实际效果和不同级别的差异(使用 SmartAssembly)
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • @WebService和@WebMethod注解的用法