Jetpack Compose中使用Notification

news2024/11/19 5:39:45

在这里插入图片描述
发送通知相关的主要有两个关键的类 NotificationCompat.BuilderNotificationManagerCompat
为方便使用,首先定义一个扩展工具类来管理通知

const val MAIN_CHANNEL_ID = "MainChannel ID"
const val MAIN_CHANNEL = "MainChannel"

fun Context.buildNotification(
    id: Int,
    title: String,
    message: String,
    action: String? = null,
    actionMessage: String? = null,
    visibility: Int = VISIBILITY_PUBLIC,
    activityIntent: Intent? = null,
    isDeepLink: Boolean = false
): Notification {
    val notification = Notification(id, title, message, action, actionMessage,
        visibility, activityIntent,isDeepLink)
    notification.builder = notification.builder(this)
    notification.manager = getNotificationManager()
   return notification
}

data class Notification(
    val id: Int,
    var title: String,
    var message: String,
    var action: String? = null,
    var actionMessage: String? = null,
    var visibility: Int = VISIBILITY_PUBLIC,
    var activityIntent: Intent? = null,
    val isDeepLink: Boolean = false,
    var builder: Builder? = null,
    var manager: NotificationManagerCompat? = null
)

fun Notification.show(): Notification {
    builder?.let { manager?.notify(id, it.build()) }
    return this
}

fun Notification.update(
    context: Context,
    titleNew: String? = null,
    messageNew: String? = null,
    action1: String? = null,
    visibleType: Int = VISIBILITY_PUBLIC
): Notification  {
    titleNew?.let { title = titleNew }
    messageNew?.let { message = messageNew}
    action1?.let { action = action1  }
    if (visibleType != visibility) visibility = visibleType
    manager?.notify(id, builder(context).build())
    return this
}

fun Notification.builder(context: Context): Builder {
    val builder = Builder(context, MAIN_CHANNEL_ID)
        .setContentTitle(title)
        .setContentText(message)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(context.bitmap(R.drawable.ic_head3,200, 200))
        .setPriority(PRIORITY_DEFAULT)
        .setVisibility(visibility)
        .setAutoCancel(true)
    if (visibility == VISIBILITY_PRIVATE) {
        builder.setPublicVersion(
            Builder(context, MAIN_CHANNEL_ID)
                .setContentTitle("收到一条新的消息")
                .setContentText("请解锁屏幕后查看!")
                .build()
        )
    }
    val flg = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
    action?.let {
        val intent = Intent(context, NotificationReceiver::class.java).apply {
            putExtra(KEY_MESSAGE, actionMessage)
            putExtra(KEY_NOTIFICATION_ID, id)
        }
        PendingIntent.getBroadcast(context, 0, intent, flg)
    }?.let { builder.addAction(0, action, it) }

    if (isDeepLink) {
        activityIntent?.let {
            TaskStackBuilder.create(context).run {
                addNextIntentWithParentStack(it)
                getPendingIntent(1, flg)
            }
        }?.let { builder.setContentIntent(it) }
    } else {
        activityIntent?.let { PendingIntent.getActivity(context, 1, it, flg) }
            ?.let { builder.setContentIntent(it) }
    }
    return builder
}

fun Context.getNotificationManager(): NotificationManagerCompat {
    val notificationManager = NotificationManagerCompat.from(applicationContext)
    // API 26 Android 8.0开始必须为每个通知指定一个channel才会显示
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(MAIN_CHANNEL_ID, MAIN_CHANNEL,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        notificationManager.createNotificationChannel(channel)
    }
    return notificationManager
}

fun Context.cancelNotification(id: Int) = getNotificationManager().cancel(id)

然后定义一个ViewModel,在其中负责具体的发送通知业务


class NotificationTestViewModel: ViewModel() {
    var notification: Notification? = null

    private fun buildNotification(context: Context, title: String, message: String) {
        val clickIntent = Intent(context, NotificationTestActivity::class.java)
        notification = context.buildNotification(
            id = 1,
            title = title,
            message = message,
            action = "Action按钮",
            actionMessage = "点击了按钮",
            visibility = VISIBILITY_PUBLIC,
            activityIntent = clickIntent,
        )
    }

    fun showNotification(context: Context, title: String, message: String) {
        buildNotification(context, title, message)
        notification?.show()
    }

    fun updateNotification(context: Context, titleNew: String, messageNew: String) {
        notification?.update(context, titleNew, messageNew)
    }
}

最后在Composable中调用viewmodel的方法发送通知:

