App 黑白化技术实践

news2024/11/27 7:42:52

前言

很高兴遇见你~

最近打开各大 App 会发现它们都做了黑白化,如下支付宝的处理:

image-20221207144305534.png

可以看到应用设置了全局灰色调,表达了一种对逝者的哀悼,非常的应景和人性化。作为程序猿,我们来探索一下它从技术角度是怎么实现的。

Github Demo 地址:github.com/sweetying52…

一、App 黑白化实现原理

1.1、修改 Canvas 的 Paint 实现黑白化

首先我们应该知道 Android 中能实现黑白化的手段:

正常情况下,App 页面上的 View 都是通过 Canvas + Paint 画出来的。Canvas 对应画布,Paint 对应画笔,两者结合,就能画出 View。

就好比画家画画,如下图:

ba58-ipmxpvz0314432.jpeg

画一幅画他需要有画布和画笔,通过不同颜色的画笔结合,就画出了一幅惟妙惟肖的画。

到这里你是否受到了一点启发:在 Canvas 上绘制 View 的时候,我们换一支色彩饱和度为 0 的 Paint(画笔),是否就能画出黑白化的 View 呢?

感觉可行,找一下 Paint 相关的 Api ,发现可以对 Paint 进行如下设置:

//新建一支画笔
val paint = Paint()
//通过 ColorMatrix 将饱和度设置为 0
val cm = ColorMatrix()
cm.setSaturation(0f)
//将画笔的色彩饱和度设置为 0
paint.colorFilter = ColorMatrixColorFilter(cm)

上述代码我们就新建了一支色彩饱和度为 0 的 Paint,接下来使用它去进行 View 的绘制,就能达到黑白化的效果。

我们进行一个简单的测试:

1、自定义黑白化 TextView 和 Button,代码如下:

//1、自定义黑白化 TextView
class GrayTextView(context: Context, attrs: AttributeSet): TextView(context,attrs) {

    //色彩饱和度为 0 的 Paint
    private val paint by lazy {
        val p = Paint()
        val cm = ColorMatrix()
        cm.setSaturation(0f)
        p.colorFilter = ColorMatrixColorFilter(cm)
        p
    }

    override fun draw(canvas: Canvas?) {
        canvas?.saveLayer(null,paint, Canvas.ALL_SAVE_FLAG)
        super.draw(canvas)
        canvas?.restore()
    }
}

//2、自定义黑白化 button
class GrayButton(context: Context, attrs: AttributeSet): Button(context,attrs) {

    //色彩饱和度为 0 的 Paint
    private val paint by lazy {
        val p = Paint()
        val cm = ColorMatrix()
        cm.setSaturation(0f)
        p.colorFilter = ColorMatrixColorFilter(cm)
        p
    }

    override fun draw(canvas: Canvas?) {
        canvas?.saveLayer(null,paint, Canvas.ALL_SAVE_FLAG)
        super.draw(canvas)
        canvas?.restore()
    }
}

2、修改 activity.main.xml 的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="20dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light"
        android:textSize="30sp"/>

    <com.dream.appblackandwhite.blackandwhitewidget.GrayTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light"
        android:textSize="30sp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_green_light"
        android:text="erdai666" />

    <com.dream.appblackandwhite.blackandwhitewidget.GrayButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light" />
</LinearLayout>

布局很简单,就是和未黑白化的 TextView 和 Button 做对比

3、运行 app ,效果如下图:

image-20221207172921306.png

这是第一种实现黑白化的方式,接下来介绍另外一种。

1.2、给 View 设置 Paint 实现黑白化

View 有个如下 Api :

image-20221207173755012.png

这个方法是用来开启离屏缓冲的,其接收两个参数。

第一个参数接收一个 Int 类型的值,其有三种情况:

1、LAYER_TYPE_NONE:视图正常渲染,不受屏幕外缓冲区支持。这是默认行为。

2、LAYER_TYPE_HARDWARE:如果应用经过硬件加速,视图在硬件中渲染为硬件纹理。如果应用未经过硬件加速,此层类型的行为方式与 LAYER_TYPE_SOFTWARE 相同。

