Android 渲染机制

1 Android 渲染流程

一般情况下,一个布局写好以后,使用 Activity#setContentView 调用该布局,这个 View tree 就创建好了。Activity#setContentView 其实是通过 LayoutInflate 来把布局文件转化为 View tree 的(反射)。需要注意的是,LayoutInflate 虽然可以帮助创建 View tree,但到这里也仅是以单纯的对象数据存在,这个时候是无法正确的获取 View 的 GUI(Graphical User Interface 图形用户界面)的相关属性的,如大小、位置和渲染状态。

View tree 生成的最后一步就是把根节点送到 ViewRootImpl#setView 中,之后就会进入渲染流程,入口方法是 ViewRootImpl#requestLayout,之后是 ViewRootImpl#scheduleTraversals,最后调用的是 ViewRootImpl#performTraversals,View tree 的渲染流程全都在这里,也就是常说的 measure、layout、draw。View体系与自定义View(三)—— View的绘制流程

以下为 View 的绘制流程/视图添加到 Window 的过程:

绘制流程

绘制流程

总结:文本数据(xml)—> 实例数据(java) —> 图像数据 bitmap,bitmap 才是屏幕(硬件)所需的数据。

在 ViewRootImpl#drawSoftware 方法中会通过 Surface#lockCanvas 方法创建一个 Canvas(在英文中是“画布”的意思) 对象,然后进入 View#draw 流程,Canvas 才是实际制作图像的工具,比如如何画点,如何画线,如何画文字、图片等等。

// /frameworks/base/core/java/android/view/ViewRootImpl.java
public final Surface mSurface = new Surface();
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {Surface surface = mSurface; // 1...if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,scalingRequired, dirty, surfaceInsets)) {return false;}}private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) {// Draw with software renderer.final Canvas canvas; // 2try {canvas = mSurface.lockCanvas(dirty); // 3canvas.setDensity(mDensity);} catch (Surface.OutOfResourcesException e) {handleOutOfResourcesException(e);return false;} catch (IllegalArgumentException e) {mLayoutRequested = true;    // ask wm for a new surface next time.return false;}try {...mView.draw(canvas);...}finally {...    }
} 

一个 Canvas 对象从 ViewRootImpl 传给 View,View 的各个方法(draw、dispatchDraw 和 drawChild)都只接收 Canvas 对象,每个 View 都要把其想要展示的内容传递到 Canvas 对象中。

// /frameworks/base/core/java/android/view/View.java
public void draw(@NonNull Canvas canvas) {...
}protected void dispatchDraw(@NonNull Canvas canvas) { }// /frameworks/base/core/java/android/view/ViewGroup.java
protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {return child.draw(canvas, this, drawingTime);
}

在 Canvas 中有一个 Bitmap 类型的对象,这个 Bitmap 才是真正的画布。

// /frameworks/base/graphics/java/android/graphics/Canvas.java
public class Canvas extends BaseCanvas {private Bitmap mBitmap;
}

那么,Surface 是什么呢?以下是 Surface 的部分源码:

/*** Handle onto a raw buffer that is being managed by the screen * 由屏幕管理的原始缓冲区*/
public class Surface implements Parcelable {private final Canvas mCanvas = new CompatibleCanvas();public Canvas lockCanvas(Rect inOutDirty)throws Surface.OutOfResourcesException, IllegalArgumentException {synchronized (mLock) {checkNotReleasedLocked();if (mLockedObject != 0) {throw new IllegalArgumentException("Surface was already locked");}mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);return mCanvas;}}
}

从注释上可以知道,Surface 是一块原始缓冲区。 在 Android 中,所有的 View 都由窗口管理,而每个窗口都会关联一个 Surface。在屏幕上绘制内容之前,都需要先获得 Surface,然后用 2D/3D 引擎(Skia/OpenGL)在这个缓冲区上绘制内容。 绘制完成之后,会通知 SurfaceFlinger 将绘制内容(Frame Buffer)渲染到屏幕上去。关于 SurfaceFlinger,之后会做详细解释。

