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

Jetpack Compose开发一个Android WiFi导航应用

在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中,我介绍了如何用Android来测量WiFi信号,上传到服务器进行分析后,生成室内不同地方的WiFi指纹,从而帮助进行室内导航。当时我是用的HTML5+的技术来快速开发一个Android的应用,可以看到HTML5+能很便利的用我们熟悉的Web技术来进行开发,而不需要了解原生Android应用繁琐的开发知识。但是Android原生应用也有其优势,尤其在性能上以及一些Android核心功能的调用上。尤其是Google推出了新的Jetpack Compose用于构建原生 Android 界面的新工具包,它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助简化并加快 Android 界面开发,打造生动而精彩的应用,让我们能更快速、更轻松地构建 Android 界面以及更加便利进行原生应用的开发。因此这次我也用Jetpack Compose来重构了我之前写的Wifi信号测量的应用。

WiFi测量主界面

界面UI设计

Jepack Compose的精髓在于用可组合函数来声明一个UI界面。界面是不可变的,在绘制后无法进行更新。您可以控制的是界面的状态。每当界面的状态发生变化时,Compose 都会重新创建界面数更新的部分。

在Android studio里面新建一个项目,选择Empty activity类型,在这种类型的项目,res资源文件夹没有layout这个子文件夹,因为这种类型已经是用新的Compose方式来进行布局了,不再采用以前的XML方式来定义布局。

新建一个名为WifiMeasure的class,在里面定义一个MeasureScreen的Composable函数,用来声明我们的主界面,代码如下:

@Composable
fun MeasureScreen() {Column(modifier = Modifier.padding(all = 8.dp)) {Text(text = stringResource(R.string.screen_title),style = MaterialTheme.typography.titleLarge,)Spacer(modifier = Modifier.height(8.dp))val imageModifier = Modifier.height(150.dp).fillMaxWidth().border(BorderStroke(1.dp, Color.Black))Image(painter = painterResource(id = R.drawable.indooratlas),contentDescription = null,contentScale = ContentScale.FillWidth,modifier = imageModifier)Spacer(modifier = Modifier.height(8.dp))OutlinedTextField(value = "",onValueChange = { },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))TextField(value = "0.0",onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(16.dp))TextButton(onClick = { },shape = RectangleShape,contentPadding = PaddingValues(16.dp),modifier = Modifier.fillMaxWidth().border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)) {Texttext = "Measure",style = MaterialTheme.typography.bodyMedium)}}
}

这里采用了Column来作为一个垂直布局,在里面放置了Text, Image等组件来显示界面。我们可以添加一个Preview的函数,这样在进行代码改动的时候,我们就可以马上在Android studio的Design里面看到UI的改动了,非常方便,代码如下:

@Preview(showBackground = true)
@Composable
fun PreviewMeasureScreen() {MeasureScreen()
}

这个界面的效果如下图:

在要进行测量的时候,我们需要首先输入当前位置的名字,同时手机会实时显示当前的朝向,因为不同的朝向对Wifi信号的测量也有影响。然后当我们点击Measure这个按钮的时候,就会把当前这个位置的Wifi信号信息测量出来。

在MainActivity的onCreate方法的setContent中直接调用刚才我们定义的函数MeasureScreen()即可在APP中显示我们的界面。

定义ViewModel保存UI状态

现在在输入框中输入位置的名字,可以看到输入无法显示,这是因为在OutlinedTextField里面我们没有定义value是一个可观测状态,因此Compose组件无法进行重组更新。为此我们需要定义一个ViewModel来保存UI的状态。新建一个WifiMeasureViewModel的class,代码如下:

class WifiMeasureViewModel : ViewModel() {var positionName by mutableStateOf("")private setfun updatePositionName(name: String) {positionName = name}

这个类里面定义了一个positionName的mutableStateOf的State容器,通过一个update方法来更新数值。

修改WifiMeasure这个函数,传入这个ViewModel进行绑定,这里的viewModel()是一个生命周期的组件,可以使得ViewModel与Compose UI生命周期同步存在。

@Composable
fun MeasureScreen(measureViewModel: WifiMeasureViewModel = viewModel()
) 

修改OutlinedTextField,现在可以正常输入文字了,如果我们旋转手机,可以看到之前输入的文字能保留下来。

