稳定的 Glance 来了,安卓小部件有救了!
稳定的 Glance 来了,安卓小部件有救了!
稳定版本的 Glance
终于发布了,来一起看看吧,看看这一路的旅程,看看好用么,再看看如何使用!
前世今生
故事发生在两年的一天吧,其实夸张了,不到两年,而是 633 天前。。。
在 Jetpack
的更新网站上发现多了一个名叫 Glance
的库,版本为 1.1.0-alpha01
,发现这个库后就赶快点击进去看看是干啥用的:
看到这个库的简介的时候给我高兴坏了,大致意思是:可以使用 Compose
风格的 API 来为小部件构建布局。然后就尝试了下并写了一篇文章:Jetpack Glance?小部件的春天来了
小部件这个东西虽然是安卓中首先发布的,但是这么多年来一直平平无奇,直到苹果 IOS 中也“推出”了小部件之后,才唤起了小部件的第二春,然后安卓官方、也就是谷歌才想起来自己原来也有这么个东西,就在 Android 12 中才对小部件做了一些改进,不容易啊,这么多年来第一次给安卓小部件增加了一些内容。。。
之后接着官方也看不下去了,看不下去什么呢?多年前的安卓开发使用起小部件没有问题,但是现在的安卓开发变为了 Compose
,而小部件还是只能使用 XML
,于是乎,Glance
应运而生!
短短几行字,基本聊了下 Glance
的前世今生,一个库,要 635 天才能从 alpha 版本变为 stable,如果再加上第一个 alpha 版本的开发时间的话,肯定超过了两年。。。这个速度如果放到国内的话。。。。算了,大家理解就好。其实也不能怪他们,Jetpack
中的库实在是太多了,都需要时间和人力维护嘛!
下面再来看一下 Glance
的发布时间线吧:
没有辜负我这么久的等待,哈哈哈!
之前那篇文章使用的是我写的一个天气,这回改下,改为使用 “玩安卓” 吧!
本文中的代码地址:玩安卓 Github:https://github.com/zhujiang521/PlayAndroid
添加依赖
dependencies {implementation "androidx.glance:glance:1.0.0"
}
android {buildFeatures {compose true}
composeOptions {kotlinCompilerExtensionVersion = "1.5.3"}
}
依赖添加很简单,如果你的项目中有 Compose
的话,只需要添加下 dependencies
中的内容即可。
创建小部件
首先来创建一个小部件,大家都知道,小部件其实就是一个 BroadcastReceiver
,所以需要在 AndroidManifest
中声明下:
<receiverandroid:name=".widget.ArticleListWidget"android:exported="false"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter>
<meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/article_list_widget_info" />
</receiver>
上面的代码大部分大家都很熟悉了,唯一和普通广播不同的就是多了一个配置项,如果写过小部件的应该也很熟悉了:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:description="@string/app_widget_description"android:initialKeyguardLayout="@layout/glance_default_loading_layout"android:initialLayout="@layout/glance_default_loading_layout"android:minWidth="110dp"android:minHeight="69dp"android:minResizeWidth="110dp"android:minResizeHeight="69dp"android:resizeMode="horizontal|vertical"android:targetCellWidth="2"android:targetCellHeight="2"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen" />
这里的配置项其实不少,上面所列举的只是常用的一些,那到底都可以配置那些项呢?点进去看看不得了!
<declare-styleable name="AppWidgetProviderInfo"><!-- AppWidget的最小宽度 --><attr name="minWidth"/><!-- AppWidget的最小高度 --><attr name="minHeight"/><!-- AppWidget可以调整大小的最小宽度. --><attr name="minResizeWidth" format="dimension"/><!-- AppWidget可以调整大小的最小高度. --><attr name="minResizeHeight" format="dimension"/><!-- AppWidget可以调整大小的最大宽度. --><attr name="maxResizeWidth" format="dimension"/><!-- AppWidget可以调整大小的最大高度. --><attr name="maxResizeHeight" format="dimension"/><!-- AppWidget的默认宽度,以桌面网格单元为单位 --><attr name="targetCellWidth" format="integer"/><!-- AppWidget的默认高度,以桌面网格单元为单位 --><attr name="targetCellHeight" format="integer"/><!-- 更新周期(以毫秒为单位),如果AppWidget将更新自己,则为0 --><attr name="updatePeriodMillis" format="integer" /><!-- 初始布局的资源id --><attr name="initialLayout" format="reference" /><!-- 初始Keyguard布局的资源id --><attr name="initialKeyguardLayout" format="reference" /><!-- 要启动配置的AppWidget包中的类名。如果没有提供,则不会启动任何活动 --><attr name="configure" format="string" /><!-- 在可绘制的资源id中预览AppWidget配置后的样子。如果没有提供,则将使用AppWidget的图标 --><attr name="previewImage" format="reference" /><!-- 预览AppWidget配置后的样子的布局资源id。与previewImage不同,previewLayout可以更好地在不同的区域、系统主题、显示大小和密度等方面展示AppWidget。如果提供了,它将优先于支持的小部件主机上的previewImage。否则,将使用previewImage --><attr name="previewLayout" format="reference" /><!-- AppWidget子视图的视图id,应该是自动高级的。通过小部件的主机 --><attr name="autoAdvanceViewId" format="reference" /><!-- 可选参数,指示是否以及如何调整此小部件的大小。支持使用|运算符组合值,也就是说可以横向和纵向可以同时使用 --><attr name="resizeMode" format="integer"><flag name="none" value="0x0" /><flag name="horizontal" value="0x1" /><flag name="vertical" value="0x2" /></attr><!-- 可选参数,指示可以显示此小部件的位置,即。主屏幕,键盘保护,搜索栏或其任何组合. --><attr name="widgetCategory" format="integer"><flag name="home_screen" value="0x1" /><flag name="keyguard" value="0x2" /><flag name="searchbox" value="0x4" /></attr><!-- 指示小部件支持的各种特性的标志。这些是对小部件主机的提示,实际上并不改变小部件的行为 --><attr name="widgetFeatures" format="integer"><!-- 小部件可以在绑定后随时重新配置 --><flag name="reconfigurable" value="0x1" /><!-- 小部件由应用程序直接添加,不需要出现在可用小部件的全局列表中 --><flag name="hide_from_picker" value="0x2" /><!-- 小部件提供了一个默认配置。主机可能决定不启动所提供的配置活动 --><flag name="configuration_optional" value="0x4" /></attr><!-- 包含小部件简短描述的字符串的资源标识符 --><attr name="description" />
</declare-styleable>
由于配置项确实不少,所以直接写了下注释,大家根据需求进行使用即可,目前这是所有的小部件配置项,有一些是在 Android 12 中新增的。
工欲善其事,必先利其器
配置项写好了,接下来该编写小部件的代码了!
GlanceAppWidgetReceiver
之前编写小部件的时候都会用到 AppWidgetProvider
,它继承自 BroadcastReceiver
,但现在使用 Glance
需要继承 GlanceAppWidgetReceiver
,那么 GlanceAppWidgetReceiver
是个啥?来,3、2、1,上代码!
abstract class GlanceAppWidgetReceiver : AppWidgetProvider() {
......
/*** 用于生成AppWidget并将其发送给AppWidgetManager的GlanceAppWidget的实例* 注意:这不会为GlanceAppWidget设置CoroutineContext,它将始终在主线程上运行。*/abstract val glanceAppWidget: GlanceAppWidget......
}
通过上面代码可以看出 GlanceAppWidgetReceiver
继承自 AppWidgetProvider
,是一个抽象类,并且需要实现一个抽象函数 glanceAppWidget
,这个函数需要返回的对象为 GlanceAppWidget
。
GlanceAppWidget
那就再来看下 GlanceAppWidget
吧,来,3、2、1,上代码!
abstract class GlanceAppWidget(@LayoutResinternal val errorUiLayout: Int = R.layout.glance_error_layout,
) {
....../*** 重写此函数以提供 Glance Composable*/abstract suspend fun provideGlance(context: Context,id: GlanceId,)
/*** 定义对大小的处理。*/open val sizeMode: SizeMode = SizeMode.Single
/*** 特定于视图的小部件数据的数据存储。*/open val stateDefinition: GlanceStateDefinition<*>? = PreferencesGlanceStateDefinition
/*** 当应用程序小部件从其主机上删除时由框架调用。当该方法返回时,与glanceId关联的状态将被删除。*/open suspend fun onDelete(context: Context, glanceId: GlanceId) {}......
}
可以看到 GlanceAppWidget
也是一个抽象类,构建这个类时有一个可选参数,意思是遇到错误时需要展示的布局。然后有几个子类可以重写的函数,还有一个必须实现的抽象函数,下面来分别看下吧:
provideGlance
: 此函数为抽象函数,子类必须重写;重写此函数以提供Glance Composable
,也就是说这个函数是用来编写布局的。一旦数据准备好,使用provideContent
提供可组合对象。provideGlance
作为CoroutineWorker
在后台运行,以响应update
和updateAll
的调用,以及来自Launcher
的请求。在provideContent
被调用之前,provideGlance
受限于WorkManager
时间限制(目前为十分钟),在调用provideContent
之后,组合继续运行并重新组合大约45秒。当接收到UI交互或更新请求时,会添加额外的时间来处理这些请求。需要注意的是:如果provideGlance
已经在运行,update
和updateAll
不会重新启动。因此应该在调用provideContent
之前加载初始数据,然后在组合中观察数据源(例如collectasstate
)。这可以确保小部件在组合处于活动状态时继续更新,当从应用程序的其他地方更新数据源时,确保调用update,以防这个小部件的Worker当前没有运行。sizeMode
: 定义对小部件大小的处理,这个会在下面展开来说stateDefinition
: 特定于视图的小部件数据的数据存储,当存储数据发生变化时,小部件会进行刷新onDelete
: 应用程序小部件从其主机上删除时由框架调用。当该方法返回时,与glanceId关联的状态将被删除。
SizeMode
OK,上面简单看了下 GlanceAppWidget
中的公开函数,接下来看下 SizeMode
,老规矩,3、2、1,上代码!
sealed interface SizeMode {/*** GlanceAppWidget提供了一个UI。LocalSize将是AppWidget的最小尺寸,在AppWidget提供程序信息中定义,单个*/object Single : SizeMode {override fun toString(): String = "SizeMode.Single"}
/*** 为每个AppWidget可能显示的大小提供了一个UI。大小列表由选项包提供(参见getAppWidgetOptions)。每个大小都将调用可组合对象。在调用期间,LocalSize将是生成UI的对象。*/object Exact : SizeMode {override fun toString(): String = "SizeMode.Exact"}
/*** 在Android 12及以后的版本中,每个提供的大小将调用一次composable,并且从大小到视图的映射将被发送到系统。然后框架将根据App Widget的当前大小来决定显示哪个视图。在Android 12之前,composable将被调用用于显示App Widget的每个大小(如Exact)。对于每种尺寸,将选择最佳视图,即适合可用空间的最大视图,或者如果不适合则选择最小视图。Params: sizes -要使用的大小列表,不能为空。*/class Responsive(val sizes: Set<DpSize>) : SizeMode {
init {require(sizes.isNotEmpty()) { "The set of sizes cannot be empty" }}
......}
}
可以看到 SizeMode
是一个接口,一共有三个类实现了 SizeMode
接口,Single
和 Exact
好理解一些,Responsive
不太好理解,但是还记得 Android 12
中小部件的更新么?RemoteView
增加了一个构造函数,来看下吧:
public RemoteViews(@NonNull Map<SizeF, RemoteViews> remoteViews)
即每个提供的大小将调用一次 composable
,并且从大小到视图的映射将被发送到系统,也就是说会将定义好的大小做缓存,可以优化小部件的展示。
爱码士
上面说了半天还没进入正题,一行正经代码都还没写。。。
先来搞一个 GlanceAppWidget
吧:
class ArticleListWidgetGlance : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {provideContent {// 编写 Glance 代码}}
}
预料之中,继承自 GlanceAppWidget
,实现抽象函数 provideGlance
,但还是无法在 provideGlance
中直接使用 Glance
来编写 Compose
风格的布局,还需要调用 provideContent
,上面其实也提到过了,那就来看下 provideContent
吧,3、2、1,上代码!
suspend fun GlanceAppWidget.provideContent(content: @Composable @GlanceComposable () -> Unit
): Nothing {coroutineContext[ContentReceiver]?.provideContent(content)?: error("provideContent requires a ContentReceiver and should only be called from " + "GlanceAppWidget.provideGlance")
}
可以看到这是一个扩展函数,只有一个参数,看到这个参数是不是就理解了,终于看到了咱们熟悉的 @Composable
,需要注意的是:如果此函数与自身并发调用,则前一个调用将抛出 CancellationException,新内容将替换它。还有就是这个函数只能从 GlanceAppWidget.provideGlance
调用。
OK,GlanceAppWidget
编好了之后就该写下 GlanceAppWidgetReceiver
了,上代码!
class ArticleListWidget : GlanceAppWidgetReceiver() {override val glanceAppWidget: GlanceAppWidget = ArticleListWidgetGlance()
}
更简单了,只有三行代码,同样地,也实现了 GlanceAppWidgetReceiver
的抽象函数,并返回了刚创建好的 ArticleListWidget
。
其实到这里为止 Glance
的整套流程就简单跑通了。接下来就来编写下布局吧:
override suspend fun provideGlance(context: Context, id: GlanceId) {val articleList = getArticleList()provideContent {GlanceTheme {Column {Text(text = stringResource(id = R.string.widget_name),)LazyColumn {items(articleList) { data ->GlanceArticleItem(context, data)}}}}}
}
啊!熟悉的配方!熟悉的味道!
爽,爽,爽
看着上面熟悉的味道是不是很舒服,哈哈哈,写小部件终于也可以优雅一些了!
耗时操作优化
不知道大家注意到没有,provideGlance
竟然是一个挂起函数,这是什么意思,难道是???
没错!可以放心地在这里执行耗时操作了!比如你就可以这样:
override suspend fun provideGlance(context: Context, id: GlanceId) {val name = getName()provideContent {Text(text = name)}
}
private suspend fun getName():String {delay(5000L)return "我爱你啊"
}
下面来运行看下效果!
是不是挺好,解决了小部件的一大坑!
小部件更新
小部件的更新一直也是个问题,比如横竖屏转换后小部件的刷新、系统配置修改了之后的刷新,这些都是没有的,系统应用可以和系统进行一些骚操作,但是普通应用不可以啊,所以 Glance
中就引入了 WorkManager
来改善这个问题,最低可以设置十分钟的间隔刷新。
下面就来简单看下使用吧:
class WorkWorker(private val context: Context,workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
companion object {
private val uniqueWorkName = WorkWorker::class.java.simpleName
// 排队进行工作fun enqueue(context: Context, size: DpSize, glanceId: GlanceId, force: Boolean = false) {val manager = WorkManager.getInstance(context)val requestBuilder = OneTimeWorkRequestBuilder<WorkWorker>().apply {addTag(glanceId.toString())setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)setInputData(Data.Builder().putFloat("width", size.width.value.toPx).putFloat("height", size.height.value.toPx).putBoolean("force", force).build())}val workPolicy = if (force) {ExistingWorkPolicy.REPLACE} else {ExistingWorkPolicy.KEEP}
manager.enqueueUniqueWork(uniqueWorkName + size.width + size.height,workPolicy,requestBuilder.build())}
/*** 取消任何正在进行的工作*/fun cancel(context: Context, glanceId: GlanceId) {WorkManager.getInstance(context).cancelAllWorkByTag(glanceId.toString())}}
override suspend fun doWork(): Result {// 需要执行的操作return Result.success()}
}
OK,先创建了一个 Work
,然后看下在 Glance
中如何使用吧!
override suspend fun onDelete(context: Context, glanceId: GlanceId) {super.onDelete(context, glanceId)WorkWorker.cancel(context, glanceId)
}
override suspend fun provideGlance(context: Context, id: GlanceId) {provideContent {val size = LocalSize.currentGlanceTheme {CircularProgressIndicator()// 在合成完成后,使用glanceId作为标记为worker排队,以便在小部件实例被删除的情况下取消所有作业val glanceId = LocalGlanceId.currentSideEffect {WorkWorker.enqueue(context, size, glanceId)}}}
}
很简单,在 provideGlance
中排队执行操作,然后在 onDelete
中将 Work
取消了即可。
便捷的 ListView
写过小部件的都知道 ListView
特别坑,原生小部件想要实现 ListView
需要实现 Factory
,Service
等,而在 Glance
这里直接两三行代码搞定。
LazyColumn(modifier = GlanceModifier.fillMaxSize().padding(horizontal = 10.dp)
) {items(articleList) { data ->GlanceArticleItem(context, data)}
}
没错,和 Compose
中一样,名字也一样,都是 LazyColumn
,写起来非常便捷。
更方便的 LocalXXX
大家都知道 Compose
中的 LocalXXX
非常方便好用,Glance
中也提供了一些:
/*** 生成的概览视图的大小。概览视图至少有那么多空间可以显示。确切的含义可能会根据表面及其配置方式而变化。*/
val LocalSize = staticCompositionLocalOf<DpSize> { error("No default size") }
/*** 生成概览视图时应用程序的上下文。*/
val LocalContext = staticCompositionLocalOf<Context> { error("No default context") }
/*** 本地视图状态,在surface实现中定义。用于特定于视图的状态数据的可定制存储。*/
val LocalState = compositionLocalOf<Any?> { null }
/*** 当前合成生成的概览视图的唯一Id。*/
val LocalGlanceId = staticCompositionLocalOf<GlanceId> { error("No default glance id") }
不过这块需要注意包的导入问题。
Action
小部件中之前如果想要实现点击效果的话只能使用 PendingIntent
,这样很麻烦,现在 Glance
为我们提供了 Action
,使用方法如下:
Button(text = "Glance按钮", onClick = actionStartActivity(ComponentName("包名","包名+类名")))
Button(text = "Glance按钮", onClick = actionStartActivity<MainActivity>())
Button(text = "Glance按钮", onClick = actionStartActivity(MainActivity::class.java))
不仅如此,还可以像下面这样操作:
Text(text = "点击", modifier = GlanceModifier.clickable {Log.e("TAG", "provideGlance: click")
})
这个实在是太方便了!推荐大家使用。但这个需要注意,如果想使用这个实现动画效果的话是不行的,因为它没有办法在特别短的时间内刷新,我之前尝试过 Compose
中的属性动画 animate*AsState
,结果就是只执行了最后的结果,中间过程全部忽略了。。。。
坑,坑,坑
“人家官方废了这么大劲开发出来的库,怎么能说人家坑呢?”
“因为它确实坑啊!”
坑一
刚才看到的熟悉的代码,其实一点也不熟悉,为什么这么说,来看下导入的包就知道了:
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.action.Action
import androidx.glance.action.clickable
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.size
import androidx.glance.layout.wrapContentWidth
import androidx.glance.text.Text
虽然 Composable
还是使用的 Compose
的,但是里面的可组合项全部是 Glance
中重写的。。。。
咱就是说啊!有没有一种可能,就是你在写的时候自然地就导入了 Compose
的包?运行直接报错!也没有任何提醒。。
是不是?没有一点提醒,这种情况官方有没有一种可能,就是像是 Glance
中的 Modifier
一样,也在前面加一个前缀,让开发者能够容易区分一点?即使加前缀不好看,你们不想加,有没有可能修改下编译器,让编译器告诉开发者不能这么写行不行?
坑二
图片的加载,图片是安卓开发中太常见的东西了,以前咱们使用 ImageView
来进行图片的展示,现在有了 Compose
了我们使用 Image
来进行展示,Glance
中同样是使用 Image
来展示,来玩个游戏吧,找不同!先来看下 Compose
中的 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
)
再来看下 Glance
中的 Image
:
@Composable
fun Image(provider: ImageProvider,contentDescription: String?,modifier: GlanceModifier = GlanceModifier,contentScale: ContentScale = ContentScale.Fit,colorFilter: ColorFilter? = null
)
是不是很像,但是 Glance
因为 RemoteView
的限制少了一些功能,在 Compose
中咱们可以通过 painterResource
来构建出 Painter
,但在 Glance
中又换了个名字 ImageProvider
,咱就是说啊,有没有一种可能,就是要不你就都学 Compose
,要不你就都不学。。。。
还有就是文字,来看下 Glance
中的 Text
吧:
@Composable
fun Text(text: String,modifier: GlanceModifier = GlanceModifier,style: TextStyle = defaultTextStyle,maxLines: Int = Int.MAX_VALUE,
)
虽然 Compose
中的 Text
接收的也是一个 String
,但是人家有 stringResource
函数啊,你呢。。。忘写了么?
算了,自己写一个吧:
@Composable
fun stringResource(@StringRes id: Int): String {return LocalContext.current.getString(id)
}
这个函数我个人觉得可以放到 Glance
中。。。。
总结
今天所讲的 Glance
其实也是基于 Compose
的,由此可见,Google 现在对 Compose
发力非常足,如果大家想系统地学习 Compose
的话,可以购买我的新书《Jetpack Compose:Android全新UI编程》进行阅读,里面有完整的 Compose 框架供大家学习。
京东购买地址
当当购买地址
本文中的代码地址:玩安卓 Github:https://github.com/zhujiang521/PlayAndroid
如果对你有帮助的话,别忘记点个 Star,感激不尽,大家如果有疑问的话可以在评论区提出来。
相关文章:
稳定的 Glance 来了,安卓小部件有救了!
稳定的 Glance 来了,安卓小部件有救了! 稳定版本的 Glance 终于发布了,来一起看看吧,看看这一路的旅程,看看好用么,再看看如何使用! 前世今生 故事发生在两年的一天吧,其实夸张了…...
用友U8与MES系统API接口对接案例分析
企业数字化转型:轻易云数据集成平台助力 U8 ERPMES 系统集成 为什么选择数字化转型? 领导层对企业资源规划(ERP)的深刻理解促使了数字化转型的启动。采用精确的“N5”滚动计划,为供应商提供充分的预期信息,…...
web UI自动化介绍
文章目录 一、web UI自动化介绍1.1 执行UI自动化测试前提1.2 Selenium介绍以及知识点梳理 二、Selenium 学习2.1 基础2.1.1 环境安装与基础使用2.1.2 web浏览器控制2.1.3 常见控件的八大定位方式2.1.3.1 八大定位方式介绍2.1.3.2 NAME、ID定位2.1.3.3 css_selector定位2.1.3.4 …...
小米13Pro/13Ultra刷面具ROOT后激活LSPosed框架微X模块详细教程
喜欢买小米手机,很多是因为小米手机的开放,支持root权限,而ROOT对普通用户来说更多的是刷入DIY模块功能,今天ROM乐园小编就教大家如何使用面具ROOT,实现大家日常情况下非常依赖的微X模块功能,体验微X模块的…...
文盘Rust -- 给程序加个日志 | 京东云技术团队
日志是应用程序的重要组成部分。无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录。在这篇文章中,我们结合log4rs聊聊rust 程序中如何使用日志。 log4rs类似java生态中的log4j,使用方式也很相似 log4rs中的基本概念 log4rs 的功能组件也由 appe…...
C语言深入理解指针(非常详细)(五)
目录 回调函数qsort使用举例qsort函数的模拟实现sizeof和strlen的对比sizeofstrlensizeof和strlen的对比一道关于sizeof的题 回调函数 回调函数就是一个通过函数指针调用的函数 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指…...
[docker]笔记-portainer的安装
1、portainer是一款可视化的容器管理软件,利用portainer可以轻松方便的管理和创建容器。portainer本身是一个容器,完全免费并且具有汉化版。本文介绍portainer的安装和使用。 2、安装好容器并配置好容器环境,可参照https://blog.csdn.net/bl…...
详解TCP/IP的三次握手和四次挥手
文章目录 前言一、TCP/IP协议的三次握手1.1 三次握手流程 二、TCP/IP的四次挥手2.1 四次挥手流程 三、主要字段3.1、标志位(Flags)3.2、序号(sequence number)3.3、确认号(acknowledgement number) 四、状态…...
YOLOv5算法改进(16)— 增加小目标检测层
前言:Hello大家好,我是小哥谈。小目标检测层是指在目标检测任务中用于检测小尺寸目标的特定网络层。由于小目标具有较小的尺寸和低分辨率,它们往往更加难以检测和定位。YOLOv5算法的检测速度与精度较为平衡,但是对于小目标的检测效…...
蓝桥杯官网练习题(图像模糊)
题目描述 小蓝有一张黑白图像,由 nm 个像素组成,其中从上到下共 n 行,每行从左到右 �m 列。每个像素由一个 0 到 255 之间的灰度值表示。 现在,小蓝准备对图像进行模糊操作,操作的方法为: 对…...
使用鳄鱼指标和ADX开立空头的条件,3秒讲清楚
使用鳄鱼指标和ADX开立空头的条件其实很简单,anzo capital昂首资本3秒钟讲清楚。 首先,市场行情需呈水平状态。再者,均线体系开始向上发散,给出明确的信号。最后,ADX确认该信号,要求指数上涨20%以上&#…...
RabbitMQ死信队列与延迟队列
目录 死信队列 死信队列的定义 死信队列的应用场景 死信队列的作用 死信队列架构图 死信队列代码实现 延迟队列 延迟队列的定义 延迟队列的应用场景 延迟队列的作用 延迟队列架构图 延迟队列的代码实现 死信队列 死信队列的定义 死信队列(Dead Letter …...
存储管理呀
世界太吵,别听,别看,别管,别怕,向前走 一. 存储管理 初识硬盘 机械 HDD 固态 SSDSSD的优势 SSD采用电子存储介质进行数据存储和读取的一种技术,拥有极高的存储性能,被认为是存储技术发展的未来…...
学习 BeautifulSoup 库从入门到精通
可以按照以下步骤进行: 1. 安装 BeautifulSoup: 首先,确保你已经安装了 Python。然后可以使用 pip 命令来安装 BeautifulSoup 库。在命令行中输入以下命令: pip install beautifulsoup42. 导入 BeautifulSoup: 在 …...
JavaScript基础知识总结
目录 一、js代码位置 二、变量与数据类型 1、声明变量 2、基本类型(7种基本类型) 1、undefined和null 2、String ⭐ 模板字符串(Template strings) 3、number和bigint ⭐ 4、boolean ⭐ 5、symbol 3、对象类型 1、Fun…...
技术面试与HR面:两者之间的关联与区别
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
【Redis】为什么要学 Redis
文章目录 前言一、Redis 为什么快二、Redis 的特性2.1 将数据储存到内存中2.2 可编程性2.3 可扩展性2.4 持久性2.5 支持集群2.6 高可用性 三、Redis 的应用场景四、不能使用 Redis 的场景 前言 关于为什么要学 Redis 这个问题,一个字就可以回答,那就是&…...
动静态库生成使用
🔥🔥 欢迎来到小林的博客!! 🛰️博客主页:✈️林 子 🛰️博客专栏:✈️ Linux 🛰️社区 :✈️ 进步学堂 🛰…...
LLVM编译安装
LLVM编译安装 #全量下载 git clone https://github.com/llvm/llvm-project.git #只下载最新commit版本 git clone --depth 1 https://github.com/llvm/llvm-project.git#配置 #!/bin/bash set -ex cmake -S llvm -B build -DCMAKE_INSTALL_PREFIX/data0/huozai/software/insta…...
表的内连接和外连接
表的连接是SQL中的一种操作,用于将两个或多个表中的数据按照某个条件进行关联。 内连接 使用内连接将两个表(Table1 和 Table2)进行连接: select * from Table1 inner join Table2 on Table1.id Table2.id;举例: -- 用普通的写法 select…...
三、C#—变量,表达式,运算符(3)
🌻🌻 目录 一、变量1.1 变量1.2 使用变量的步骤1.3 变量的声明1.4 变量的命名规则1.5 变量的初始化1.6 变量初始化的三种方法1.7 变量的作用域1.8 变量使用实例1.9 变量常见错误 二、C#数据类型2.1 数据类型2.2 值类型2.2.1 值类型直接存储值2.2.2 简单类…...
纷享销客受邀出席CDIE2023数字化创新博览会 助力大中型企业增长
2023年,穿越周期,用数字化的力量重塑企业经营与增长的逻辑,再次成为企业数字化技术应用思考的主旋律,以数字经济为主线,数字技术融入产业发展与企业增长为依据,推动中国企业数字化升级。 9月5日,…...
linux下qt交叉编译 tslib 库
在 Linux 下进行 Qt 的交叉编译,并包含 tslib 库,可以按照以下步骤进行操作:1. 准备交叉编译工具链:首先,你需要准备适用于目标平台的交叉编译工具链。这个工具链包括交叉编译器、 2. 链接器和其他相关的工具ÿ…...
2.13 PE结构:实现PE代码段加密
代码加密功能的实现原理,首先通过创建一个新的.hack区段,并对该区段进行初始化,接着我们向此区段内写入一段具有动态解密功能的ShellCode汇编指令集,并将程序入口地址修正为ShellCode地址位置处,当解密功能被运行后则可…...
Rust更换Cargo国内源,镜像了寂寞
换皮不换身 换了国内源,构建时该卡还会卡。因为它所谓的换源,只是更换crates.io“索引”的源,而不是package“内容”的源。换了国内源后,在国内编译时访问 crates.io-index 自然会快很多,可是crates.io-index里面的信…...
【网络安全带你练爬虫-100练】第23练:文件内容的删除+写入
目录 0x00 前言: 0x02 解决: 0x00 前言: 本篇博文可能会有一点点的超级呆 0x02 解决: 你是不是也会想: 使用pyrhon将指定文件夹位置里面的1.txt中数据全部删除以后---->然后再将参数req_text的值写入到1.txt …...
ESP32蓝牙实例-BLE服务器与客户端通信
BLE服务器与客户端通信 文章目录 BLE服务器与客户端通信1、软件准备2、硬件准备3、代码实现3.1 BLE服务器实现3.2 Android手机测试BLE服务器3.3 ESP32 BLE客户端在本文中,我们将介绍如何使用低功耗蓝牙在两个 ESP32 开发板之间执行 BLE 服务器客户端通信。 换句话说,将介绍如…...
第11章_瑞萨MCU零基础入门系列教程之SysTick
本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id728461040949 配套资料获取:https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总: ht…...
【面试题精讲】如何使用Stream的聚合功能
有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 求和(Sum): List<Integer> numbers Arrays.asList(1, 2, 3, 4, 5);int sum n…...
Linux 中的 chmod 命令及示例
在 Unix 操作系统中,chmod命令用于更改文件的访问模式。该名称是change mode的缩写。其中规定每个文件和目录都有一组权限来控制权限,例如谁可以读取、写入或执行该文件。其中权限分为三类:同时读、写和执行,用“r”、“w”和“x”表示。这些字母组合在一起形成一组用户的特…...
潍坊哪里可以做网站/优化网站标题
Java是什么? 如果要向一无所知的人解释Java是什么还是比较有难度的,是的,它是一门编程语言,但发展到今天,Java一词远程超出了语言的定义,具体来说,Java是一个包括虚拟机环境,与C语言类似&#…...
彩票网站开发注意事情/刚刚发生 北京严重发生
正则表达式用于字符串处理、表单验证等场合,实用高效。现将一些常用的表达式收集于此,以备不时之需。 匹配中文字符的正则表达式: [\u4e00-\u9fa5] 评注:匹配中文还真是个头疼的事,有了这个表达式就好办了 匹配双字节字…...
如何靠做网站赚钱/网络销售怎么找客户
Org-mode 学习: http://emacser.com/org-mode.htm http://starb.me/2009/12/24/emacs-org-mode/...
南京做网站找哪家好/网址提交
前几天,eric已经发表了欧博旗舰RDS1电池数字界面转盘旗舰RDB1电池解码器开箱篇。开箱篇 | 试听欧博旗舰RDS1电池数字界面转盘旗舰RDB1电池解码器,支持roon、Tidal、MQA。本地播放评论篇。开盖照本地播放评论篇 | 试听欧博旗舰RDS1电池数字界面转盘旗舰RD…...
哪个地方旅游网站做的比较好/上海优化公司
ssh-keygen.exe cat ~/.ssh/id_rsa.pub 转载于:https://www.cnblogs.com/chentailin/p/11136191.html...
竞网做的网站怎么样/充电宝关键词优化
子选择器 引用链接:http://blog.csdn.net/u012110719/article/details/41171517 还有一个比较有用的选择器子选择器,即大于符号(>),用于选择指定标签元素的第一代子元素。如右侧代码编辑器中的代码: .food>li{border:1px solid red;} 这…...