3、LAYER_TYPE_SOFTWARE:使用软件来渲染视图,绘制到 Bitmap,并顺便关闭硬件加速 。

第二个参数接收一个 Paint,也就是画笔,那么我们就可以对画笔做配置,从而达到黑白化的效果。

有了思路,我们先做一个简单的测试:

1、修改 activity.main.xml 的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="20dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light"
        android:textSize="30sp"/>

    <TextView
        android:id="@+id/tvBlackAndWhite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light"
        android:textSize="30sp"/>

    <Button
        android:id="@+id/btnBlackAndWhite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_green_light"
        android:text="erdai666" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="erdai666"
        android:textColor="@android:color/holo_green_light" />
</LinearLayout>

上述布局很简单,就是给要黑白化的 TextView ,Button 加了一个 id,方便我们在 Activity 里面操作

2、修改 MainActivity:

class MainActivity: AppCompatActivity() /*: BaseActivity()*/ {

    //色彩饱和度为 0 的 Paint
    private val paint by lazy {
        val p = Paint()
        val cm = ColorMatrix()
        cm.setSaturation(0f)
        p.colorFilter = ColorMatrixColorFilter(cm)
        p
    }
		
    //黑白化 TextView
    private val tvBlackAndWhite by lazy {
        findViewById<TextView>(R.id.tvBlackAndWhite)
    }

    //黑白化 Button
    private val btnBlackAndWhite by lazy {
        findViewById<Button>(R.id.btnBlackAndWhite)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

      	//给 View 设置色彩饱和度为 0 的 Paint 实现黑白化
        tvBlackAndWhite.setLayerType(View.LAYER_TYPE_HARDWARE,paint)
        btnBlackAndWhite.setLayerType(View.LAYER_TYPE_HARDWARE,paint)
    }
}

3、运行 app ,效果展示:

image.png

了解了 App 黑白化的原理,接下来我们就来实现 App 真正的黑白化

二、App 黑白化方案实践

上述我们都是对单个 View 进行黑白化处理,那有没有什么办法,让整个页面都变成黑白化的呢?

答:有的,我们可以找到当前 View 树一个合适的父 View,对他进行黑白化设置或者替换为自定义黑白化 View,因为父 View 的 Canvas 和 Paint 是往下分发的,所以它所包含的子 View 都会黑白化处理,这样我们就可以实现 App 黑白化

但是我有一些疑问:哪个父 View 是最合适的呢?具体如何实现呢?

带着上面的疑问,我们看下下面这张图:

每个页面中有一个顶级 View 叫 DecorView,DecorView 中包含一个竖直方向的 LinearLayout,LinearLayout 由两部分组成,第一部分是标题栏,第二部分是内容栏,内容栏是一个 FrameLayout,我们在 Activity 中调用 setContentView 就是将 View 添加到这个 FrameLayout 中。

了解了上面的内容,你心中是否有了实现方案了呢?

1、是不是可以拿到页面对应的 DecorView ,对其进行黑白化设置

2、是不是可以把内容栏(FrameLayout)替换为自定义的 FrameLayout(黑白化的 FrameLayout)

上述两种方案都是可行的

2.1、方案一:对 DecorView 进行黑白化设置

想要拿到一个页面的 DecorView 有很多方式,主要介绍两种:

1、直接在 Activity 中通过 Window 获取 DecorView

window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE,paint)

Tips: 建议创建一个 Activity 的基类 BaseActivity,在 BaseActivity 里面处理,这样所有继承 BaseActivity 的都会生效

那万一我有些 Activity 没有继承呢?那你接着往下看😂

2、在 Application 中注册 registerActivityLifecycleCallbacks 回调,回调中通过 activity 实例同样可以拿到 DecorView

registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                val decorView = activity.window.decorView
                decorView.setLayerType(View.LAYER_TYPE_HARDWARE, paint)
            }

            override fun onActivityStarted(activity: Activity) {
            }

            override fun onActivityResumed(activity: Activity) {
            }

            override fun onActivityPaused(activity: Activity) {
            }

            override fun onActivityStopped(activity: Activity) {
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            }

            override fun onActivityDestroyed(activity: Activity) {
            }
        })