屏幕渲染分为软件渲染和硬件渲染,Canvas 对象的来源也有两个:

  • 一是走软件渲染时,在 ViewRootImpl 中创建,从源码上看,ViewRootImpl 本身就会创建一个 Surface 对象,然后用 Surface 获取出一个 Canvas 对象,再传递给 View,由 View 进行具体的绘制;
  • 二是走硬件加速,会由 hwui 创建 Canvas 对象;

因此,draw 的触发逻辑也有两条:

  • 没有硬件加速时,走的是 ViewRootImpl#performTraversals —> performDraw —> draw —> drawSoftware —> View#draw;
  • 启动硬件加速时,走的是 ViewRootImpl#performTraversals —> performDraw —> draw —> ThreadedRenderer.java#draw

2 软件绘制和硬件绘制

Android 4.0 开始引入硬件加速机制,之前走的都是软件渲染。如果有一些 API 是不支持硬件加速的,需要进行手动关闭。

软件绘制和硬件绘制

UI 渲染需要要依赖两个核心的硬件,CPU 和 GPU:

  • CPU(Center Processing Unit 中央处理器),是计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元;
  • GPU(Graphics Processing Unit 图形处理器),是一种专门用于图像运算的处理器,在加计算机系统中通常被称为“显卡”的核心部位就是 GPU;

在没有 GPU 的时代,UI 的绘制任务都是由 CPU 完成的,也就是说,CPU 除了负责逻辑运算、内存管理还要负责 UI 绘制,这就导致 CPU 的任务繁重,性能也会受到影响。

CPU 和 GPU 在结构设计上完全不同,如下所示:

CPU 和 GPU

  • 黄色部分:Control 控制器,用于协调控制整个 CPU 的运行,包括读取指令、控制其他模块的运行等;
  • 绿色部分:ALU(Arithmetic Logic Unit)是算数逻辑单元,用于进行数学、逻辑运算;
  • 橙色部分:Cache 和 DRAM 分别为高速缓存和 RAM,用于存储信息;

从上图可以看出,CPU 的控制器较为复杂,而 ALU 数量较少,因此 CPU 更擅长各种复杂的逻辑运算,而不擅长数学尤其是浮点运算。而 GPU 的设计正是为了实现大量的数学运算。GPU 的控制器比较简单,但包含大量的 ALU,GPU 中的 ALU 使用了并行设计,且具有较多的浮点运算单元,可以帮助我们加快 Rasterization(栅格化)操作。

栅格化将 UI 组件拆分到显示器上的不同像素上进行显示。UI 组件在绘制到屏幕之前都要经过 Rasterization(栅格化)操作,是绘制 Button、Shape、Path、String、Bitmap 等显示组件最基础的操作。这是一个非常耗时的操作,GPU 的引入就是为了加快栅格化。

栅格化

因此,硬件绘制的思想就是 CPU 将 XML 数据转换成实例对象,然后将 CPU 不擅长的图形计算交由 GPU 去处理,由 GPU 完成绘制任务,以便实现更好的性能(CPU 和 GPU 都是制图者)。

底层图像库有很多,Android 选择的是 Skia(2D) 和 OpenGL(3D) 来绘制图形,图形库可以直接控制 GPU 产生图形数据(Canvas.draw —> native —>Skia/OpenGL —> GPU)。

软件绘制使用的是 Skia 库,是一款能在低端设备,如手机上呈现高质量的 2D 跨平台图形框架,Chrome、Flutter 内部使用的都是 Skia 库。需要注意的是,软件绘制使用的是 Skia 库,但这并不代表 Skia 库不支持硬件加速,从 Android 8 开始,我们可以使用 Skia 进行硬件加速,Android 9 开始默认使用Skia 进行硬件加速。

在处理 3D 场景时,通常使用 OpenGL ES。在 Android 7.0 中添加了对 Vulkan 的支持。Vulkan 的设计目标是取代 OpenGL,Vulkan 是个相当低级别的 API,并且提供了并行的任务处理。除了较低的 CPU 的使用率,VulKan 还能够更好的在多个 CPU 内核之间分配工作。在功耗、多核优化提升会图调用上有非常明显的优势。