@Composable
fun NotificationTest(viewModel: NotificationTestViewModel = viewModel()) {
    val context = LocalContext.current
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = {
            viewModel.showNotification(context,"外卖提醒", "您好,您的外卖到了!")
        }) {
            Text(text = "创建一个新通知")
        }
        Button(onClick = {
            viewModel.updateNotification(context,"订单提醒", "您有一条新的外卖订单,请及时接单!")
        }) {
            Text(text = "更新通知")
        }
    }
}

更新通知

发送通知后通过NotificationManagerCompat.notify(id, notification)对相同的通知id进行再次调用,就会更新通知中对应的属性
在这里插入图片描述

通知的可见性

通知有三种可见性规则,分别是:

  • NotificationCompat.VISIBILITY_PUBLIC: 默认所有屏幕可见
  • NotificationCompat.VISIBILITY_SECRET: 锁屏下不可见
  • NotificationCompat.VISIBILITY_PRIVATE:锁屏可见,但是隐藏敏感或私人信息

VISIBILITY_PUBLIC的效果:
在这里插入图片描述
VISIBILITY_SECRET的效果:
在这里插入图片描述
VISIBILITY_PRIVATE的效果:
在这里插入图片描述
要测试 VISIBILITY_PRIVATE的效果需要先将系统设置中通知管理里的锁屏显示敏感信息的选项关闭:
在这里插入图片描述
然后在创建 NotificationCompat.Builder 时,需要通过 setPublicVersion 设置在锁屏界面时展示的信息:

  if (visibility == VISIBILITY_PRIVATE) {
        builder.setPublicVersion(
            Builder(context, MAIN_CHANNEL_ID)
                .setContentTitle("收到一条新的消息")
                .setContentText("请解锁屏幕后查看!")
                .build()
        )
    }

通知中的Action

通知中最多可以添加三个Action按钮,点击时,可以执行对应的pendingIntent,比如在前面的代码中,构建 NotificationCompat.Builder 时判断如果 Action 按钮的文字不为空,就为builder设置一个action选项:

    val flg = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
    action?.let {
        val intent = Intent(context, NotificationReceiver::class.java).apply {
            putExtra(KEY_MESSAGE, actionMessage)
            putExtra(KEY_NOTIFICATION_ID, id)
        }
        PendingIntent.getBroadcast(context, 0, intent, flg)
    }?.let { builder.addAction(0, action, it) }

这里使用PendingIntent.getBroadcast构建了一个用于触发广播的PendingIntent,builder.addAction的第一个参数还可以设置一个图标的资源id。

当用户点击通知中的action按钮时,就会发送广播,然后在NotificationReceiver中从intent查询信息进行显示即可:

const val KEY_MESSAGE = "Notification_Message"
const val KEY_NOTIFICATION_ID = "Notification_Id"

class NotificationReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.run {
            val msg = getStringExtra(KEY_MESSAGE)
            msg?.let { context?.showToast(msg) }
            val id = getIntExtra(KEY_NOTIFICATION_ID, 0)
            context?.cancelNotification(id) // 根据需求决定要不要取消
        }
    }
}

在这里插入图片描述
这里收到通知后在广播接收器中弹出一个toast提示同时取消了通知,实际业务中可以根据需求决定要不要关闭通知(可能是需要后台常驻的就不要取消)

点击通知跳转具体页面

很简单,就是在构建 NotificationCompat.Builder 时,设置一个Activity类型的PendingIntent即可

val activityIntent= Intent(context, NotificationTestActivity::class.java)
...
activityIntent?.let { PendingIntent.getActivity(context, 1, it, flg) }
            ?.let { builder.setContentIntent(it) }

点击通知后就会跳转的具体的Activity页面

触发DeepLink页面

可以配合Compose导航路由的DeepLink,在点击通知时,跳转到某个导航图中的某个子路由页面中
首先需要配置Compose导航路由的DeepLink,这里使用开源库 compose-destinations 进行配置路由:

// NotificationTest.kt
@Composable
fun NotificationNavHostScreen() {
    Box(modifier = Modifier.fillMaxSize()) {
        DestinationsNavHost(navGraph = NavGraphs.root)  
    }
}

@RootNavGraph(start = true)
@Destination
@Composable
fun NotificationTest(navigator: DestinationsNavigator,
                     viewModel: NotificationTestViewModel = viewModel()) {
    ...
}
// DetailScreen.kt
const val APP_URI ="http://my.app.com/detail/"