这种方式所有的 Activity 都会生效。

看一眼效果:

image-20221208105821732.png

可以看到,整个页面都黑白化了。

大家可以思考一下这种方案有什么不足之处?后面在讲

2.2、方案二:替换内容栏 FrameLayout 为黑白化 FrameLayout

怎么替换?

这个你就需要对 LayoutInflater 的 inflate 过程有一定的了解,如下方法截图:

image-20221208112810720.png

可以看到,LayoutInflater 在创建 View 的过程中:

1、优先使用 mFactory2 去创建 View ,如果 mFactory2 为空则使用 mFactory,mFactory 为空才会使用 mPrivateFactory

2、Activity 中,系统给我们设置了 mFactory2:

image-20221208115031789.png

实际流程跟下去最终就是想做如下处理:

将一些系统的 View 替换为:androidx.appcompat.widget 下的 View,如:TextView -> AppCompatTextView ,ImageView -> AppCompatImageView。

3、Activity 可以复写 onCreateView 方法,这个方法其实也是 LayoutFactory 在构建 View 的时候回调出来的,一般对应其内部的 mPrivateFactory。

4、目前系统对于 FrameLayout 并没有特殊处理,Activity 可以复写 onCreateView 方法,然后将内容栏 FrameLayout 替换为黑白化 FrameLayout 即可。

了解了替换思路,接下来我们实践一下。

1、创建 BaseActivity ,将替换逻辑写在基类里面:

abstract class BaseActivity: AppCompatActivity() {
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(getLayoutId())
        initView()
    }

    abstract fun getLayoutId(): Int

    abstract fun initView()

    override fun onCreateView(
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
        try {
            //tag name 为 FrameLayout
            if ("FrameLayout" == name) {
                val attributeCount = attrs.attributeCount
                for (i in 0 until attributeCount) {
                    //属性名称
                    val attributeName = attrs.getAttributeName(i)
                    //属性值
                    val attributeValue = attrs.getAttributeValue(i)
                    //属性名称为:id
                    if ("id" == attributeName) {
                      	//@16908290 => 16908290
                        val resId = Integer.parseInt(attributeValue.substring(1))
                      	//获取资源名称:android:id/content
                        val idValue = resources.getResourceName(resId)
                        if ("android:id/content" == idValue) {
                            //如果是 DecorView 的 FrameLayout,替换为 GrayFrameLayout
                            val grayFrameLayout = GrayFrameLayout(context, attrs)
                            return grayFrameLayout
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return super.onCreateView(parent, name, context, attrs)
    }
}

在看一眼 GrayFrameLayout:

class GrayFrameLayout(context: Context, attrs: AttributeSet): FrameLayout(context,attrs) {

    //色彩饱和度为 0 的 Paint
    private val paint by lazy {
        val p = Paint()
        val cm = ColorMatrix()
        cm.setSaturation(0f)
        p.colorFilter = ColorMatrixColorFilter(cm)
        p
    }
    
    override fun draw(canvas: Canvas?) {
        canvas?.saveLayer(null,paint,Canvas.ALL_SAVE_FLAG)
        super.draw(canvas)
        canvas?.restore()
    }
  
    //分发给子 View
    override fun dispatchDraw(canvas: Canvas?) {
        canvas?.saveLayer(null,paint,Canvas.ALL_SAVE_FLAG)
        super.dispatchDraw(canvas)
        canvas?.restore()
    }
}


2、修改一下 MainActivity

class MainActivity: BaseActivity() {

    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }

    override fun initView() {

    }
}

3、最后运行 App ,看一眼效果:

image-20221208153942930.png

状态栏颜色没变,🤔,手动设置状态栏颜色和标题栏颜色保持一致

吸取标题栏颜色值:#4A4A4A ,在 BaseActivity 里面设置一下:

abstract class BaseActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      	//5.0及以上才能设置状态栏颜色
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.statusBarColor = Color.parseColor("#4A4A4A")
        }
        setContentView(getLayoutId())
        initView()
    }
  
    //...
}

运行 App,在看一眼效果:

image-20221208154638094.png

ok,现在整个页面都黑白化了,🍺。