Skia、OpenGL、Vulkan 的区别:

  • Skia:是 2D 图形渲染库。如果想完成 3D 效果需要 OpenGL、Vulkan、Metal 进行支持。Android 8 开始 Skia 支持硬件加速,Chrome、Flutter 都是用它来完成绘制的;
  • OpenGL:是一种跨平台的 2D/3D 图形绘制规范接口,OpenGL ES 是针对嵌入式设备的,对手机做了优化;
  • Vulkan:Vulkan 是用来替换 OpenGL 的,它同时支持 2D 和 3D 绘制,也更加轻量级;

3 Android 黄油计划(Project Butter)

虽然引入了硬件加速机制,加快了渲染的时间,但是对于 GUI(Graphical User Interface 图形用户界面)的流畅度、响应度,特别是动画这一块的流畅程度和其他平台(如 Apple)差距仍然是很大的。一个重要的原因就在于,GUI 整体的渲染缺少协同。 最大的问题在于动画,动画要求连续不断的重绘,如果仅靠客户端来触发,帧率不够,由此造成的流畅度也不好。

Google 在 2012 年的 I/O 大会上宣布了 Project Butter 黄油计划,并且在 Android 4.1 中正式开启这个机制。Project Butter 主要包含三个组成部分:

  • VSync
  • Choreographer
  • TripBuffer

其中,VSync(Vertical Synchronization) 是理解 Project Butter 的核心。

3.1 VSync

帧率 vs 屏幕刷新频率:

  • 帧率(Frame Rate):单位 fps,即 GPU 在一秒内生成的帧数(图片),帧率越高越好。例如电影界采用 24 帧的速度就可以画面非常流畅了,而 Android 系统则采用更高的 60fps,即每秒生成 60 帧的画面,也就是 1000/60 ≈ 16ms 生成一帧画面;
    • 12 fps:由于人类眼睛的特殊生理结构,如果所看画面帧率高于 10~12 fps 的时候,就会认为是连贯的;
    • 24fps:有声电影的拍摄及播放帧率均为 24fps,对一般人来说是可以接受的;
    • 30fps:早起的高动态电子游戏,帧率小于 30fps 时就会显得不连贯,这是因为没有动态模糊使流畅度降低;
    • 60fps:在与手机交互过程中,如果触摸和反馈在 60fps 以下是可以被人感觉出来的,会感到画面卡顿和迟滞现象;
  • 屏幕刷新频率(Refresh Rate):单位是赫兹(Hz),表示屏幕在一秒内刷新画面的次数,刷新频率取决于硬件的固定参数,该值对于特定的设备来说是一个常量。如 60Hz、144 Hz 表示每秒刷新 60 次或 144 次。

对于一个特定的设备来说,帧率和屏幕刷新速率没有必然的关系。但是两者需要协同工作,才能正确的获取图像数据并进行绘制。比如 Android 手机的刷新频率是 60Hz,那么一帧数据需要在 16ms 内完成。

屏幕并不是一次性的显示画面的,而是从左到右(行刷新,水平刷新,Horizontal Scanning)、从上到下(屏幕刷新,垂直刷新,Vertiacl Scanning)逐行扫描显示,不过这一过程快到人眼无法察觉。以 60Hz 的刷新频率的屏幕为例,即 1000/60 ≈ 16ms,16ms 刷新一次。

屏幕刷新过程

如果上一帧的扫描没有结束,屏幕又开始扫描下一帧,就会出现扫描撕裂的情况:
tearing

因此,GPU 厂商开发出了一种防止屏幕撕裂的技术方案 —— Vertical Synchronization,即 VSync,垂直同步信号或时钟中断。VSync 是一个硬件信号,它和显示器的刷新频率相对应,每当屏幕完成一次垂直刷新,VSync 信号就会被发出,作为显示器和图形引擎之间时间同步的标准,其本质意义在于保证界面的流畅性和稳定性。

3.2 Choreographer

Choreographer(编舞者)根据 VSync 信号来对 CPU/GPU 进行绘制指导,协调整个渲染过程,对于输入事件响应、动画和渲染在时间上进行把控,以保证流畅的用户体验。

Choreographer 在 ViewRootImpl 中的使用:

// /frameworks/base/core/java/android/view/ViewRootImpl.java
final Choreographer mChoreographer;
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

