Kotlin+MVVM 构建todo App 应用

作者:易科

项目介绍

使用Kotlin+MVVM实现的todo app,功能界面参考微软的Todo软件(只实现了核心功能,部分功能未实现)。

功能模块介绍

  1. 项目模块:添加/删除项目,项目负责管理todo任务
  2. 任务模块:添加/删除任务,标记任务完成情况,标记任务为重要,标记为我的一天,设置提醒时间(发送前台通知),设置过期时间。
  3. 搜索模块:依据任务名称模糊搜索。

效果截图

技术栈

  • Kotlin
  • ViewModel + LiveData + Room + AlarmManager + WorkerManager
  • navigation + DiaLog + 前台通知

功能设计与实现

1. 项目模块设计实现

在项目模块中,分为固定模块和自定义模块。其中固定模块分为以下几个模块:

  • 我的一天:可以查看当天需要完成的任务列表;
  • 重要:可以查看标记为重要的任务列表;
  • 计划内:( 未实现
  • 已分配:(未实现
  • 任务:可以查看未完成的所有任务列表;

而自定义项目模块是提供给用户来将任务归类到项目的功能。

项目模块主要显示:icon + 项目名称 + 包含的任务列表数量

(没啥好说的,简单的recyclerView实现即可

2. 任务列表页面的动态更新

点击项目进入项目后可创建任务,任务是由Recyclerview生成的,由于想要在任务添加/删除时出现列表滑动的效果,所以任务的apater实现了ListAdapter。

class TasksAdapter(val viewModel: TasksViewModel): ListAdapter<Task, TasksAdapter.ViewHolder>(DIFF_CALLBACK) {//...}

2.1 任务列表页的操作

另外在搜索页面上也会用到跟任务列表一样的UI,所以将任务列表的UI用fragment实现,方便复用。

2.1.1 任务列表Fragment化
  • TasksFragment.kt
class TasksFragment: BaseFragment() {
​override fun getResourceId() = R.layout.fragment_task_list
​lateinit var taskViewModel : TasksViewModelprivate var projectId = 0Lprivate var projectName = ""private var projectSign : ProjectSign? = null
​private lateinit var adapter: TasksAdapterprivate lateinit var taskRecyclerView: RecyclerView
​private var previousList : List<Task>? = nullprivate lateinit var baseActivity: BaseTaskActivity
​// 搜索参数var searchName = ""var isSearchPage = false
​override fun initView(rootView: View) {// 判断当前fragment的Activty是哪个,方便做特殊操作baseActivity = if (activity is TasksMainActivity) {activity as TasksMainActivity}else {isSearchPage = trueactivity as SearchMainActivity}taskViewModel = ViewModelProvider(baseActivity)[TasksViewModel::class.java]
​projectId = baseActivity.intent.getLongExtra(Constants.PROJECT_ID, 0L)projectName = baseActivity.intent.getStringExtra(Constants.PROJECT_NAME).toString()val serializable = baseActivity.intent.getSerializableExtra(Constants.PROJECT_SIGN)if (serializable != null) {projectSign = serializable as ProjectSign}
​Log.d(Constants.TASK_PAGE_TAG, "projectId = $projectId, projectName= $projectName")refreshList("onCreate")
​adapter = TasksAdapter(taskViewModel)taskRecyclerView = rootView.findViewById(R.id.task_recycle_view)taskRecyclerView.layoutManager = LinearLayoutManager(baseActivity)taskRecyclerView.adapter = adapter
​// 下拉刷新val swipeRefreshTask: SwipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_task)swipeRefreshTask.setOnRefreshListener {refreshList("refresh")swipeRefreshTask.isRefreshing = false   // 取消刷新状态}override fun initEvent(rootView: View) {initClickListener()
​initObserve()}}
2.1.2 任务item操作的封装

对任务的item操作有三个点击事件,分别是标记完成、点击item进入详情页编辑、标记为重要。故构建出TaskItem用来封装item的三个操作

class TaskItem(private val nameText: MaterialTextView?,private val checkTaskBtn : ImageButton,private val setTaskStartBtn: ImageButton,val task: Task) {
​var nameTextEdit: EditText? = nullvar curTaskName : String? = nullfun initItem() {flushItemUI()}
​fun initClickListener(viewModel: TasksViewModel) {// 标记完成按钮checkTaskBtn.setOnClickListener {val upState = if (task.state == TaskState.DONE) {TaskState.DOING} else  {Log.d(Constants.TASK_PAGE_TAG,"播放动画")TaskState.DONE}task.state = upStateviewModel.updateTask(task)flushItemUI()Log.d(Constants.TASK_PAGE_TAG,"update task state id= ${task.id} state for $upState")}// 标记重要按钮setTaskStartBtn.setOnClickListener {val isStart = !FlagHelper.containsFlag(task.flag, Task.IS_START)if (isStart) {task.flag = FlagHelper.addFlag(task.flag, Task.IS_START)}else {task.flag = FlagHelper.removeFlag(task.flag,Task.IS_START)}viewModel.setStart(task.id, task.flag)updateStartUI()Log.d(Constants.TASK_PAGE_TAG,"update task start id= ${task.id} isStart for $isStart")}}
​
​fun flushItemUI() {updateNameUI()updateStartUI()}
​fun updateNameUI() {/*** 从task中获取到名字 或者从输入框获取到名字*/if (curTaskName == null) {curTaskName = task.name}else {curTaskName = if (nameText?.visibility == View.VISIBLE) {nameText.text.toString()} else {nameTextEdit?.text.toString()}}
​/*** checkTaskBtn*/var resId = R.drawable.ic_selectif (task.state == TaskState.DONE) {val spannableString = SpannableString(curTaskName)spannableString.setSpan(StrikethroughSpan(),0,spannableString.length,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)if (nameText?.visibility == View.VISIBLE) {nameText.text = spannableString} else {nameTextEdit?.setText(spannableString) /** 划线的效果 **/}resId = R.drawable.ic_select_check}else {if (nameText?.visibility == View.VISIBLE) {nameText.text = curTaskName} else {nameTextEdit?.setText(curTaskName)}}checkTaskBtn.setImageResource(resId)}
​fun updateStartUI() {var startResId = R.drawable.ic_shoucangif (FlagHelper.containsFlag(task.flag, Task.IS_START)) {startResId = R.drawable.ic_shoucang_check}setTaskStartBtn.setImageResource(startResId)}
}

3. 任务详情页的编辑操作

3.1 任务的状态设计

任务详情页主要的操作包括:任务item的操作(标记完成、修改任务名、标记为重要),标记为我的一天,任务提醒,添加截止日期,重复(未实现),添加附件(未实现) 等。

任务item的操作同样封装在上面的 TaskItem中,直接调用即可,无需再实现

这里的有几个标记功能,标记为我的一天,标记为重要。因为不想新增一个字段来表示0或1的存储,这里将这两个属性为归为同一个字段flag,用int存储,用不同的位来表示对应字段的值,如:

  • 当字段值为 1 时,说明标记为重要的;(01)
  • 当字段值为 2 时,说明标记为我的一天;(10)
  • 当字段值为 3 时,说明标记为重要的且是我的一天;(11)
/*** Flag 常量*/
companion object {/** 设为重要的 **/const val IS_START = 1/** 设为我的一天 **/const val IN_ONE_DAY = 2
}

其实就是位运算的一种,用二进制的位来表示不同状态下的真或假。判断也就比较简单了,通过与,或运算即可:

object FlagHelper {
​/*** 添加标识*/fun addFlag(flag: Int, newFlag : Int) : Int {return flag.or(newFlag)}
​/*** 移除标识*/fun removeFlag(flag: Int, newFlag: Int) : Int {return flag.and(newFlag.inv())}
​/*** 判断是否包含该标识*/fun containsFlag(flag: Int, checkFlag: Int) : Boolean {return flag.and(checkFlag) == checkFlag}
}

接下来只要用 FlagHelper.containsFlag(task.flag, Task.IN_ONE_DAY) 来判断该任务是否该状态,添加/删除也是同理调用该帮助类即可。

3.2 提醒功能的设计

3.2.1 UI设计

提醒功能的UI是这样的,日期和时间有对应的DiaLog实现,也有Picker实现,那么只需要通过Button点击切换两个UI即可实现。

我这里采用DiaLogFragment实现的,通过自定义的DtPickerDiaLogFragment 来管理Button与两个时间Picker组件。遇到的难点在于在两个时间Picker组件选择好时间后,该怎么跟DiaLogFrament做通信呢。这里使用了EventBus来做DiaLogFrament和两个时间picker组件对应的fragment做通信。实现如下:

  • 日期选择器:DatePickerFragment.kt
class DatePickerFragment : BaseFragment() {
​private lateinit var dp : DatePickerlateinit var localDate : LocalDate
​override fun getResourceId() = R.layout.fragment_datepicker
​override fun initView(rootView: View) {dp = rootView.findViewById(R.id.datePicker)}
​override fun initEvent(rootView: View) {/*** The month that was set (0-11) for compatibility with java.util.Calendar.*/dp.setOnDateChangedListener { view, year, monthOfYear, dayOfMonth ->localDate = LocalDate.of(year, monthOfYear + 1, dayOfMonth)EventBus.getDefault().post(DateTimeMessage(localDate))findNavController().navigate(R.id.switchTime)}}
​
​
}
  • 时间选择器:TimePickerFragment.kt
class TimePickerFragment : BaseFragment() {override fun getResourceId() = R.layout.fragment_timepicker
​private lateinit var tp : TimePickerprivate lateinit var localTime: LocalTime
​override fun initView(rootView: View) {tp = rootView.findViewById(R.id.timePicker)}
​override fun initEvent(rootView: View) {tp.setOnTimeChangedListener { view, hourOfDay, minute ->localTime = LocalTime.of(hourOfDay,minute)EventBus.getDefault().post(DateTimeMessage(localTime))}}
}
  • 日期和时间的选择器弹窗:DtPickerDiaLogFragment.kt
class DtPickerDiaLogFragment(private val dateTimeClick: DateTimeClickListener) : DialogFragment() {
​private var chooseDate: LocalDate? = nullprivate var chooseTime: LocalTime? = nullprivate var chooseDateTime : LocalDateTime? = null
​
​override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
​Log.d("DtPickerDiaLogFragment","onCreateView")val curView = inflater.inflate(R.layout.dialog_datetime_picker, null)
​val navHostFragment : FragmentContainerView = curView.findViewById(R.id.fragment_container_view)val switchCalendar : Button = curView.findViewById(R.id.switchCalendar)val switchTime : Button = curView.findViewById(R.id.switchTime)val cancelDialog : TextView = curView.findViewById(R.id.cancelDialog)val saveDateTime : TextView = curView.findViewById(R.id.saveDateTime)
​switchCalendar.setOnClickListener {switchCalendar.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.light_blue))switchTime.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.gray))navHostFragment.findNavController().navigate(R.id.switchCalendar)}
​switchTime.setOnClickListener {switchCalendar.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.gray))switchTime.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.light_blue))navHostFragment.findNavController().navigate(R.id.switchTime)}
​cancelDialog.setOnClickListener {dialog?.dismiss()}
​saveDateTime.setOnClickListener {chooseDateTime = if (chooseDate == null && chooseTime == null) {LocalDateTime.now()}else if (chooseDate == null) {LocalDateTime.of(LocalDate.now(), chooseTime)}else if (chooseTime == null) {LocalDateTime.of(chooseDate, LocalTime.now())} else {LocalDateTime.of(chooseDate, chooseTime)}Log.d("","选中的时间为:$chooseDateTime")dateTimeClick.onSaveDateTimeClick(chooseDateTime!!)dialog?.dismiss()}
​// 注册EventBus.getDefault().register(this)return curView}
​override fun onActivityCreated(savedInstanceState: Bundle?) {super.onActivityCreated(savedInstanceState)initWindow()}
​override fun onDestroy() {EventBus.getDefault().unregister(this)  // 注销super.onDestroy()}
​fun initWindow() {val window = dialog?.windowwindow?.attributes?.width = 800 // 单位pxwindow?.attributes?.height = 1450 // 单位pxwindow?.attributes?.gravity = Gravity.CENTER    // 居中}
​fun getChooseTime() = chooseDateTime
​
​@Subscribe(threadMode = ThreadMode.MAIN)fun receiveDateTime(dateTimeMessage: DateTimeMessage) {if (dateTimeMessage.localDate != null) {chooseDate = dateTimeMessage.localDate!!}if (dateTimeMessage.localTime != null) {chooseTime = dateTimeMessage.localTime!!}Log.d("","接收到event消息,chooseDate=$chooseDate,chooseTime=$chooseTime")}
​
}
3.2.2 提醒功能设计

提醒功能是采用WorkerManager + AlarmManager实现的,实现流程如下:、

  1. 当选择好时间保存后就会提交一次性的后台任务;
  2. Worker后台接收到任务后,检查提醒时间,没过期的话检查当前任务是否已经存在闹钟,有的话则取消;
  3. 使用AlarmManager设置闹钟,保存当前任务和闹钟id的关系,方便下一次设置时取消该闹钟;
  4. 保存下一个闹钟的提醒id,防止PendingIntent 的requestCode重复导致任务提醒失败。

实现如下:

class RemindWorker(context: Context, params: WorkerParameters) : Worker(context, params)  {
​companion object {val Tag = "RemindWorker"}
​private lateinit var alarmManager: AlarmManager
​@RequiresApi(Build.VERSION_CODES.S)override fun doWork(): Result {val taskByte = inputData.getByteArray(Constants.TASK_BYTE)val task = taskByte?.toObject() as Taskval projectName = inputData.getString(Constants.PROJECT_NAME)alarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
​Log.d(Tag,"需要提醒的任务为:task=$task, projectName=$projectName")if (LocalDateTime.now().isBefore(task.remindTime)) { // 未执行,发起广播alarmTask(task, projectName)}
​return Result.success()}
​private fun alarmTask(task: Task, projectName: String?) {val bundle = Bundle()bundle.putByteArray(Constants.TASK, task.toByteArray())bundle.putString(Constants.PROJECT_NAME, projectName)val intent = Intent(applicationContext, RemindAlarmReceiver::class.java).apply {putExtras(bundle)}val oldAlarmId = Repository.getInteger4Broad(task.id.toString())   // 找到旧的请求id,如果有值的话说明需要重设,取消旧闹钟var pi : PendingIntentif (oldAlarmId != 0 && LocalDateTime.now().isAfter(task.remindTime)) {// 取消闹钟,重设pi = PendingIntent.getBroadcast(applicationContext, oldAlarmId, intent, 0)alarmManager.cancel(pi)}var alarmId = Repository.getInteger4Alarm(Constants.ALARM_ID, 0)pi = PendingIntent.getBroadcast(applicationContext, alarmId, intent, 0)val triggerAtMillis = task.remindTime!!.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis , pi)
​Repository.setInteger4Broad(task.id.toString(), alarmId)Repository.setInteger4Alarm(Constants.ALARM_ID, ++alarmId)Log.d(Tag,"闹钟设置成功;taskName=${task.name};remindTime=${task.remindTime};;now=${System.currentTimeMillis()}")}
​
}

闹钟时间到了后,通过broadcast广播。所以还需要用Recevier去接收,接收到广播后。发起前台通知即实现了任务提醒功能。

class RemindAlarmReceiver: BroadcastReceiver() {
​private val channelId = "remind"private val channelName = "任务提醒"
​override fun onReceive(context: Context, intent: Intent) {Log.d("RemindAlarmReceiver", "请求收到了.")val taskByteArray = intent.getByteArrayExtra(Constants.TASK)val task = taskByteArray?.toObject() as Taskval projectName = intent.getStringExtra(Constants.PROJECT_NAME)Log.d("RemindAlarmReceiver","接收到任务 task=$task,projectName=$projectName")val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager// 创建渠道// Android8.0 以上才有下面的APIval channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)manager.createNotificationChannel(channel)val intent = Intent(context, EditTaskActivity::class.java).apply {putExtra(Constants.TASK, task)putExtra(Constants.PROJECT_NAME, projectName)setPackage("com.example.mytodo")}val alarmId = Repository.getAndSet4Alarm(Constants.ALARM_ID, 0)val pi = PendingIntent.getActivity(context, alarmId, intent, PendingIntent.FLAG_IMMUTABLE)val notification = NotificationCompat.Builder(context, channelId)   // 必须传入已经创建好的渠道ID.setContentTitle("提醒").setContentText(task.name).setSmallIcon(R.drawable.todo).setColor(Color.BLUE).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL).setContentIntent(pi)       // 设置内容点击的Intent.setAutoCancel(true)        // 点击后自动关闭.build()
​manager.notify(1, notification)Log.d("RemindAlarmReceiver", "通知发送成功;task=$task")}
}

搜索功能

搜索功能的UI跟任务列表的UI大体相似,只是多了一个搜索栏。前面将任务列表fragment化了,直接复用。

实现简单就不说了。

最后

这是本人在学完Android后搞得第一个练手项目,其中很多编码方式不一定规范,有些功能也未实现(如帐号管理,任务云同步,项目可移动,任务可重新分组,任务可细分步骤等功能)。

想说下Kotlin真的好用hh(比起Java),比如扩展函数的特性。在这个app开发中,有个功能是当编辑框出现的时候,要自动弹出输入法。这里直接用扩展函数把View扩展,EditText这个组件就能直接用了,真方便。

/*** 显示软键盘* postDelayed:避免界面还没绘制完毕就请求焦点导致不弹出键盘*/
fun View.showSoftInput(flags: Int = InputMethodManager.SHOW_IMPLICIT) {postDelayed({requestFocus()val inManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManagerinManager.showSoftInput(this, flags)},100)
}
​
/*** 隐藏软键盘*/
fun View.hideSoftInputFromWindow(flags: Int = 0) {val inManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManagerinManager.hideSoftInputFromWindow(this.windowToken, flags)
}
​
// 直接调用,看起来真优雅
editTaskName.showSoftInput()

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化

时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化 目录 时序分解 | MATLAB实现MVMD多元变分模态分解信号分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MVMD多元变分模态分解 可直接替换Excel运行包含频谱相关系数图 Matlab语言 1.算法新颖小众&…

Matlab之数组字符串函数汇总

一、前言 在MATLAB中&#xff0c;数组字符串是指由字符组成的一维数组。字符串可以包含字母、数字、标点符号和空格等字符。MATLAB提供了一些函数和操作符来创建、访问和操作字符串数组。 二、字符串数组具体怎么使用&#xff1f; 1、使用单引号或双引号括起来的字符序列 例…

数据结构 - 双向链表

文章目录 目录 文章目录 前言 一、什么是双向链表? 双向链表有什么优势? 二、双向链表的设计和实现 1.设计思想 尾增 : 在链表的末尾添加新的元素 头插 : 在链表头部插入节点 删除 : 根据val的值删除节点 查找 : 根据索引的值查找并返回节点 总结 前言 大家好,今天给…

Linux TCP和UDP协议

目录 TCP协议TCP协议的面向连接1.三次握手2.四次挥手 TCP协议的可靠性1.TCP状态转移——TIME_WAIT 状态TIME_WAIT 状态存在的意义&#xff1a;&#xff08;1&#xff09;可靠的终止TCP连接。&#xff08;2&#xff09;让迟来的TCP报文有足够的时间被识别并被丢弃。 2.应答确认、…

Google Chrome 浏览器以全屏模式打开

目录 前言以全屏模式打开禁止弹出无法更新的提示窗禁止翻译网页Chrome设置禁止翻译网页可能1可能2可能3 网页添加指令禁止Chrome翻译网页 禁用脚本气泡浏览器解决办法html解决办法方法1&#xff1a;鼠标滑过超链接时&#xff0c;使状态栏不出现超链接方法2&#xff1a;方法3&am…

微服务06-Dockerfile自定义镜像+DockerCompose部署多个镜像

常见的镜像在DockerHub能找到&#xff0c;但是我们自己写项目得自己构造镜像 1 镜像结构 作用&#xff1a;提高复用性&#xff0c;当应用需要更新时&#xff0c;不再是整个系统重装进行更新 &#xff0c;而是对需要更新的部分进行更新&#xff0c;其他地方不动——>这就是分…

如何写出一篇爆款产品文案,从目标受众到市场分析!

一篇爆款产品文案意味着什么?意味着更强的种草能力&#xff0c;更高的销售转化和更强的品牌传播力。今天来分享下如何写出一篇爆款产品文案&#xff0c;从目标受众到市场分析&#xff01; 一、产品文案策略 一篇爆款产品文案&#xff0c;并不是一时兴起造就的。在撰写之前&…

解决报错之org.aspectj.lang不存在

一、IDEA在使用时&#xff0c;可能会遇到maven依赖包明明存在&#xff0c;但是build或者启动时&#xff0c;报找不存在。 解决办法&#xff1a;第一时间检查Setting->Maven-Runner红圈中的√有没有选上。 二、有时候&#xff0c;明明依赖包存在&#xff0c;但是Maven页签中…

录音新手必备,2款音频录制软件推荐!

“有好用的音频录制软件推荐吗&#xff1f;最近需要录制歌曲去参加一个线上的歌手大赛&#xff0c;只需要上传自己录制的音乐就可以了&#xff0c;但是录音软件的质量太差了&#xff0c;就想问问有没有好用的音频录制软件&#xff0c;谢谢。” 随着数字化时代的到来&#xff0…

MATLAB实现函数拟合

目录 一.理论知识 1.拟合与插值的区别 2.几何意义 3.误差分析 二.操作实现 1.数据准备 2.使用cftool——拟合工具箱 三.函数拟合典例 四.代码扩展 一.理论知识 1.拟合与插值的区别 通俗的说&#xff0c;插值的本质是根据现有离散点的信息创建出更多的离散点&#xf…

利用Scrum敏捷工具管理敏捷产品迭代Sprint Backlog

​什么是Sprint Backlog&#xff1f; Sprint Backlog是Scrum的主要工件之一。在Scrum中&#xff0c;团队按照迭代的方式工作&#xff0c;每个迭代称为一个Sprint。在Sprint开始之前&#xff0c;PO会准备好产品Backlog&#xff0c;准备好的产品Backlog应该是经过梳理、估算和优…

程序依赖相关知识点(PDG,SDG)

什么叫可达性 变量v的定义d&#xff1a;对变量v的赋值语句称为变量v的定义 变量v的使用&#xff1a;在某个表达式中引用变量v的值 当变量v被再次赋值时&#xff0c;上一次赋值对变量v的定义d就被kill掉了 如果定义d到点p之间存在一条路径&#xff0c;且在路径中定义d没有被…

RabbitMQ管控台使用

安装成功RabbitMQ后&#xff0c;进入到管理控制台界面 拷贝配置文件到指定目录当中然后重启RabbitMQ。

ES-OAS-ERP-电子政务-企业信息化

ES-OAS-ERP-电子政务-企业信息化 专家系统ES办公自动化系统OAS企业资源规划ERP典型的信息系统架构模型 专家系统ES 模拟人类专家&#xff0c;解决结构化&#xff0c;半结构化问题 数据级&#xff0c;知识库级&#xff0c;控制级 专家系统的特点就是和人的区别 启发性知识&#…

RHCSA-VM-Linux基础配置命令

1.代码命令 1.查看本机IP地址&#xff1a; ip addr 或者 ip a [foxbogon ~]$ ip addre [foxbogon ~]$ ip a 1&#xff1a;<Loopback,U,LOWER-UP> 为环回2网卡 2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP>为虚拟机自身网卡 2.测试网络联通性&#xff1a; [f…

HTTPS加密协议详解:TLS/SSL握手过程

1、握手与密钥协商过程 基于RSA握手和密钥交换的客户端验证服务器为示例详解TLS/SSL握手过程。 (1).client_hello 客户端发起请求&#xff0c;以明文传输请求信息&#xff0c;包含版本信息&#xff0c;加密套件候选列表&#xff0c;压缩算法候选列表&#xff0c;随机数&#…

Kotlin 协程 - 生命周期 Job

一、概念 对于每一个由协程构建器开启的协程&#xff0c;都会返回一个 Job 实例用来管理协程的生命周期。launch()直接返回 Job实现&#xff0c;async() 返回的 Deferred 实现了 Job接口。 Job public fun start(): Boolean public fun cancel(cause: CancellationException? …

引入Bootstrap的CSS样式后,<h>标签、<p>标签等HTML自带的标签被覆写没有?答:覆写了。

引入Bootstrap的CSS样式后,标签、 标签等HTML自带的标签被覆写没有&#xff1f;答&#xff1a;覆写了。 为什么这么说&#xff1f;证据呢&#xff1f; 写一个实例&#xff0c;然后调试模式看一下不就得了。 先看没有引入引入Bootstrap的CSS样式情况。 代码如下&#xff1a; …

一些芯片设计的冷知识

关于芯片物理版图 芯片物理版图是一种用来描述集成电路内部结构和连接的图形文件&#xff0c;它是芯片设计的最终结果&#xff0c;也是芯片制造的依据。芯片物理版图中包含了各种工艺层的信息&#xff0c;例如多晶硅层、金属层、活性区层、接触层等&#xff0c;每一层都有不同…

什么是JavaScript中的严格模式(strict mode)?应用场景是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 严格模式&#xff08;Strict Mode&#xff09;&#xff1a;⭐ 使用场景⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&…