三、问题

3.1、方案一问题

接下来我们看看方案一存在的不足之处,我们在第一个 Button 按钮添加点击事件,让它弹出一个 Dialog:

//1、activity_main.xml,给第一个 Button 增加点击事件
<Button
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:onClick="btnClick"
   android:text="erdai666"
   android:textColor="@android:color/holo_green_light" />

//2、MainActivity
fun btnClick(view: View) {
    AlertDialog.Builder(this)
        .setTitle("标题")
        .setMessage("owejfioweofwe")
        .setPositiveButton("确定"){dialog,which->
            dialog.dismiss()
         }
         .setNegativeButton("取消"){dialog,which->
             dialog.dismiss()
         }
         .show()
    }

效果展示:

image-20221208160228303.png

Dialog 并未黑白化,为啥呢?先记着

3.2、方案二问题

把上述代码放在方案二跑一遍,你会发现 Dialog 黑白化了,如下图:

image-20221208160958646.png

但是如果我们换成 PopupWindow:

//1、activity_main.xml,给第二个 Button 增加点击事件
<Button
   android:id="@+id/btnBlackAndWhite"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:onClick="btnClick1"
   android:text="erdai666"
   android:textColor="@android:color/holo_green_light" />

//2、popup_window_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/color_E62117"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:text="function1"
            android:textColor="@color/white"
            android:textSize="20sp" />

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@color/white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:text="function2"
            android:textColor="@color/white"
            android:textSize="20sp" />

    </LinearLayout>
</FrameLayout>

//3、MainActivity
fun btnClick1(view: View) {
  	val contentView = layoutInflater.inflate(R.layout.popup_window_view,null)
        val popupWindow = PopupWindow(
        contentView,
        ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT,
        true
    )
    popupWindow.isOutsideTouchable = true
    popupWindow.isTouchable = true
    popupWindow.setBackgroundDrawable(ColorDrawable())
    popupWindow.showAsDropDown(view)
}

效果展示:

image-20221208161454907.png

PopupWindow 没有黑白化。

梳理一下方案一和方案二的问题:

1、方案一:Dialog,PopupWindow 都未黑白化

2、方案二:Dialog 黑白化,PopupWindow 未黑白化

小朋友,你是不是有很多问号?为啥呢?

想了解这些问题,我们首先得对 Android 的 Window 机制,Dialog 源码,PopupWindow 源码有一定的了解,推荐一篇文章:Android全面解析之Window机制 ,这里就不展开讲了

方案一之所以 Dialog,PopupWindow 都未黑白化,是因为 Activity,Dialog,PopupWindow 它们拥有不同的 DecorView ,你设置 Activity 的 DecorView,当然不会影响 Dialog,PopupWindow

方案二之所以 Dialog 黑白化,PopupWindow 未黑白化,是因为 Dialog 和 Activity 拥有相同的 View 结构,如下图:

image-20221208173238175.png

Dialog 创建了新的 PhoneWindow,使用了 PhoneWindow 的 DecorView 模板。而 PopupWindow 并没有。

两种方案都不行,问题到了这里似乎无解了,真的无解了吗?

3.2、新思路

想一下,Activity,Dialog,PopupWindow 或其他一些 Window 组件它们是不是都要进行 Window 的添加, Window 的添加最终会走到如下方法:

//WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
        //...
	synchronized (mLock) {

        // 将 view 添加到 mViews,mViews 是一个 ArrayList 集合
        mViews.add(view);
       	

        // 最后通过 viewRootImpl 来添加 window
        try {
            root.setView(view, wparams, panelParentView);
        } 
    }  
}

Tips:

Window 是 View 树的载体,View 树是 Window 的具体表现形式,View 树可以是一个单独的 View,也可以是很多 View 组合。

就好比一个班级,班级是学生的载体,学生是班级的具体体现

WindowManagerGlobal 是一个全局单例,其中 mViews 是一个集合,App 中所有的 Window 在添加的时候都会被它给存起来。

那我们是不是可以 Hook 拿到 mViews 中所有的 View 然后对他们进行黑白化设置,这样是不是所有的页面都变成黑白化了呢?