@Destination(deepLinks = [DeepLink(uriPattern = "$APP_URI/{message}")])
@Composable
fun DetailScreen(message: String) {
 ...
}

在Activity中使用DestinationsNavHost作为根布局显示

class NotificationMainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeApplicationTheme {
                Surface(Modifier.fillMaxSize(),
                    color=MaterialTheme.colorScheme.background) {
                    NotificationNavHostScreen()
                }
            }
        }
    }
}

然后在构建 NotificationCompat.Builder 时,通过TaskStackBuilder来构建pendingIntent

	if (isDeepLink) {
        activityIntent?.let {
            TaskStackBuilder.create(context).run {
                addNextIntentWithParentStack(it)
                getPendingIntent(1, flg)
            }
        }?.let { builder.setContentIntent(it) }
    }

NotificationTestViewModel中添加一个专门用于构建Deeplink的方法:

    private fun buildDeepLinkNotification(context: Context, title: String, message: String) {
        val clickIntentDeepLink = Intent(
            Intent.ACTION_VIEW,
            "$APP_URI/message from NotificationTest".toUri(),
            context, NotificationMainActivity::class.java
        )
        notification = context.buildNotification(
            id = 1,
            title = title,
            message = message,
            action = "Action按钮",
            actionMessage = "点击了按钮",
            visibility = VISIBILITY_PUBLIC,
            activityIntent = clickIntentDeepLink,
            isDeepLink = true
        )
    }

    fun showNotification(context: Context, title: String, message: String) {
        buildDeepLinkNotification(context, title, message)
        notification?.show()
    }

这样就可以了,Manifest中无需为Activity标签配置任何额外的属性
效果如下
在这里插入图片描述

在通知中显示进度条

首先在构建NotificationManagerCompat时,需要再添加一个channel2, 并且将其importance参数设置为NotificationManager.IMPORTANCE_LOW(这是因为我们不想在每次更新进度条时都会发出系统提示音)

fun Context.getNotificationManager(): NotificationManagerCompat {
    val notificationManager = NotificationManagerCompat.from(applicationContext)
    // API 26 Android 8.0开始必须为每个通知指定一个channel才会显示
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(MAIN_CHANNEL_ID, MAIN_CHANNEL,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        val channel2 = NotificationChannel(SECOND_CHANNEL_ID, SECOND_CHANNEL,
            NotificationManager.IMPORTANCE_LOW
        )
        notificationManager.createNotificationChannel(channel)
        notificationManager.createNotificationChannel(channel2)
    }
    return notificationManager
}

然后也需要单独为这个channel创建Builder:

fun Notification.secondChannelBuilder(context: Context): Builder {
    return Builder(context, SECOND_CHANNEL_ID) // 这里要使用SECOND_CHANNEL_ID
        .setContentTitle("下载中")
        .setContentText("${progress}/${max}")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setProgress(max, progress, false)
        .setPriority(PRIORITY_LOW) // 设置低优先级
        .setOngoing(true) // 不允许用户取消
}
fun Notification.updateProgress(
    context: Context,
    progress: Int,
    max: Int,
): Notification  {
    this.progress = progress
    this.max = max
    manager?.notify(id, secondChannelBuilder(context).build())
    return this
}
fun Notification.showFinished(context: Context): Notification  {
    this.title = "下载完成!"
    this.message = ""
    // 下载完后使用MAIN_CHANNEL的builder构建更新通知
    manager?.notify(id, builder(context).apply {
        setContentIntent(null)
        clearActions()
        setProgress(0, 0, false)
    }.build())
    return this
}

在NotificationTestViewModel中添加一个showProgress方法:

fun showProgress(context: Context) {
        val max = 10
        var progress = 0
        viewModelScope.launch {
            while (progress != max) {
                delay(1000) // 最多允许每秒发送一次通知
                progress++
                notification?.updateProgress(context, progress, max)
            }
            notification?.showFinished(context)
        } 
    }

注意,这里更新通知的进度值使用的是SecondChannel的Builder进行构建的,而在进度完成以后使用MainChannel进行构建完成通知的。

接着在NotificationTest Composable中添加一个按钮,点击调用上面viewModel的showProgress方法:

@Composable
fun NotificationTest(viewModel: NotificationTestViewModel = viewModel()) {
    val context = LocalContext.current
    Column() {
        ...
        Button(onClick = { viewModel.showProgress(context) }) {
            Text(text = "showProgress")
        }
    }
}

在这里插入图片描述
这里也可以在完成时再设置一个PendingIntent点击启动系统的安装页面。