Choreographer 的作用:

  • 布局请求:当视图需要进行布局操作时,Choreographer 发出布局请求并协调布局操作的执行。它确保将布局请求与其他动画和绘制操作同步,避免冲突和界面不一致;
  • 绘制同步:Choreographer 负责将绘制操作与显示器的刷新同步。它通过监听系统的 VSync 信号,去定绘制操作的时机,避免图形撕裂和卡顿现象;
  • 输入事件处理:Choreographer 管理和分发用户输入事件,确保它们在正确的时间点被处理,并与动画和渲染操作同步。这有助于提供更流畅和响应敏捷的用户交互体验;
  • 动画调度:Choreographer 调度和管理应用程序中的动画效果,确保动画按照预定的帧率和时间表进行播放,并平滑地过渡到下一个动画阶段;

Choreographer 使用了以下几种机制来实现流畅的界面渲染:

  • VSync(垂直同步信号):Choreographer 监听系统发出的 VSync 信号。每当收到 VSync 信号时,Choreographer 就知道屏幕即将进行一次刷新。这样,它可以根据 VSync 信号的时间点来安排渲染和动画操作的触发和执行;
  • 时间戳(Timestamping):Choreographer 在收到 VSync 信号时,会获取一个时间戳,以记录每次 VSync 信号的时间点。这个时间戳可以用于计算渲染和动画的操作时间和持续时间,从而在合适的时机进行调度和执行;
  • 界面刷新(Frame Refresh):Choreographer 使用 VSync 信号和时间戳来决定界面的刷新时机。它根据预定的逻辑和优先级,调度动画、布局和绘制操作,以确保它们在下一次 VSync 信号到来之前完成。这样可以避免界面的撕裂或卡顿现象,提供流畅的用户体验;

其实这个 Choreogarpher 这个类本身并不会很复杂,简单来说它就是负责定时回调,主要方法有 postFrameCallback 和 removeFrameCallback,FrameCallback 是个比较简单的接口:

// /frameworks/base/core/java/android/view/Choreographer.java
public interface FrameCallback {public void doFrame(long frameTimeNanos);
}
3.3 TripBuffer 三缓存
3.3.1 单缓存

在没有引入 Vsync 的时候,屏幕显示图像的工作流程是这样的:

没有引入 Vsync 时

如上图所示,CPU/GPU 将需要绘制的数据存放在图像缓冲区中,屏幕从图像缓冲区中获取数据,然后刷新显示,这是典型的生产者-消费者模型。

理想的情况是帧率(GPU)和刷新频率(屏幕)相等,每绘制一帧,屏幕就显示一帧。而实际情况是,二者之间没有必然的联系,如果没有锁来控制同步,很容易出现问题。

  • 如果刷新频率大于帧率的时候,屏幕拿不到下一帧数据,就会重复绘制当前帧数据。
  • 如当帧率大于刷新频率时,屏幕还没有刷新到 n-1 帧的时候,GPU 已经生成第 n 帧了,屏幕刷新的时候绘制的就是第 n 帧数据,这个时候屏幕上半部分显示的是第 n 帧数据,屏幕的下半部分显示的是第 n-1 帧之前的数据,这样显示的图像就会出现明显的偏差,也就是“tearing”,如下所示:
    tearing
    tearing
3.3.2 双缓存(Double Buffer)

这里的双缓存和计算机组成原理中的“二级缓存”不是一回事。

为了解决单缓存的 tearing 问题,双缓存和 VSync 应运而生。双缓存的模型如下所示:

双缓存

两个缓存分别为 Back Buffer 和 Frame Buffer(帧缓冲区)。GPU 向 Back Buffer 中写数据,屏幕从 Frame Buffer 中读数据。VSync 信号负责调用 Back Buffer 到 Frame Buffer 的复制操作,可以认为该复制操作在瞬间完成。

在双缓冲模式下,工作流程是这样的:在某个时间点,一个屏幕刷新周期完成,进入短暂的刷新空白期。此时,VSync 信号产生,先完成复制操作,然后通知 CPU/GPU 绘制下一帧图像。复制操作完成后屏幕开始下一个刷新周期,即将刚复制到 Frame Buffer 的数据显示到屏幕上。