限于篇幅,我打算在写一篇文章去对新思路进行实践,

四、总结

本篇文章我们介绍了:

1、App 黑白化实现原理:将 Paint 的饱和度设置为 0,然后进行 View 的绘制

2、App 黑白化两种方案实践:

1、对页面的 DecorView 进行黑白化设置

2、替换页面的内容栏 FramLaout 为黑白化 FrameLayout

3、分析了 App 黑白化两种方案存在的一些问题

1、方案一:Dialog,PopupWindow 黑白化不生效

2、方案二:Dialog 黑白化生效,PopupWindow 黑白化不生效

4、给出了 App 黑白化两种方案出现问题的原因以及新的思路

关于新思路实践,预知后事如何,且听下回分解。

好了,本篇文章到这里就结束了,希望能给你带来帮助 🤝

感谢你阅读这篇文章

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

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

相关文章

[附源码]Python计算机毕业设计SSM基于java旅游信息分享网站(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

汇编语言第2章—寄存器

8086CPU有14个寄存器&#xff0c;分别是&#xff1a;AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。2.1 通用寄存器 8086CPU的所有寄存器都是16位的&#xff0c;可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据&#xff0c;称为通用…

【Spring】AOP记录日志

我的aop记录日志&#xff0c;可以记录&#xff1a;【 操作类型、操作描述、参数、登录项目的用户ip】 当然记录什么靠你自己决定。 一.自定义一个注解 Target({ElementType.METHOD,ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Documented public interface A…

两位前阿里 P10 的成长经历的启发

目录 汤峥嵘的成长经历 关键节点一&#xff1a;到美国留学 关键节点二&#xff1a;美国工作十年 关键节点三&#xff1a;八年阿里时光 关键节点四&#xff1a;加入途牛和 VIPABC 毕玄的成长经历 关键节点一&#xff1a;小公司里脱颖而出 关键节点二&#xff1a;加入淘宝…

FineReport数据分析教程- 图表刷新接口

1. 概述 1.1 预期效果 点击按钮可以刷新普通报表或决策报表中的图表&#xff0c;以普通报表为例&#xff0c;效果如下图所示&#xff1a; 1.2 实现思路 通过FR.Chart.WebUtils.getChart("chartID").dataRefresh()获取要刷新的图表对象&#xff0c;其中chartID为图表…

程序员如何写一份更好的简历

简历中的常见错误 1. 信息过多&#xff0c;缺乏重点 信息过多的常见表现是十几行的技能列表&#xff0c; 我举一个血淋淋的例子&#xff1a; 20 行的技能列表&#xff0c;这位求职者开始就把自己了解的所有工具都列出来&#xff0c;希望能够突显自己的经验和学习能力&#xf…

pytorch基础操作(五)多层感知机的实现

1、多层感知机 1、激活函数的引入 这个多层感知机有4个输⼊&#xff0c;3个输出&#xff0c;其隐藏层包含5个隐藏单元。输⼊层不涉及任何计算&#xff0c;因此使⽤此⽹络产⽣输出只需要实现隐藏层和输出层的计算。因此&#xff0c;这个多层感知机中的层数为2。注意&#xff0…

小米盒子为什么搜不到电视家?电视安装包解析错误解决方案

不少的朋友在小米电视盒子上安装了美家市场软件商店后&#xff0c;却发现在市场里面没法安装想要的电视盒子直播软件&#xff0c;这是怎么回事呢&#xff1f;其实大部分原因是电视盒子机制的问题限制了安装&#xff0c;导致部分品牌电视盒子装软件时会弹出“无法安装”的提示。…

Mysql双主整理

目录 1. Mysql binlog参数配置 2. Mysql binlog查看详细内容 3. Mysql双主搭建 4. Mysql双主解决数据回环 4.1 双主同步测试一 4.1.1 测试总结 4.2 双主同步测试二 4.2.1 测试总结 4.3 双主同步测试三 4.3.1 测试总结 1. Mysql binlog参数配置 log-binmysql-bin 打…

水果FLStudio21.0.0软件最新版有哪些新增功能变化?

FL Studio(水果软件)21 引入更快、更精确的音频编辑、改进的内容发现、对 DAW 情绪的控制以及更多鼓舞人心的创意工具。FL Studio是一款功能非常强大的音乐创作编辑软件它就是FL Studio(水果软件)。使用FL Studio中文版可以轻松帮我们制作自己的音乐唱片&#xff0c;拥有强大且…

【ROS】HelloWord简单实现

C实现 1. 创建工作空间并初始化 创建工作目录demo01_ws&#xff0c;并在该文件夹下创建src文件夹 mkdir -p demo01_ws/src进入到该目录下 cd demo01_ws/初始化 catkin_make这时在demo01_ws目录下除了src文件夹外&#xff0c;多处了两个文件夹。 ![在这里插入图片描述](htt…

数字化门店转型| 水疗会所管理系统| 小程序搭建

水疗会所与沐足采耳、洗浴按摩等都属于休息享受型服务&#xff0c;是不少中年人的选择&#xff0c;一天的压力可以得到缓解&#xff0c;同时客单价一般在几百元左右&#xff0c;在水疗会所里可以洗澡蒸桑拿、桌游、乒乓球等&#xff0c;同时随着近些年来生活压力逐渐加大&#…

【C语言学习】详解二级指针

在学习数据结构时&#xff0c;通常会遇到调用函数无法对主函数中的全局变量进行有效的更改操作&#xff0c;这时我们就需要指针&#xff0c;但二级指针对于初学者而言有着一定的学习难度&#xff0c;本文通过代码结合实验调试来详细说明二级指针。 如果一个指针指向另一个指针&…

[附源码]Node.js计算机毕业设计高校图书服务系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

Atcoder 前缀和优化DP Candies

Candies - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意&#xff1a; 思路&#xff1a; 考虑DP 状态设计&#xff1a; 首先&#xff0c;因为是线性DP&#xff0c;dp[i]是必不可少的 然后去考虑一下决策&#xff0c;看看是什么东西影响了决策 对于第 i 个位置&#xf…

stm32f767自举

仅作笔记 一&#xff0c;自举。 在M0&#xff0c;M3&#xff0c;M4内核中&#xff0c;是通过boot0和boot1两个引脚的电平组合来确定启动地址的&#xff0c;启动的介质可以是系统存储器&#xff0c;SRAM&#xff0c;主Flash等。 在M7内核中&#xff0c;是通过boot0的电平加 Fla…

Linux内核调试技术之动态调试

前言 使用printk的打印方式只能通过设置输出等级来进行控制&#xff0c;具备一定的局限性。在实际系统运行过程中&#xff0c;我们更希望能选择性地打开某些子系统或者模块的输出&#xff0c;为此内核提供了动态调试技术。内核中包括pr_debug、dev_dbg接口都使用了动态调试技术…

javax.crypto.BadPaddingException: Decryption error

问题描述 使用Postman调用Java api解密token时 token值为iRdLmVEYUUvoH1oDF2QhSVhJxXYMRCxzbtJsL01Iun2OLHY/FxNQOrAwF4Bj2cdp1vhsXt9BQtcxmiyuCvyi2Itl2qlvlCT6VwRM6UgQ5SBIiInGlLYCrzDfOoQ74zhxwW7M43vIuLs6W0y7Rt86uZgmAR8gYwMLfvGnRg 执行时报错如下&#xff1a; 原因分析…

Redis框架(十二):大众点评项目 阻塞队列+异步处理 实现秒杀优化

大众点评项目 阻塞队列异步处理 实现秒杀优化需求&#xff1a;阻塞队列异步处理 实现秒杀优化为什么使用异步处理&#xff1f;为什么使用阻塞队列&#xff1f;为什么使用Lua&#xff1f;业务逻辑及其实现原有逻辑代码 / 优化后逻辑代码完整优化业务代码原有优化业务代码总结Spr…

Traefik整理

entryPoints配置 defaultEntryPoints ["oneway"][entryPoints]# 代表traefik的监听端口为90[entryPoints.oneway]address ":90"#90端口接收到的请求先进行鉴权&#xff0c;traefik会先访问跳转到http://127.0.0.1:51001/t/test1进行权限验证[entryPoint…