DirectReply

DirectReply是指可以直接在通知栏中回复消息,效果如下:

在这里插入图片描述

同样,先搞一个扩展工具类

data class DirectReply(
    val id: Int,
    var name: String,
    var message: String,
)
const val RESULT_KEY = "Result_Key"

fun DirectReply.directReplyBuilder(context: Context): Builder {
    val replyIntent = Intent(context, NotificationReplyReceiver::class.java).apply {
        putExtra(KEY_NOTIFICATION_ID, id)
    }
    // 这个flg一定要使用FLAG_MUTABLE否则广播中将无法获取到RemoteInput中的值
    val flg = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
    val pendingIntent = PendingIntent.getBroadcast(context, 2, replyIntent, flg)
    val remoteInput = RemoteInput.Builder(RESULT_KEY).setLabel("请输入").build()
    val replayAction = Action.Builder(0, "Reply", pendingIntent)
        .addRemoteInput(remoteInput).build()
    val person = Person.Builder().setName(name).build()
    val notificationStyle = MessagingStyle(person)
        .addMessage(message, System.currentTimeMillis(), person)
    return Builder(context, MAIN_CHANNEL_ID)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setPriority(PRIORITY_DEFAULT)
        .setOnlyAlertOnce(true)
        .setStyle(notificationStyle)
        .addAction(replayAction)
}

fun DirectReply.show(context: Context): DirectReply {
    context.getNotificationManager().notify(id, directReplyBuilder(context).build())
    return this
}

注意上面代码中有一个Person类,这个类是系统自带的,不是自定义的实体类,它是androidx.core.app.Person

然后定义 NotificationReplyReceiver 在其中接受到消息后,读取RemoteInput输入的文本再发送更新通知即可:


class NotificationReplyReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.run {
            val result = RemoteInput.getResultsFromIntent(this)
            val text = result?.getCharSequence(RESULT_KEY).toString() // 获取通知中输入的文本
            val person = Person.Builder().setName("Me").build()
            val notificationStyle = NotificationCompat.MessagingStyle(person)
                .addMessage(text, System.currentTimeMillis(), person)
            val id = getIntExtra(KEY_NOTIFICATION_ID, 0)
            context?.run {
                val builder = DirectReply(id,"Me", text).directReplyBuilder(this)
                    .setStyle(notificationStyle)
                    //.setContentTitle("Sent!")
                    //.setStyle(null)
                getNotificationManager().notify(id, builder.build())
            }
        }
    }
}

在ViewModel中添加一个方法,来模拟业务调用方法:

    fun showDirectReply(context: Context) {
        DirectReply(3,"Tom", "Hello, guy!").show(context)
    }

最后在Composable中添加一个按钮来测试:

@Composable
fun NotificationTest(viewModel: NotificationTestViewModel = viewModel()) {
    val context = LocalContext.current
    Column() {
        ....
        Button(onClick = { viewModel.showDirectReply(context) }) {
            Text(text = "showDirectReply")
        }
    }
}

这里需要注意的有两点:

  • 创建PendingIntentflg参数在API 31 以上必须使用PendingIntent.FLAG_MUTABLEAPI 31以下可以传0,否则广播中将无法获取到RemoteInput中的值,即 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 这一句代码很重要
  • RemoteInput创建时使用的Key和广播中读取时RemoteInput.getResultsFromIntent(intent)?.getCharSequence(RESULT_KEY) 必须保持一致

另外在广播中接受到消息后必须发送一个更新通知,否则在通知栏中回复后会显示bug(要么一直转圈圈要么会显示对方的名字)

如果是比较注重隐私的app,不想输入文本后在通知栏显示自己回复的内容,则可以在接受广播中进行如下配置:

class NotificationReplyReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        intent?.run {
            val result = RemoteInput.getResultsFromIntent(this)
            val text = result?.getCharSequence(RESULT_KEY).toString() // 获取通知中输入的文本
            // 根据text请求后台接口进行业务处理 
            val id = getIntExtra(KEY_NOTIFICATION_ID, 0)
            context?.run {
                val builder = DirectReply(id,"Me", text).directReplyBuilder(this)
                    .setContentTitle("已发送!") // 这样写通知栏不会显示发送的内容
                    .setStyle(null) // 这样写通知栏不会显示发送的内容
                getNotificationManager().notify(id, builder.build())
            }
        }
    }
}

在这里插入图片描述

使用Hilt为ViewModel注入ApplicationContext