在双缓冲模型下,只有当 VSync 信号产生时,CPU/GPU 才会开始绘制。这样,当帧率大于刷新频率时,帧率就会被迫跟刷新频率保持同步,从而避免“tearing”现象。

需要注意的是,当 VSync 信号发出时,如果 CPU/GPU 正在生产帧数据,此时不会发生复制操作。当屏幕进入下一个刷新周期时,就会从 Frame Buffer 中取出“老”数据,而非正在产生的帧数据,即两个刷新周期显示的是同一帧数据,这就是“掉帧”现象(Dropped Frame,Skipped Frame,Jank)。因此,双缓存的缺陷在于,当 CPU/GPU 绘制一帧的时间超过 16ms 时,就会产生 Jank。

如下图所示,A、B 和 C 都是 Buffer。蓝色代表 CPU 生成的帧数据,绿色代表 GPU 执行生成帧数据,黄色代表生成帧完成:

double buffering

CPU/GPU 处理数据的时间过长,超过了一帧绘制的时间,在第二个时间段内,由于 GPU 还在处理 B 帧数据,无法进行数据交换,导致 A 帧被重复绘制。而 B 帧数据在绘制完成后又缺乏 VSync 信号,只能等待下一次的 VSync 信号的来临。因此,在这一过程中,有一段时间是被浪费的。

3.3.3 三缓存(Triple Buffer)

于是有了三缓存:

三缓存

工作原理同双缓冲类似,只是多了一个 Back Buffer。三缓冲机制有效的利用了等待 VSync 信号的时间,可以帮助我们减少 jank。

如果有第三个 Buffer 能让 CPU/GPU 在这个时候继续工作,那就完全可以避免第二个 Jank 产生了。

Triple buffering

需要注意的是,第三个缓存并不是总存在的,只有当需要的时候才会创建。 之所以这样,是因此三缓存会显著增加用户输入到显示的延迟时间。如上图,帧 C 是在第 2 个刷新周期产生的,却是在第 4 个周期显示的。

4 Android 渲染的整体架构

以下是 Android 渲染的整体架构:
渲染整体架构

Android 渲染的整体架构可以分为以下几部分:

  • 图像生产者(image stream producers):主要有 MediaPlayer、CameraPreview、NDK(Skia)、OpenGL ES。其中,MediaPlayer 和 Camera Preview 是通过直接读取图像源来生成图像数据。NDK(Skia)、OpenGL ES 是通过自身的绘制能力产生的图像数据。
  • 图像缓冲区(BufferQueue):一般是三缓冲区。NDK(Skia)、OpenGL ES、Vulkan 将绘制的数据存放在图像缓冲区;
  • 图像消费者(image stream consumers): SurfaceFlinger 从图像缓冲区将数据取出,通过硬件合成器 Hardware Composer 进行加工及合成 layer,最终交给 HAL 展示;
  • HAL:硬件抽象层,把图形数据展示到设备屏幕;

整个图像渲染系统就是采用了生产者-消费者模式,屏幕渲染的核心,是对图像数据的生产和消费。 生产和消费的对象是 BufferQueue 中的 Buffer。
生产者-消费者模式

前面我们已经说过,Surface 是一块原始缓冲区,每个窗口都会管理一个 Surface,屏幕在绘制内容之前,先要获得 Surface,然后在再用 2D/3D 引擎(Skia/OpenGL)在这个缓冲区上进行绘制(Surface —> Canvas)。

SurfaceFlinger 是图像数据的消费者,它的作用主要是接收 Graphic Buffer,然后交给 HWComposer 合成,合成完的数据,最终交给了 Frame Buffer(帧缓冲区)。

三缓冲机制

软件渲染

再没有硬件加速之前主要是通过 Skia 这种软件方式渲染 UI,如下所示:

软件渲染

整个渲染流程看上去比较简单,但是正如前面所说,CPU 对于图形处理器并不是那么高效,这个过程完全没有利用 GPU 的高性能。

硬件渲染

Android 3.0,支持硬件加速,需要手动打开,Android 4.0 就默认开启硬件加速了,开启硬件加速流程如下:

生产者-消费者模型

硬件加速绘制最核心就是通过 GPU 完成 Graphic Buffer 的内容绘制。

