从 0 到 1 搞一个 Compose Desktop 版本的玩天气之踩坑

news2024/11/27 10:23:31

从 0 到 1 搞一个 Compose Desktop 版本的玩天气之踩坑

大家好,好久不见,接下来一段时间我会系统性地写一套关于 Compose Desktop 的文章,带大家从头到尾写一个桌面版的天气应用,并且打好包让别人也可以进行使用,接下来就开始吧!先来看下最终的实现效果吧!

视频

效果是不是挺好?哈哈哈!

其实作为一个安卓开发来说,当运行起第一个桌面版程序的时候内心突然感觉回到了最开始学习编程的时候,那种感觉就好像一个多年未见的老友对你说:久违了!特别是使用的技术还都是安卓开发的技术,只是有一些平台原因需要稍做修改的地方就能开发出一个完整的桌面版软件,内心还是非常激动,非常地有成就感,这种感觉太舒服了!

缘起 Compose

为什么会搞 Compose Desktop,这还得从 Jetpack Compose 说起:Google 从 2017 年开始立项开搞 Compose 到第一个正式版本用了四年的时间,那么久的时间,投入了那么多的人力,以及后面投入了大量经费宣传,无一不在告诉安卓开发者 Compose 很重要,这也是之后安卓开发的新方向!所以当第一个 alpha 版本的 Compose 出现的时候我就坐不住了,立马加上依赖尝试了下!刚开始写的时候感觉有点奇怪,毕竟从之前的开发模式变为了全新的声明式开发,但写了不到一周就感受到了 Compose 的优势,编写起来太快了,动画实现起来也太简单了,声明式编程也太方便了。。。。

其实 Compose Desktop 出现的也很早,Jetpack Compose 出来没多久它也就出来了,有很多同行在 Compose Desktop 出来第一个 alpha 版本的时候就开始研究,不过由于我是做安卓开发的,日常工作也不会涉及到桌面开发,况且 Compose DesktopJetbrains 开发的,并不 Google 开发的,毕竟是模仿 Jetpack Compose 的,未来两边的 API 都有可能对应不上,所以也就一直没有关注。

但是到后来转折点来了,公众号:《Android 开发者》在 2022 年 11 月 30 日早上发了一篇文章,名为:“Jetpack 更新一览 | 2022 Android 开发者峰会”,这篇文章的前半部分没有什么新鲜感,因为这些库我也一直在用,也经常关注着新版本和新功能,但看到文章最后的时候,里面有一段话是这么写的:

我们一直在尝试使 Jetbrains 的 Kotlin 多平台移动版支持跨平台共享代码。我们针对 Android 和 iOS 应用推出了实验性的 Collection 预览版和 DataStore 库。期待您的反馈!您可以查看相关博文,了解更多内容。

这话说的,你们一直在尝试,Jetpack Compose 正式版都出来快一年半了!Compose Desktop 正式版也都一年多了!这么久怎么连点信都没有。。。不过好在现在有信了!Google 这也算放出了几个信号,也不知道我的理解是否正确😂:

  1. 他们也一直在关注 JetbrainsCompose Desktop
  2. 目前已经有两个 Jetpack 的库支持了 Compose Desktop
  3. 后续也会将更多的 Jetpack 中的库支持 Compose Desktop

对标 Flutter ?

Flutter 现在已经比较成熟了,它最大的优势就是跨平台,Flutter 虽然宣称的是原生的性能,一套代码多端实现,但其实对于跨平台来说一套代码并不能完全实现需求,肯定需要各种适配,只不过看框架适配的好与坏,Compose Desktop 也是如此,但 Flutter 的性能也只是媲美原生,而 Compose 就是原生啊!Compose Desktop 其实并不是和 Flutter 抢饭碗,它只是告诉广大安卓开发:你们并不需要学习安卓之外的东西就能开发各种设备上的应用!这也是 Kotlin 的辉煌,我个人认为这也是 Jetbrains 公司开发 Compose Desktop 的初衷。