前面的代码中ViewModel里发送和更新通知的方法都要携带一个context对象,这是因为通知相关的API需要使用到context对象,因此在Composable中通过LocalContext.current 获取当前的context对象然后传递给ViewModel调用的方法。

这里其实我们可以简化一下,借助官方提供的Hilt组件库,对ViewModel对象注入一个Application类型的Context对象。

Hilt接入可以参考官方文档:使用 Hilt 实现依赖项注入

在添加依赖之后,将Application、使用到的Activity、ViewModel全部使用Hilt注解进行标注:

@HiltAndroidApp
class MyApp: Application() {
  ...
}
@AndroidEntryPoint
class NotificationMainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeApplicationTheme {
                Surface(Modifier.fillMaxSize(),
                    color=MaterialTheme.colorScheme.background) {
                    NotificationNavHostScreen()
                }
            }
        }
    }
}
@HiltViewModel
class NotificationTestViewModel @Inject constructor(): ViewModel() {

    @SuppressLint("StaticFieldLeak")
    @Inject
    @ApplicationContext
    lateinit var appContext: Context
	...
}

这里会有一个编译器黄色提醒内存泄漏,由于我们注入的context是全局Application对象,因此不会有内存泄漏问题,所以这里可以忽略掉。

最后一步就是将Composable中使用的ViewModel参数也使用Hilt提供的方法替换掉:

@RootNavGraph(start = true)
@Destination
@Composable
fun NotificationTest(navigator: DestinationsNavigator,
                     viewModel: NotificationTestViewModel = hiltViewModel()) {
    ....
}

注意这里之前使用的默认参数值是小写的viewModel() 它是lifecycle-viewmodel-compose提供的一个函数,现在换成hiltViewModel()就可以了。(如果该Composable不使用导航路由那么不需要更换)

一切就绪后,就可以将NotificationTestViewModel中所有使用context的地方将context参数移除,内部使用注入的appContext作为参数传递,NotificationTest在访问NotificationTestViewModel中的方法时也不需要提供context了。


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

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

相关文章

人工智能导论实验——前馈神经网络

实验目的通过实验了解全连接神经网络的结构,应用全连接网络处理分类和回归任务。实验任务1)初级实验:①手写体图像识别实验;②FashionMnist图像分类实验;③汽车里程数预测实验。2)中级实验:①鸢…

数据集划分和交叉验证

机器学习实践中,为防止模型出现过拟合问题,需要预先将数据划分为训练集和测试集,训练集用来建模,训练模型,测试集用来提前测试模型的实际预测能力,这期间就会出现不同的数据集划分和模型评价方法&#xff0…

Linux常用命令——alias命令

Linux命令查询工具 alias 用来设置指令的别名 补充说明 alias命令用来设置指令的别名。我们可以使用该命令可以将一些较长的命令进行简化。使用alias时,用户必须使用单引号将原来的命令引起来,防止特殊字符导致错误。 alias命令的作用只局限于该次登入…

EventLoop与宏任务和微任务

1、JavaScript 是单线程的语言 JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。 单线程执行任务队列的问题:如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。 2、同步…

性能优化系列之『混合式开发:Flutter内核及优势介绍』

文章の目录一、愿景二、技术优势三、底层实现四、选型建议写在最后一、愿景 A portable toolkit for building beautiful experiences wherever you might want to paint pixels on the screen.一个拥有美好构建体验的便携式的工具包,可以让你在屏幕的任何地方绘制…

LeetCode题解 二叉树(十四):669 修改二叉搜索树;108 将有序数组转换为二叉搜索树;538 把二叉搜索树转换为累加树

二叉树 669 修改二叉搜索树 medium 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除&#xf…

数据可视化系列-06数据分析工具QuickBI

文章目录数据可视化系列-06数据分析工具QuickBI一文介绍QuickBIQuick BI 的基本对象快速入门Quick BI产品架构界面说明菜单栏我的看板工作台首页通过仪表板分析数据数据可视化系列-06数据分析工具QuickBI 参考: Quick BI 数据可视化分析平台 QuickBI官方文档 一文…

Jina AI 荣获「2022 稀土掘金引力榜」年度新锐企业 Top 10 称号!

2023 年 1 月 6 日,由稀土掘金技术社区打造的「掘金引力榜」正式公布。凭借在开源技术、产品等领域的积极布局,在社区生态影响力的突出贡献,Jina AI 荣获「掘金引力榜 2022 年度新锐企业 Top10」!「掘金引力榜」是由稀土掘金技术社…