        OutlinedTextField(value = measureViewModel.positionName,onValueChange = { measureViewModel.updatePositionName(it) },label = { Text(text = stringResource(R.string.label_position_name), style = MaterialTheme.typography.bodyMedium)},modifier = Modifier.fillMaxWidth())

获取手机朝向

因为手机的朝向对于Wifi测量会有影响,因此通常我们会在同一个地点测试不同朝向的WiFi信号并记录下来。我们需要在APP上实时显示当前的朝向,这就需要用到手机提供的传感器数据。

传统的Android应用的方法是,在Activity类里面继承SensorEventListener并重写相应的方法来实现。但是在Composable function里面如何实现,在官网上并没有介绍。我的做法是先定义一个新的类继承SensorEventListener,例如我们新建一个SensorDataManager的类,代码如下:

class SensorDataManager(context: Context): SensorEventListener {private val sensorManager by lazy {context.getSystemService(Context.SENSOR_SERVICE) as SensorManager}private var accelerometerReading = FloatArray(3)private var magnetometerReading = FloatArray(3)private var rotationMatrix = FloatArray(9)private var orientationAngles = FloatArray(3)val data: Channel<Float> = Channel(Channel.UNLIMITED)fun init() {Log.d("SensorDataManager", "init")val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI)}override fun onSensorChanged(event: SensorEvent) {if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)} else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)}SensorManager.getRotationMatrix(rotationMatrix,null,accelerometerReading,magnetometerReading)SensorManager.getOrientation(rotationMatrix, orientationAngles)data.trySend(orientationAngles[0])}override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}fun cancel() {Log.d("SensorDataManager", "cancel")sensorManager.unregisterListener(this)}
}

解释一下代码,在init函数中获取accelerometer和maganetic这两个传感器并注册listener,根据官网的介绍,推荐用这两个传感器数据来获取准确的朝向。在重写的onSensorChanged方法中,根据这两个传感器的数据计算朝向,并通过Channel把协程的数据发送出去。最后在cancel中取消listener注册。

现在修改一下MeasureScreen这个函数,增加以下代码:

    val context = LocalContext.currentval scope = rememberCoroutineScope()var angle by remember { mutableStateOf<Float>(0f) }DisposableEffect(Unit) {val dataManager = SensorDataManager(context)dataManager.init()val job = scope.launch {dataManager.data.receiveAsFlow().onEach { angle = it }.collect()}onDispose {dataManager.cancel()job.cancel()}}

这里用到了Compose里面的附带效应Side effects,按照官网的解释附带效应是指发生在可组合函数作用域之外的应用状态的变化。DisposableEffect可以在键发生变化或可组合项退出组合后进行清理。因此我采用DiposableEffect(Unit)来监控这个MeasureScreen函数,完成初始化和清除Sensor Listener的工作。

修改一下显示朝向角度的TextField,设置其Value

        TextField(value = angle.toString(),onValueChange = { },label = { Text(text = stringResource(R.string.label_current_angle), style = MaterialTheme.typography.bodyMedium)},readOnly = true,modifier = Modifier.fillMaxWidth())

现在这个测量页面可以正常工作了。

WiFi测量报告页面

增加导航

当点击测量页面的Measure按钮的时候,应该能跳转到另一个页面,显示WiFi的测量结果。要实现导航的功能,我们需要用到Navigation组件。新增一个名为Navigation的class,代码如下:

object Destinations {const val MEASURE_ROUTE = "measure"const val REPORT_ROUTE = "report/{positionName}"
}@Composable
fun AppNavHost(modifier: Modifier = Modifier,navController: NavHostController = rememberNavController(),startDestination: String = MEASURE_ROUTE
) {NavHost(navController = navController,startDestination = startDestination) {composable(MEASURE_ROUTE) {MeasureScreen(navController = navController)}composable(REPORT_ROUTE,arguments = listOf(navArgument("positionName") {type = NavType.StringType},)) { backStackEntry ->val positionName = backStackEntry.arguments?.getString("positionName")WifiMeasureReport(positionName)}}
}