基于上面的分析,打开了 JetbrainsCompose Desktop 的官网:https://www.jetbrains.com/zh-cn/lp/compose-desktop/ ,也开始试着玩一玩桌面版的应用!

初探 Compose Desktop

我本来还想着使用 Android Studio 来使用 Compose Desktop 来着,结果打开 Android Studio 新建项目一看并没有找到创建 Compose Desktop 的入口,后来想想也对,Android Studio 嘛!本来就是为了构建 Android 项目的,并不是为了构建别的东西,对吧!(内心独白:可能是我自己没找到)

那就使用 IntelliJ Idea 来看看吧,点击 New -> Project 就会出现下面的页面:

在这里插入图片描述
选择 Kotlin 之后就可以看到右边有 Compose Multiplatform 的选项,里面有三种,第一种就是这段时间要搞的 Compose Desktop ,第二种就是多平台了,里面有 Android ,也有 Compose Desktop ,第三种是 Compose Web 。不得不说太强了!桌面、Web、移动端,Compose 一套搞定!目前 IOS 也支持了,不过这不是咱们要看的重点,还是来看 Compose Desktop 吧!接下来点击 Next ,之后选择配置项之后点击 Finish 后第一个 Compose Desktop 项目就创建好了!

项目结构

接下来看下初始项目的结构吧:
在这里插入图片描述

OK,有一个 Main.kt 文件,还有 build 和 settings 文件。下面咱们一个一个来看,先来看下 settings 文件吧:

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    }
    
}
rootProject.name = "Demo"

嗯,这个很简单,放了依赖的仓库地址,还有项目的名称。

接下来再来看下 build 文件:

plugins {
    kotlin("jvm") version "1.5.31"
    id("org.jetbrains.compose") version "1.0.0"
}

dependencies {
    implementation(compose.desktop.currentOs)
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "11"
}

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "Demo"
            packageVersion = "1.0.0"
        }
    }
}

build 文件中稍微多点,分别是 plugins 、dependencies 、jvmTarget 和 application ,前几个就不过多介绍,因为安卓项目中都有,最后的 application 是这里独有的,其实这块就是对桌面项目的一些属性的配置,可以看到有包名和版本号等信息,这块在这里先不进行过多介绍,因为这块的内容很多,WindowsMacLinux 各个系统的配置都不太相同,在之后的文章中会着重来介绍,这里先跳过。

初始代码

最后来看下 Main.kt 文件:

@Composable
@Preview
fun App() {
    var text by remember { mutableStateOf("Hello, World!") }
    MaterialTheme {
        Button(onClick = {
            text = "Hello, Desktop!"
        }) {
            Text(text)
        }
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

代码并不多,而且很熟悉,但也有不认识的地方。可以看到这里出现了 Java 中熟悉的 Main 方法,然后里面调用了一个 application 方法,在其中有一个可组合项 Window ,在里面调用了 App 可组合项。

Application

可组合项咱们都是非常了解的,这块不太清楚的其实就是 applicationWindow ,因为这两个在之前 Jetpack Compose 中都是没有的,下面咱们就先来看看 application

fun application(
    exitProcessOnExit: Boolean = true,
    content: @Composable ApplicationScope.() -> Unit
) {
    val configureSwingGlobals = System.getProperty("compose.application.configure.swing.globals") == "true"
    if (configureSwingGlobals) {
        configureSwingGlobalsForCompose()
    }
    runBlocking {
        awaitApplication {
            content()
        }
    }
    if (exitProcessOnExit) {
        exitProcess(0)
    }
}

applicationCompose Desktop 应用程序的入口点,这块需要注意的是:不要在这个函数中使用任何动画(例如,withframamanosanimatefloatasstate 等),因为底层的 MonotonicFrameClock 没有与任何显示同步,所以无法尽快地生成帧。

方法一共接收两个参数,来分别看下:

  • exitProcessOnExit:结束进程,默认为 true,在应用程序关闭后调用 exitProcess(0) exitProcess 加速进程退出(立即退出,而不是1-4秒)。如果为 false ,函数的执行将在应用程序退出后被解除阻塞(当最后一个窗口关闭,以及所有 LaunchedEffect 完成时)。
  • content:放可组合项的,不做多介绍。

Window

下面再来看下可组合项 Window

@Composable
fun Window(
    onCloseRequest: () -> Unit,
    state: WindowState = rememberWindowState(),
    visible: Boolean = true,
    title: String = "Untitled",
    icon: Painter? = null,
    undecorated: Boolean = false,
    transparent: Boolean = false,
    resizable: Boolean = true,
    enabled: Boolean = true,
    focusable: Boolean = true,
    alwaysOnTop: Boolean = false,
    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
    onKeyEvent: (KeyEvent) -> Boolean = { false },
    content: @Composable FrameWindowScope.() -> Unit
)

Window 的代码有点多,这块咱们先不关心里面的具体实现,先来看看都有哪些功能。在当前 Compose 中组合成平台窗口。当 Window 进入组合成时,将创建一个新的平台窗口并接收焦点。当 Window 离开合成时,Window 将被释放并关闭。Window 的参数有点多,咱们分别来看下:

  • onCloseRequest:当用户关闭窗口时将被调用的回调函数
  • state:用于控制或观察窗口状态的状态对象
  • visible:是否对用户可见
  • title:窗口的名称
  • icon:窗口的图标(和应用图标不同,完全两码事
  • undecorated:禁用或启用此窗口的装饰
  • transparent:禁用或启用窗口透明度,需要注意:只有在窗口未装饰时才应该设置透明度,否则将引发异常
  • resizable:用户是否可以调整窗口的大小
  • enabled:窗口是否能对输入事件作出反应
  • focusable:窗口是否可以接收焦点
  • alwaysOnTop:窗口是否在另一个窗口的顶部
  • onPreviewKeyEvent:当用户与硬件键盘交互时调用此回调,它为聚焦组件的祖先提供了拦截KeyEvent 的机会
  • onKeyEvent:当用户与硬件键盘交互时调用此回调。在实现此回调时,返回 true 以停止此事件的传播。如果返回 false,KeyEvent 将被发送给这个 onKeyEvent 的父事件。

第一次运行

OK,到现在位置初始项目中的内容大概都过了一遍,接下来运行看下效果吧!

那么问题又来了,怎么运行呢。。。之前咱们运行安卓项目的时候都是点击 Android Studio 上方运行,但现在看下:

在这里插入图片描述

没有了,灰着的!不用担心,不还有 main 函数呢嘛!直接运行 main 函数!

在这里插入图片描述

点击运行按钮看下:

在这里插入图片描述

直接弹出了一个 Java 程序,里面放着一个按钮,刚才咱们看可组合项 Window 的时候不是可以修改名字嘛,下面修改下看看!

fun main() = application {
    Window(onCloseRequest = ::exitApplication, title = "天青色等烟雨") {
        App()
    }
}

改了下名字,第二次运行的时候可以点击 main 函数,也可以点击 Idea 的上方运行按钮了,因为刚才运行的记录已经被保存下来了!

在这里插入图片描述

OK,点击运行查看结果:

没错,和想的一样!

到现在为止已经可以使用咱们之前学习的 Jetpack Compose 知识来愉快的编程了!

显示图片

刚还想的可以好好使用 Compose 来编写桌面程序来着,可我刚想显示一张图片就发现了问题!怎么显示???

普通图片

Jetpack Compose 中显示图片不叫事,直接使用 painterResource 将图片资源传进去就可以了,但在 Compose Desktop 中该怎么办呢?

先来看下 Compose Desktop 中的 Image

@Composable
fun Image(
    painter: Painter,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null
) 

可以看到和 Jetpack Compose 中是一致的,不同的就是如何在 Compose Desktop 中构建 Painter

先来看一种构建的方式吧:

BitmapPainter(useResource(resourcePath, ::loadImageBitmap))

这块的 resourcePath 指的是图片的路径,这个路径是如何定义的呢?还记得上面创建完项目的初始结构么?里面有一个 resource 文件夹,这个文件夹就是根目录,比如 resource 文件夹中有一张图片“icon.png”,要构建这张图片的 Painter 就可以使用如下代码:

BitmapPainter(useResource("icon.png", ::loadImageBitmap))

当然可以在 resource 中创建不同的文件夹来存放不同的资源,图片也是一样的。

简单解释下这行代码吧,虽然看着就一行,其实使用到了好几个函数,首先说下 useResource ,它的作用是将传入的文件路径打开为 InputStream ,而 loadImageBitmap 函数是将 InputStream 转为 ImageBitmap ,最后通过使用 BitmapPainter 才构建出一个 Painter

光说不练假把式!来整一张图片试下吧!

Image(painter = BitmapPainter(useResource("image/icon.png", ::loadImageBitmap)),"Test")

由于我将图片放到 resource 中的 image 文件夹中,所以这块的路径做了一些修改,再来看下图片的目录吧:

在这里插入图片描述

下面来运行看下效果!

在这里插入图片描述

OK,没问题,图片展示出来了!又向成功迈进了一步!!!

SVG 图片?

咱们现在在安卓中使用的图片大多改为了 SVG 格式的,体积又小且清晰,接下来按照相同的方式试一下,先放一张 SVG 格式的图片到刚才创建的 image 文件夹中:

在这里插入图片描述

图片放好了,下面来修改下图片的路径:

Image(painter = BitmapPainter(useResource("image/ic_launcher.svg", ::loadImageBitmap)),"Test")

再运行下程序!

在这里插入图片描述

额。。。刚不是还好好的嘛!这改了个图片格式就不行了?来看下报错信息吧!

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Failed to Image::makeFromEncoded
	at org.jetbrains.skia.Image$Companion.makeFromEncoded(Image.kt:139)
	at androidx.compose.ui.res.ImageResources_desktopKt.loadImageBitmap(ImageResources.desktop.kt:33)
	at MainKt$App$1.invoke(Main.kt:31)
	at MainKt$App$1.invoke(Main.kt:24)

可以看到报了编码错误,这应该咋么搞???

Compose Desktop 早就为我们想到了:

useResource(resourcePath) { loadSvgPainter(it, Density(2f)) }

Compose Desktop 为我们提供了一个叫 loadSvgPainter 的函数,专门用来处理 SVG 图片,接下来使用下看看:

Image(painter = useResource("image/ic_launcher.svg") { loadSvgPainter(it, Density(2f)) },"Test")

使用也很简单,运行看下效果吧:

在这里插入图片描述

嗯,没问题,正常展示!为了方便大家在 Compose Desktop 中使用图片,我写了一个构建 Painter 的函数:

/**
 * 构建Painter,为了图片使用
 * 
 * @param resourcePath 图片路径
 */
fun buildPainter(resourcePath: String): Painter {
    val painter: Painter = if (resourcePath.endsWith(".svg")) {
        useResource(resourcePath) {
            loadSvgPainter(it, Density(2f))
        }
    } else if (resourcePath.endsWith(".png") || resourcePath.endsWith(".jpg") ||
        resourcePath.endsWith(".jpeg") || resourcePath.endsWith(".webp") ||
        resourcePath.endsWith(".PNG") || resourcePath.endsWith(".JPG") ||
        resourcePath.endsWith(".JPEG") || resourcePath.endsWith(".WEBP") || resourcePath.endsWith(".ICO")
    ) {
        BitmapPainter(useResource(resourcePath, ::loadImageBitmap))
    } else {
        throw IllegalArgumentException("resource is illegal argument")
    }
    return painter
}

这里并没有列举全所有的图片的后缀,但咱们一般使用到的都列举了出来,如果有特殊需求的话大家可以自己加上需要的后缀即可。

柳暗花明

上面的一堆都是自己犯傻。。。其实 Compose Desktop 中也可以直接使用 painterResource 来构建图片。

Image(
    painter = painterResource(getWeatherIcon(dailyBean.iconDay)), "",
)

哈哈哈,为什么要写上面的一大堆呢,是让大家体会下我当时写的时候的经历。。。(内心:我是不是太坏了,哈哈哈😂)

网络图片

在安卓中加载网络图片一般都会使用 Glide ,但 Glide 是依赖于安卓的,所以无法在 Compose Desktop 中使用,不过也没有必要,下面来看下如何在 Compose Desktop 中使用网络图片吧:

@Composable
fun <T> AsyncImage(
    load: suspend () -> T,
    painterFor: @Composable (T) -> Painter,
    contentDescription: String,
    modifier: Modifier = Modifier,
    contentScale: ContentScale = ContentScale.Fit,
) {
    val image: T? by produceState<T?>(null) {
        value = withContext(Dispatchers.IO) {
            try {
                load()
            } catch (e: IOException) {
                e.printStackTrace()
                null
            }
        }
    }
    if (image != null) {
        Image(
            painter = painterFor(image!!),
            contentDescription = contentDescription,
            contentScale = contentScale,
            modifier = modifier
        )
    }
}

/* 加载网络图片 */

fun loadImageBitmap(url: String): ImageBitmap =
    URL(url).openStream().buffered().use(::loadImageBitmap)

fun loadSvgPainter(url: String, density: Density): Painter =
    URL(url).openStream().buffered().use { loadSvgPainter(it, density) }

fun loadXmlImageVector(url: String, density: Density): ImageVector =
    URL(url).openStream().buffered().use { loadXmlImageVector(InputSource(it), density) }

加载图片的方法有了,如何使用呢?

AsyncImage(
    load = { loadImageBitmap("https://www.wanandroid.com/blogimgs/42da12d8-de56-4439-b40c-eab66c227a4b.png") },
    painterFor = { BitmapPainter(it) },
    contentDescription = "Sample",
    modifier = Modifier.width(200.dp)
)

使用并不难,将图片的网址放进去即可,下面来运行看下效果吧:

在这里插入图片描述

还不错,图片显示得也挺快,大家可以试试!

网络请求—Retrofit

咱们要编写的天气应用肯定是需要网络请求的,这个项目中使用的是和风天气的免费 API,但问题来了,在 Compose Desktop 中要如何使用网络请求呢?

难道要使用 HttpURLConnection 原生进行请求么?这根本不像一个安卓开发的风格啊!就算不能使用 Retrofit ,最不济也得使用 OKHttp 啊!那。。。在 Compose Desktop 中能使用这些安卓中的网络请求框架么?

答案是能!为什么不能?这些框架又没有依赖安卓中的一些特定东西!对吧?

说干就干!第一步当然还是添加依赖:

dependencies {
    implementation(compose.desktop.currentOs)
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}

这代码是不是很熟悉?没错,在安卓项目中也是这么使用的啊!

接下来和在安卓中的使用方法其实就是一样的了,协程也可以使用!

object ServiceCreator {

    // 开发版
    private const val BASE_URL = "https://devapi.qweather.com/v7/"
    private const val CONNECT_TIMEOUT = 30L
    private const val READ_TIMEOUT = 10L

    private fun create(url: String = BASE_URL): Retrofit {
        val okHttpClientBuilder = OkHttpClient().newBuilder().apply {
            connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
            readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
        }

        return RetrofitBuild(
            url = url,
            client = okHttpClientBuilder.build(),
            gsonFactory = GsonConverterFactory.create()
        ).retrofit
    }

    fun <T> create(service: Class<T>): T = create().create(service)
}

先构建了一个 Retrofit 对象,然后创建一个接口:

interface CityWeatherService {

    /**
     * 实时天气
     *
     * @param key 用户认证key
     * @param location 需要查询地区的LocationID或以英文逗号分隔的经度,纬度坐标
     * @param lang 多语言设置,默认中文
     *
     * 实时温度、体感温度、风力风向、相对湿度、大气压强、降水量、能见度、露点温度、云量等数据。
     */
    @GET("weather/now")
    suspend fun getWeatherNow(
        @Query("key") key: String = WEATHER_KEY,
        @Query("location") location: String,
        @Query("lang") lang: String = Lang.ZH_HANS.code
    ): WeatherNowBean

}

这个没什么说的,接下来再创建一个调用的中间层:

object PlayWeatherNetwork {

    private val cityWeatherService = ServiceCreator.create(CityWeatherService::class.java)

    suspend fun getWeatherNow(location: String): WeatherNowBean =
        cityWeatherService.getWeatherNow(location = location)

}

OK,完事,最后调用下试试!

LaunchedEffect(text) {
    val weatherNow = PlayWeatherNetwork.getWeatherNow("CN101010100")
    println("weatherNow:$weatherNow")
}

运行看结果!

当然 Window 中显示还是刚才的截图,只不过这块打印出了网络请求的结果,来看下打印出的 Log 信息:

weatherNow:WeatherNowBean(fxLink=http://hfx.link/2ax1, code=200, refer=model.Refer@5a146688, now=NowBaseBean(vis=2, temp=3, obsTime=2022-12-12T15:56+08:00, icon=503, wind360=98, windDir=东风, pressure=1016, feelsLike=-3, cloud=0, precip=0.0, dew=-20, humidity=16, text=扬沙, windSpeed=11, windScale=2, city=null), updateTime=2022-12-12T16:02+08:00)

WeatherNowBean 就是网络请求的实体类,这块不做过多描述。

通过这个小例子是为了告诉大家在 Compose Desktop 项目中也能使用咱们熟悉的 Retrofit !大家知道这一点就够了!

这块我在写的时候还有一个小插曲,这块的实体类之前是直接复制和风天气开源项目中的,不是 kotlin 而是是 java 编写的,运行的时候就报错了,将实体类改为 Kotlin 后就能正常运行了。

Jetpack 库使用

刚才在上面提到了,我就是因为 GoogleJetpack 中的库之后会支持 Compose Desktop 才决定玩一玩 Compose Desktop 的,那肯定也要尝试使用下啊!

Google 目前只推出了两个库支持:CollectionDataStoreCollection 目前使用不到,那就先来看看如何使用 DataStore 吧!

第一步还是添加依赖:

dependencies {
  	......
    implementation("androidx.datastore:datastore-preferences-core:1.1.0-dev01")
}

其实 DataStore 的使用方法和之前在安卓中没有什么区别,唯一的区别就是创建的时候,先来看下在安卓中是如何创建 DataStore 的吧:

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

这里用到了 Context ,这就是安卓中独有的,Java 中并没有啊,Kotlin 中也没有啊!那应该如何创建呢?

val dataStores = PreferenceDataStoreFactory.create {
    File("${System.getProperty("user.home")}/playWeatherData.preferences_pb")
}

DataStore 早就为我们考虑到了,使用 PreferenceDataStoreFactory 中的 create 方法就可以创建 DataStore 了。这块需要说明下 System.getProperty("user.home") ,这个获取的是用户 home 的路径,这块踩坑踩了很久,搞了大半天!最开始使用的是 user.dir ,这是项目 work 的目录,但是如果使用 user.dir 的话打包运行的话就会报错,说无法进行读写,所以修改为了 "user.home" 。其实还有一点也会报错,这块文件的后缀名一定得是 preferences_pb ,如果不写或修改为别的就会报错。这块帮助大家避下坑吧!

剩下的使用方法就和安卓中一摸一样了!大家如果没有使用过的话可以看下我之前写 DataStore 的一篇文章:再抱一抱DataStore 。

小结

本文从一个新建的 Compose Desktop 项目开始,列举了一些大家在开发过程中可能遇到的一些问题,提前帮助大家踩了踩坑,文中所有代码都在 Github 中,包括文章开始放的这回要编写的天气应用。

Github 地址:https://github.com/zhujiang521/PlayWeather/tree/desktop

如果文中写的有误,欢迎在评论区提出,咱们一起探讨。

文章如果能帮助到大家,哪怕是一点,我也非常高兴,先这样。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java判断选择的日期是否在某个时间区间

效果展示&#xff1a; 具体代码&#xff1a; String startTimeZoom "08:00";//时间区间-开始时间 String endTimeZoom "22:00";//时间区间-结束时间String startTimeChoice "08:00";//时间区间-选择的开始时间 String endTimeChoice "2…

Java中的异常(Exception)

目录 一、什么是异常(Exception)&#xff1f; 二、解决方案&#xff1a;try-catch 三、异常的概念 四、异常的体系图(重点) 小结&#xff1a; 五、常见的五大运行时异常 1、NullPointerException空指针异常 2、ArithmeticException数学运算异常 3、ArrayIndexOutOfBounds…

【MySQL进阶】浅谈InnoDB中的BufferPool

【MySQL进阶】浅谈InnoDB中的BufferPool 文章目录【MySQL进阶】浅谈InnoDB中的BufferPool一、前言——缓存的重要性二、InnoDB的Buffer Pool1&#xff1a;BufferPool 简介2&#xff1a;BufferPool内部组成3&#xff1a;free链表的管理4&#xff1a;flush链表的管理5&#xff1a…

PCA与PCoA

通过分析坐标轴中样本和样本间的距离可看到 2 个样本或 2 组样本间的差异性。若2个样本或2组样本之间的直线距离较近&#xff0c;则表示这2个样本或2组样本差异性较小&#xff1b;相反则表示差异性较大。因此PCA和PCoA 具有直观性(直接看两点之间的距离)和完整性(呈现所有样本)…

spring中i18n国际化处理多语言

前言 在项目中&#xff0c;往往用户会存在多语言的述求&#xff0c;比如说一个系统既有中文的用户&#xff0c;又有英文的用户。怎么来实现多语言呢&#xff1f; 首先前后端分离的项目&#xff0c;前端会有自己的多语言实现方案&#xff0c;大致效果就是&#xff0c;用户切换…

rocketmq源码-pull模式拉取消息、同步拉取消息

前言 上一篇博客&#xff0c;记录的是push模式&#xff0c;异步发送netty请求拉取消息的代码&#xff0c;这篇博客主要记录consumer发送同步netty请求&#xff0c;去拉取消息的逻辑&#xff0c;但是对于同步发送请求&#xff0c;需要结合LitePullConsumer来看 在Lite PullCon…

C语言基础—运算符及优先级

本章主要讲解运算符的优先级和结合顺序 知识点&#xff1a; 运算符分类&#xff0c;记忆了解13种运算符注意运算符的易错点运算符的优先级 运算符及优先级运算符分类❗ 运算符易错不能直接连续判断“< 值 <”关于判断两个浮点数是否相等逻辑表达式&#xff08;布尔型&am…

CSS:border-image

border-image属性对图像的规格和比例比较高&#xff0c;导致使用成本比较高。另外&#xff0c;常见的场景中&#xff0c;大家更倾向于扁平化而不是非拟物化&#xff0c;边框装饰通常在项目中不会出现。 border-image是由多个CSS属性缩写的&#xff0c;比如: border-image-sou…

hc32和stm32 can波特率设置

前言 笔者在调试一款新的mcu的can通信时候&#xff0c;最麻烦的是波特率设置。由于没有弄明白其计算原理&#xff0c;经常出错&#xff0c;且不同的波特率有不同的采样点的要求。浪费了不少时间。这次一次搞明白can波特率的计算公式。 can波特率计算 在ISO 11898-1-2015 标准…

音视频基础概念(2)——音频

目录 1. 基本知识 2.采样率和采样位数 3.音频编码 4. 声道数 5. 码率 6. 音频格式 日常生活中&#xff0c;音视频随处可见&#xff0c;包括视频、音频、编解码、封装容器、音视频等概念。 1. 基本知识 音频数据的承载方式最常用的是脉冲编码调制&#xff0c;即PCM。于…

JAVA面试(2022年Java常见面试问题)

1、谈谈你对Spring中IOC和AOP的理解。 答案&#xff1a; 2、谈谈Spring的bean的创建过程和生命周期。 答案&#xff1a; 3、谈一下JVM的内存分配和垃圾回收机制。 答案&#xff1a; 4、谈一下你使用比较多的设计模式和场景。 答案&#xff1a; 5、谈一些mysql的事务隔离。 …

什么是文件系统?

【推荐阅读】 一文了解Linux上TCP的几个内核参数调优 一文剖析Linux内核中内存管理 分析linux启动内核源码 文件系统是操作系统用于明确存储设备&#xff08;常见的是磁盘&#xff0c;也有基于NAND Flash的固态硬盘&#xff09;或分区上的文件的方法和数据结构&#xff0c;…

传奇列表上传登录器公告小窗口怎么修改

传奇列表上传登录器公告小窗口怎么修改 很多小伙伴不会上传列表&#xff0c;我是艾西今天给大家分享下怎么上传列表 我们开始实操&#xff08;纯教学分享&#xff09; 在我们的网站文件夹里创建一个列表.txt 在浏览器里找一个列表模板例&#xff1a;www.pkp123.cn:88&#xff…

1570_AURIX_TC275_SCU_ERU

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 从系统的逻辑图看&#xff0c;能够很清楚看到这个模块的功能处理过程&#xff1a;首先是边沿信号的识别&#xff0c;接着是根据判断进行置位处理&#xff0c;最后进行工作触发。如果设置的…

使用Nordic的nRF52840 Dongle配合Wireshark对蓝牙设备抓包(BLE)

硬件准备&#xff1a; 1&#xff09;nRF52840 Dongle 2&#xff09;待抓包的蓝牙设备 软件准备&#xff1a; 1&#xff09;Python 2&#xff09;Wireshark 3&#xff09;nRF Sniffer for Bluetooth LE Python安装 需要注意的是下载的nRF Sniffer for Bluetooth LE版本是否…

K8s 之 Deployment 应用案例

目录一、YAML 配置文件二、运行服务三、更新 Deployment3.1 动态伸缩容3.2 触发上线四、故障自动转移五、指定节点运行 Pod六、删除 deployment一、YAML 配置文件 我们要清楚&#xff0c;在 K8s 中有两种创建资源的方式&#xff1a; &#xff08;1&#xff09;命令行方式&…

【GO】 K8s 管理系统项目[API部分--Pod]

K8s 管理系统项目[API部分–Pod] 前端: Vueelement plus 后端: gogin 1. 功能设计 2. 初始化 2.1创建项目 2.2 配置goproxy GOPROXYhttps://goproxy.cn 2.3 添加格式化工具 2.4 安装模块 go get k8s.io/client-go/tools/clientcmd go get k8s.io/api/core/v1 go get k8s.i…

使用 docker buildx 构建跨平台 Go 镜像

目录 前提 docker buildx 启用 Buildx builder 实例 构建驱动 buildx 的跨平台构建策略 一次构建多个架构 Go 镜像实践 源代码和 Dockerfile 执行跨平台构建 验证构建结果 如何交叉编译 Golang 的 CGO 项目 准备交叉编译环境和依赖 交叉编译 CGO 示例 总结 参考链接…

供水设备远程监控客户案例

一、客户介绍 客户积累多年的技术研发和工程运维经验&#xff0c;对传统的恒压供水工程所面临的维护难、维修难、运维效率低和能耗管控弱等诸多问题有深刻的体会&#xff0c;经过广泛调研&#xff0c;客户最终选择使用蓝蜂物联网的云平台和边缘计算产品对恒压供水设备和工程进行…

数据处理指令(一)—— 搬移指令MOV、MVN

数据处理指令指的是和数学运算、逻辑运算相关的指令&#xff0c;比如加减乘、与或非、赋值比较等 目录 1、MOV —— 直接搬移 (1) MOV 指令格式 (2) MOV生成指令的策略&#xff08;MOV的优点&#xff09; (3) MOV 只能搬移“立即数”的原因&#xff08;MOV的缺点&#x…