RenderThread 线程

经过 Android 4.1 的 Projcet Butter 黄油计划之后,Android 的渲染性有了很大的改善。不过你有没有注意到这样一个问题,虽然利用了 GPU 的图形高性能运算,但是从计算到通过 GPU 绘制到 Frame Buffer,整个计算和绘制都在 UI 主线程中完成。UI 线程任务过于繁重。如果整个渲染过程比较耗时,可能造成无法响应用户的操作,进而出现卡顿的情况。GPU 对图形的渲染能力更胜一筹,如果使用 GPU 并在不同的线程绘制渲染图形,那么整个流程会更加顺畅。

在 Android 5.0 之通过引进 RenderThread(渲染线程),我们就可将 UI 渲染工作从 Main Thread 释放出来,交由 RenderThread 来处理,从而也使得 Main Thread 可以更专注高效地处理用户输入,这样使得在提高 UI 绘制效率的同时,也使得 UI 具有更高的响应。

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

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

相关文章

使用Go语言编写自定义的HTTP代理:探险网络奇幻之旅

你是否曾经想过自己也能编写一个代理服务器,掌握网络冲浪的主动权?现在,有了Go语言,这个梦想不再遥不可及!让我们一起踏上这段探险之旅,用Go语言编写一个自定义的HTTP代理,开启网络奇幻之旅&…

音频格式之AAC:(3)AAC编解码原理详解

系列文章目录 音频格式的介绍文章系列: 音频编解码格式介绍(1) ADPCM:adpcm编解码原理及其代码实现 音频编解码格式介绍(2) MP3 :音频格式之MP3:(1)MP3封装格式简介 音频编解码格式介绍(2) MP3 :音频格式之MP3&#x…

HNU-编译原理-甘晴void学习感悟

前言 熬过煎熬的考试周、复习以及更加煎熬的等成绩,查到成绩的那一刻,心里还是挺开心的。 虽然我没有完全学懂这门课程,但我还是兢兢业业地通过了课程的考试,拿到了这门课程的认可。 记录一下自己对编译原理的学习感悟&#xf…

优化用户体验测试应用领域:提升产品质量与用户满意度

在当今数字化时代,用户体验测试应用已经成为确保产品质量、提升用户满意度的关键工具。随着技术的不断发展,用户的期望也在不断演变,因此,为了保持竞争力,企业必须将用户体验置于产品开发的核心位置。本文将探讨用户体…

知识圣殿,智慧熔炉

知识圣殿,智慧熔炉 知识殿堂,巍然屹立 一座灵魂熔炉,号称图书馆 万卷书香盈架,智慧如星河汇聚 每一册书页,流淌着人类文明的血脉 钢笔与墨水交织诗篇 思想发芽,真理绽放光焰 浩瀚知识海洋,波涛…

Dlearning

Deep Learning Basic 神经网络: #mermaid-svg-rR22a8Udy5SxGOoP {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-rR22a8Udy5SxGOoP .error-icon{fill:#552222;}#mermaid-svg-rR22a8Udy5SxGOoP .error-t…

【MATLAB源码-第122期】基于matlab斑马优化算法(ZOA)无人机三维路径规划,输出做短路径图和适应度曲线。

操作环境: MATLAB 2022a 1、算法描述 斑马优化算法(Zebra Optimization Algorithm,简称ZOA)是一种模仿斑马群体行为的优化算法。在自然界中,斑马是一种社会性很强的动物,它们具有独特的群体行为模式&…

【STM32】STM32学习笔记-硬件SPI读写W25Q64(40)

00. 目录 文章目录 00. 目录01. SPI简介02. W25Q64简介03. SPI相关API3.1 SPI_Init3.2 SPI_Cmd3.3 SPI_I2S_SendData3.4 SPI_I2S_ReceiveData3.5 SPI_I2S_GetFlagStatus3.6 SPI_I2S_ClearFlag3.7 SPI_InitTypeDef 04. 硬件SPI读写W25Q64接线图05. 硬件SPI读写W25Q64示例06. 程序…

1块9毛钱,修复拓牛TC1D智能垃圾桶盖子不能正常开合的故障