这里定义了两个route,分别对应APP的两个页面。在跳转到测量报告页面的时候,route会带上positionName这个参数。

修改MainActivity,把setContent的内容替换为调用AppNavHost(),如以下代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {WifiPositionTheme {// A surface container using the 'background' color from the themeSurface(modifier = Modifier.fillMaxSize(),color = MaterialTheme.colorScheme.background) {AppNavHost()}}}}
}

WiFi扫描服务

增加一个Wifi扫描的服务,实现对Wifi信号的测量。新增一个WifiScanService的class,代码如下:

class WifiScanService(context: Context) {private val wifiManager by lazy {context.getSystemService(Context.WIFI_SERVICE) as WifiManager}private val context: Context = contextval data: Channel<List<WifiMeasureData>> = Channel(Channel.UNLIMITED)private val wifiScanReceiver = object : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)if (success) {scanSuccess()} else {scanFailure()}}}fun init() {val intentFilter = IntentFilter()intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)context.registerReceiver(wifiScanReceiver, intentFilter)val success = wifiManager.startScan()if (!success) {scanFailure()}}private fun scanFailure() {Log.d("WIFI", "Scan failure")}@SuppressLint("MissingPermission")private fun scanSuccess() {val results = wifiManager.scanResultsif (!results.isNullOrEmpty()) {val wifiMeasureData = results.map {WifiMeasureData(it.BSSID,it.level)}data.trySend(wifiMeasureData)}}fun cancel() {context.unregisterReceiver(wifiScanReceiver)}
}

定义一个WifiMeasureData的数据class,保存测量数据

data class WifiMeasureData (val bssId: String,val signalStrength: Int
)

另外,要开启WiFi测量,还需要申请相应的权限,在AndroidManifest.xml里面,增加以下权限申请

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

报告页面设计

同样是采用Compose的方式来设计一个页面展示Wifi测量数据的结果,新建一个WifiMeasureReport的class,代码如下:

@Composable
fun WifiMeasureReport (positionName: String?) {val context = LocalContext.currentval scope = rememberCoroutineScope()var wifiScanResult by remember { mutableStateOf<List<WifiMeasureData>>(listOf(WifiMeasureData("", 0))) }DisposableEffect(Unit) {val wifiScanService = WifiScanService(context)wifiScanService.init()val job = scope.launch {wifiScanService.data.receiveAsFlow().onEach { wifiScanResult = it }.collect()}onDispose {wifiScanService.cancel()job.cancel()}}Column() {Text(text = stringResource(id = R.string.report_title),style = MaterialTheme.typography.titleLarge,textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth())Spacer(modifier = Modifier.height(8.dp))Text(text = stringResource(id = R.string.report_position_name)+": "+positionName,style = MaterialTheme.typography.bodyLarge,modifier = Modifier.padding(10.dp))LazyColumn(Modifier.fillMaxWidth(),contentPadding = PaddingValues(horizontal = 4.dp)){item {ItemHeader()}itemsIndexed(wifiScanResult) { index: Int, item: WifiMeasureData ->ItemRow(index, item)}}}
}@Composable
fun ItemHeader() {Row(Modifier.fillMaxWidth()//.border(BorderStroke(0.5.dp, Color.Black))) {Text(text = stringResource(R.string.report_header_bssid), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))Text(text = stringResource(R.string.report_header_strength), fontWeight = FontWeight.Bold, modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Composable
fun ItemRow(index: Int, item: WifiMeasureData) {val modifier: Modifier = Modifier.fillMaxWidth()Row(modifier = if (index%2 == 0) modifier.background(Color.LightGray) else modifier) {Text(text = item.bssId, modifier = Modifier.weight(5f).padding(10.dp))Text(text = item.signalStrength.toString(), modifier = Modifier.weight(5f).padding(10.dp))}Divider(color = Color.LightGray,modifier = Modifier.height(1.dp).fillMaxHeight().fillMaxWidth())
}@Preview(showBackground = true)
@Composable
fun PreviewMeasureReportScreen() {WifiMeasureReport("grid_1")
}

这里同样采用了Column垂直布局,其中用了一个LazyColumn来展示Wifi测量结果的列表。这个LazyColumn类似于以前的RecyclerView。

测试结果上报

WiFi测试的结果要上报到服务器来进行汇总分析,最后生成Wifi指纹,这部分的内容可以参考我之前提到的博客内容,这里不再重复。我们只需要修改一下WifiMeasureReport,把拿到的结果通过REST API上传即可。改动如下:

待补充。。。

运行效果

最后把项目打包为APK后上传到手机运行,实际效果如下:

待补充。。。

相关文章:

Jetpack Compose开发一个Android WiFi导航应用

在以前的一篇文章构建一个WIFI室内定位系统_wifi定位系统-CSDN博客中&#xff0c;我介绍了如何用Android来测量WiFi信号&#xff0c;上传到服务器进行分析后&#xff0c;生成室内不同地方的WiFi指纹&#xff0c;从而帮助进行室内导航。当时我是用的HTML5的技术来快速开发一个An…...

【Mode Management】ComM详细介绍

目录 1. Introduction and functional overview 2.Dependencies to other modules 3.Functional specification 3.1 Partial Network Cluster Management 3.2 ComM channel state machine 3.2.1 Behaviour in state COMM_NO_COMMUNICATION 3.2.1.1 COMM_NO_COM_NO_PENDI…...

【C++多线程编程】(二)之详解锁(lock)和解锁(unlock)

在C多线程编程中&#xff0c;锁&#xff08;lock&#xff09;和解锁&#xff08;unlock&#xff09;通常用于管理共享资源的访问&#xff0c;以防止多个线程同时对资源进行修改&#xff0c;从而避免竞态条件&#xff08;Race Condition&#xff09;和数据不一致性问题。C标准库…...

【Mypy】超级实用的python高级库!

今天&#xff0c;我很兴奋地向大家介绍一个神奇的Python库&#xff1a;Mypy。这个库是Python世界中的一颗璀璨明星&#xff0c;提供了静态类型检查的强大功能&#xff0c;极大地增强了Python这门动态类型语言的健壮性和可维护性。我们将深入探索Mypy的多个方面&#xff0c;并通…...

【Python基础】循环语句

文章目录 [toc]什么是循环Python中的循环方式while循环格式示例 什么是循环 程序中需要重复执行的代码&#xff0c;可以通过循环实现比如和女朋友道歉&#xff0c;或一万遍“宝宝&#xff0c;我错了”&#xff0c;在没有学习循环之前&#xff0c;我们只能通过如下方式实现 pr…...

【面试】广告优化

a1&#xff1a;点击率公式是什么&#xff1f;点击率低的原因是什么&#xff1f; 点击率点击/曝光&#xff0c;点击率低的原因主要有两点&#xff1a;一是创意不吸引人&#xff1b;二是目标受众不准确/定向过宽不精确&#xff0c;广告曝光给了对产品不感兴趣用户 a2&#xff1a;…...

RabbitMQ插件详解:rabbitmq_message_timestamp【Rabbitmq 五】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 RabbitMQ时空之旅&#xff1a;rabbitmq_message_timestamp的奇妙世界 前言什么是rabbitmq_message_timestamprabbitmq_message_timestamp 的定义与作用&#xff1a;如何在 RabbitMQ 中启用消息时间戳&…...

AD9361 Evaluation Software配置脚本转换工具

最近在玩一个开源的AD9361项目&#xff0c;AD9361采用纯逻辑配置&#xff0c;不需要ARM或者MicroBlaze。其中&#xff0c;先是用AD9361 Evaluation Software生成配置脚本&#xff0c;再转换成ad9361_lut.v。 在网上查了一圈&#xff0c;有个转换工具叫bit_converter&#xff0…...

Centos7 配置Git

随笔记录 目录 1&#xff0c; 新建用户 2. 给用户设置密码相关操作 3. 为新用户添加sudo 权限 4. 配置Git 4.1 配置Git 4.2 查看id_ras.pub 5, 登录Git 配置SSH 秘钥 6. Centos7 登录Git 7. clone 指定branch到本地 8. 将新代码复制到指定路径 9. 上传指定代码 …...

python工具方法 44 数据仿真生成(粘贴目标切片到背景图像上,数据标签校验)

在深度学习训练中数据是一个很重要的因素,在数据不够时需要我们基于现有的数据进行增强生成新的数据。此外,在某特殊情况,如对某些目标切片数据(例如:石块分割切片)预测效果较差,需要增强其在训练数据中的频率。故此,我们可以将先有数据标注中的目标裁剪出来,作为样本…...

Llama 架构分析

从代码角度进行Llama 架构分析 Llama 架构分析前言Llama 架构分析分词网络主干DecoderLayerAttentionMLP 下游任务因果推理文本分类 Llama 架构分析 前言 Meta 开发并公开发布了 Llama系列大型语言模型 (LLM)&#xff0c;这是一组经过预训练和微调的生成文本模型&#xff0c;参…...

vue3前端 md5工具类

工具类 /*** Namespace for hashing and other cryptographic functions* Copyright (c) Andrew Valums* Licensed under the MIT license, http://valums.com/mit-license/*/var V V || {}; V.Security V.Security || {};(function () {// for faster accessvar S V.Secur…...

Unity触摸 射线穿透UI解决

unity API 之EventSystem.current.IsPointerOverGameObject() 命名空间 &#xff1a;UnityEngine.EventSystems 官方描述&#xff1a; public bool IsPointerOverGameObject(); public bool IsPointerOverGameObject(int pointerId); //触摸屏时需要的参数&#xff…...

基于QTreeWidget实现带Checkbox的多级组织结构选择树

基于QTreeWidget实现带Checkbox的多级组织结构选择树 采用基于QWidgetMingw实现的原生的组织结构树 通过QTreeWidget控件实现的带Checkbox多级组织结构树。 Qt相关系列文章&#xff1a; 一、Qt实现的聊天画面消息气泡 二、基于QTreeWidget实现多级组织结构 三、基于QTreeWidget…...

探索 Vim:一个强大的文本编辑器

引言&#xff1a; Vim&#xff08;Vi IMproved&#xff09;是一款备受推崇的文本编辑器&#xff0c;拥有强大的功能和高度可定制性&#xff0c;提供丰富的编辑和编程体验。本文将探讨 Vim 的基本概念、使用技巧以及为用户带来的独特优势。 简介和发展 1. Vim 的简介和历史 V…...

K8S(十)—容器探针

这里写目录标题 容器探针&#xff08;probe&#xff09;检查机制探测结果探测类型何时该使用存活态探针?何时该使用就绪态探针?何时该使用启动探针&#xff1f; 使用exechttptcpgrpc使用命名端口 使用启动探针保护慢启动容器定义就绪探针配置探针HTTP 探测TCP 探测探针层面的…...

[C错题本]

1.int,short,long都是signed的 但是char可能是signed 也可能是unsigned的——《C Primer》 2.在16位的PC中 char类型占1个字节 int占2个字节 long int占4个字节 float占四个字节 double占八个字节 3.自增运算符和自减运算符即使是在判断条件中使用也会实际生效 int i 1; int…...

tomcat启动异常:子容器启动失败(a child container failed during start)

最近在使用eclipse启动Tomcat时&#xff0c;发现一个问题&#xff0c;启动以前的项目突然报子容器启动异常。 异常信息如下&#xff1a; 严重: 子容器启动失败 java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: 无法启动组件[org.apache.…...

JAVA序列化(创建可复用的 Java 对象)

JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象&#xff0c;但一般情况下&#xff0c;只有当 JVM 处于运行时&#xff0c;这些对象才可能存在&#xff0c;即&#xff0c;这些对象的生命周期不…...

如何使用自动化工具编写测试用例?

以下为作者观点&#xff0c;仅供参考&#xff1a; 在快速变化的软件开发领域&#xff0c;保证应用程序的可靠性和质量至关重要。随着应用程序复杂性和规模的不断增加&#xff0c;仅手动测试无法满足行业需求。 这就是测试自动化发挥作用的地方&#xff0c;它使软件测试人员能…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...