什么是商家转账到零钱

1. 什么是商家转账到零钱商家转账到零钱为商户提供向一个或者同时向多个用户微信零钱转账的能力,商户可免费使用。1.1. 使用场景商户可以使用商家转账用于现金营销、分销返佣、行政补贴、行政奖励、保险理赔、佣金报酬、企业报销、企业补贴、服务款项、采购货款等向…

阿里微服务质量保障系列(二):研发流程知多少

持续坚持原创输出,点击蓝字关注我吧介绍研发流程主要是给大家一个体感,可以直观感受阿里测试工程师从项目的立项到最终发布经历了哪些过程、做了什么工作。需求的产生刚毕业工作那会,认为需求来源于产品,把PD宣讲的产品需求奉为圭…

12.30第一周 星期五SSH

SSH功能:1.远程连接 2.sftp 1. 使用之前首先确认系统中有无 ssh 的软件包 rpm -qa | grep ssh 主程序或执行文件位置:/usr/sbin/ss服务端配置文件: /etc/ssh/sshd_config服务器用客户端的公钥对数据---->加密---->到客户端时&#xf…

TemplateFlow:一个跨物种,多尺度脑部模板和图谱的共享平台

Problem大脑的参考模板在神经影像工作流程中起着核心作用,是报告标准化结果的基础。而模板或者图谱的选择是不同研究中方法学差异的一个相关来源,最近被认为是对神经科学可重复性的一个重要挑战而受到关注。大多数情况下,我们所使用的模板由神…

Linux中Shell与环境变量与配置文件

Shell是什么 Shell是个程序,他会在用户登录系统后系统开启的,他可以通过标准输入来接收命令,然后执行命令,Shell可以执行内部命令和外部命令,内部命令就是Shell程序里面自己的逻辑,外部命令是Shell调用其他…

Linux常用命令——firewall-cmd命令

在线Linux命令查询工具 firewall-cmd Linux上新用的防火墙软件,跟iptables差不多的工具。 补充说明 firewall-cmd 是 firewalld的字符界面管理工具,firewalld是centos7的一大特性,最大的好处有两个:支持动态更新,不…

【SCL】博图scl语言应用项目:装配流水线模拟控制

使用西门子博图SCL语言和factoryio软件来编写应用案例——装配流水线模拟控制项目 文章目录 目录 前言 一、应用:装配流水线模拟控制 1.控制要求 2.完善控制要求和场景 1.布置场景(factoryio) 2.控制要求 3.变量表(I/O分配&…

Cocos Creator Protobuf的js版本使用

一、基础知识参考Protocol Buffers 在游戏中的应用Protobuf语言指南android与PC,C#与Java 利用protobuf 进行无障碍通讯【Socket】1.性能好/效率高现在,俺就来说说Google公司为啥放着好端端的XML不用,非要另起炉灶,重新造轮子。一…

10.0、Linux-磁盘管理简单初体验

10.0、Linux-磁盘管理简单初体验 列出文件系统整体的磁盘使用量 -> df -h ( 检查磁盘空间使用量 ) 查看目录中所有的文件磁盘空间使用情况 -> du -a -h 根目录下每个目录所占空间大小容量 -> du -sm /* Mac 或者想使用 Linux 挂载我们的一些本地磁盘或者文件 挂载&am…

时隔 20 年,这个编程语言再次“称王”!

↓推荐关注↓综合整理:程序员的那些事(id: iProgrammer)近日,全球知名的编程语言流行度排行榜网站 TIOBE 公布了 1 月编程指数信息。前三的编程语言是Python、C 和C,第四为Java,第五是C#。TIOBE 的 2022 年…

MyBatis#1(快速入门与Mapper代理)

一.MyBatis简介1.MyBatis是一款优秀的持久层框架,可用于简化JDBC的开发2.持久层: 负责将数据保存到数据库的那一层代码JavaEE的三层架构: 表现层, 业务层, 持久层3.框架:框架就是一个半成品软件, 是一套可重用的, 通用的, 软件基础代码模型优点: 高效, 规范, 通用, 可扩展二.My…

ESP32设备驱动-BMP180气压温度传感器驱动

BMP180气压温度传感器驱动 1、BMP180介绍 BMP180 是Bosch Sensortec 新推出的数字气压传感器,性能非常高,可用于智能手机,平板电脑和运动设备等高级移动设备。它遵循BMP085并带来许多改进,如较小的尺寸和数字接口的扩展。超低功耗低至3μA,使BMP180成为移动设备节能的领…