前言 21年9月份买了拓牛的智能垃圾桶,一直用的很流畅,再加上屋里没啥有机垃圾,也没有宠物,用上之后每次投入垃圾,之后都会盖上盖子,没有很多的异味散发,屋里也没有蟑螂等害虫。 再加上门口有帘…

Chrome单独配置代理的方法

Windows Windows上单独对Chrome设置代理,需要在启动时传递参数,具体步骤如下。 在Chrome浏览器的快捷方式上右击,进入属性。在 快捷方式 标签下找到 目标 项目,在最后添加 –proxy-server“socks5://xxx.xxx.xx.xx:xxxx” 如果要…

Flink SQL 实时数据开发经验总结

使用SQL实现流处理的核心技术 在了解了Table\SQL API的使用方法以及作业运行机制之后,接下来分析SQL实现流处理的核心技术。 为什么要分析这个问题呢? 因为传统的关系代数以及SQL最开始是为了批处理设计的,在传统关系型数据库以及批处理中…

SpringBoot操作Jedis

SpringBoot操作Jedis 1、pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://ma…

大创项目推荐 目标检测-行人车辆检测流量计数

文章目录 前言1\. 目标检测概况1.1 什么是目标检测&#xff1f;1.2 发展阶段 2\. 行人检测2.1 行人检测简介2.2 行人检测技术难点2.3 行人检测实现效果2.4 关键代码-训练过程 最后 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 行人车辆目标检测计数系统 …

Conda python管理环境environments 二 从入门到精通

Conda系列&#xff1a; 翻译: Anaconda 与 miniconda的区别Miniconda介绍以及安装Conda python运行的包和环境管理 入门Conda python管理环境environments 一 从入门到精通 1. 指定环境environment的位置 可以通过提供路径来控制 conda 环境所在的位置 复制到目标目录。例如…

实习日志6

1.发现base64编码对同一张图片编码好像不会改变 用word文档的查重&#xff0c;似乎是一模一样的 于是去看了一下CSDN 1.1.base64编码原理 编码原理 使用 Base64 进行编码&#xff0c;大致可以分为 4 步&#xff1a; 将原始数据每三个字节作为一组&#xff0c;一共是 24 个 …

Python解释器的启动方式

Python解释器的启动方式 Python 解释器是一个运行 Python 代码的程序。它读取并执行写成 Python 语言的指令。由于 Python 是一种解释型语言&#xff0c;所以它的代码不需要编译成机器语言就可以直接运行。这就是为什么我们需要一个解释器来逐行读取 Python 代码&#xff0c;将…

QT5.14.2开发的Mysql8.0系统安装部署过程

最近在Windows 11 64位系统下使用QT5.14.2开发了套系统、使用了MYSQL8.0数据库&#xff0c;项目使用mingw-64编译器进行编译&#xff0c;编译完成后使用windeployqt进行发布&#xff0c;并制作安装包&#xff0c;拷贝到工控机Windows10 64位系统上进行安装运行。本文记录下安装…

RedisInsight详细安装教程

简介 RedisInsight 是一个直观高效的 Redis GUI 管理工具&#xff0c;它可以对 Redis 的内存、连接数、命中率以及正常运行时间进行监控&#xff0c;并且可以在界面上使用 CLI 和连接的 Redis 进行交互&#xff08;RedisInsight 内置对 Redis 模块支持&#xff09;。 RedisIn…

试卷扫描转化word的功能有吗?分享4款工具!

试卷扫描转化word的功能有吗&#xff1f;分享4款工具&#xff01; 随着科技的飞速发展&#xff0c;将试卷扫描并转化为Word文档已经成为我们日常学习和工作的常规需求。但是&#xff0c;市面上的扫描工具众多&#xff0c;如何选择一个既方便又准确的工具呢&#xff1f;本文将为…

Win7 和 Win Server 2008 安装Anaconda报错:Failed to extract packages

在Python官网来看&#xff0c;Python 3.8.18之后&#xff0c;就不再支持Windows7。 对应Anaconda的版本就是anaconda3-2021.05。 下载地址是&#xff1a;https://repo.anaconda.com/archive/Anaconda3-2021.05-Windows-x86_64.exe 相关链接 Python官方下载 Anaconda归档