【Android GLSurfaceView源码学习】第二天:GLSurfaceView深度分析

GLSurfaceView

在Android图形渲染体系中,SurfaceView作为一种特殊的视图组件,为开发者提供了在独立线程中进行高效绘制的能力。

GLSurfaceView则是在SurfaceView基础上针对OpenGL ES渲染场景的深度扩展,它封装了复杂的EGL(Embedded Graphics Library)上下文管理、渲染线程调度等底层细节,极大简化了OpenGL在Android平台的应用开发。

本文将从代码实现角度,详细解析GLSurfaceView如何基于SurfaceView进行扩展。

核心能力

GLSurfaceView的扩展始于对SurfaceView的直接继承,这确保了它能够复用SurfaceView的核心特性:

java 复制代码
public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2

通过继承SurfaceViewGLSurfaceView获得了以下基础能力:

  • 独立于UI线程的绘制表面(Surface),避免渲染操作阻塞UI响应
  • 通过SurfaceHolder管理Surface的生命周期(创建、销毁、尺寸变化)
  • 与Android视图系统的集成能力,可像普通视图一样布局和交互

针对OpenGL的核心扩展

OpenGL ES渲染需要与底层窗口系统建立连接,而EGL正是实现这一连接的中间层。

GLSurfaceView的核心扩展之一,就是对EGL上下文、配置和表面的自动化管理,这是SurfaceView不具备的关键能力。

1. EGL配置选择机制

SurfaceView仅提供绘制表面,而GLSurfaceView通过EGLConfigChooser接口封装了OpenGL渲染配置的选择逻辑:

java 复制代码
public interface EGLConfigChooser {
    EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
}

代码中实现了多种默认配置选择器(如SimpleEGLConfigChooserComponentSizeChooser),可根据需求选择RGB分量位数、深度缓冲、模板缓冲等参数。

例如默认配置会选择RGB_888格式且至少16位深度缓冲的配置,开发者也可通过setEGLConfigChooser方法自定义配置逻辑。

2. EGL上下文与表面工厂

为了解耦EGL上下文(EGLContext)和窗口表面(EGLSurface)的创建逻辑,GLSurfaceView定义了两个工厂接口:

  • EGLContextFactory:负责创建和销毁EGL上下文,默认实现DefaultContextFactory支持指定OpenGL ES版本(通过setEGLContextClientVersion设置)
  • EGLWindowSurfaceFactory:负责创建和销毁与Surface关联的EGL窗口表面,默认实现DefaultWindowSurfaceFactory处理表面创建的异常情况

这些工厂类封装了eglCreateContexteglDestroyContext等底层EGL调用,开发者无需直接操作EGL API。

渲染线程的封装与调度

SurfaceView需要开发者手动管理绘制线程,而GLSurfaceView内置了GLThread渲染线程,实现了渲染逻辑与UI线程的彻底分离:

1. 渲染线程的启动与管理

当调用setRenderer方法时,GLSurfaceView会启动GLThread

java 复制代码
public void setRenderer(Renderer renderer) {
    // 初始化默认配置器和工厂
    if (mEGLConfigChooser == null) {
        mEGLConfigChooser = new SimpleEGLConfigChooser(true);
    }
    // ... 初始化其他工厂
    mRenderer = renderer;
    mGLThread = new GLThread(mThisWeakRef);
    mGLThread.start();
}

GLThread作为内部类,负责执行OpenGL渲染的核心循环,包括EGL初始化、上下文创建、以及调用Renderer接口的渲染方法。

2. 两种渲染模式的支持

GLSurfaceView提供了灵活的渲染触发机制,通过setRenderMode支持两种模式:

  • RENDERMODE_CONTINUOUSLY:持续渲染模式(默认),渲染线程不断调用onDrawFrame
  • RENDERMODE_WHEN_DIRTY:按需渲染模式,仅在Surface创建或调用requestRender时触发渲染

这种设计既满足了游戏等需要高频刷新的场景,也支持了静态画面等低功耗场景。

3. 每次重启渲染线程

java 复制代码
    /**
     * This method is used as part of the View class and is not normally
     * called or subclassed by clients of GLSurfaceView.
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (LOG_ATTACH_DETACH) {
            Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
        }
        if (mDetached && (mRenderer != null)) {
            int renderMode = RENDERMODE_CONTINUOUSLY;
            if (mGLThread != null) {
                renderMode = mGLThread.getRenderMode();
            }
            mGLThread = new GLThread(mThisWeakRef);
            if (renderMode != RENDERMODE_CONTINUOUSLY) {
                mGLThread.setRenderMode(renderMode);
            }
            mGLThread.start();
        }
        mDetached = false;
    }

onAttachedToWindow()是 Android View 生命周期中的关键方法,当视图被添加到窗口管理器(WindowManager)时触发,此时视图开始具备绘制能力。

对于GLSurfaceView而言,此方法的核心作用是在视图重新附加到窗口时,恢复之前可能因脱离窗口而停止的渲染线程(GLThread)。

  • 创建新的GLThread实例,传入当前GLSurfaceView的弱引用(mThisWeakRef),避免因线程持有视图强引用导致的内存泄漏。
  • 若之前的渲染模式不是默认的持续渲染,则为新线程设置该模式。
  • 启动渲染线程,开始执行 OpenGL 渲染循环(调用Renderer的onSurfaceCreated、onDrawFrame等方法)。

Renderer接口:渲染逻辑的解耦

GLSurfaceView通过Renderer接口将渲染逻辑从视图组件中分离,这是对SurfaceView开发模式的重要改进:

java 复制代码
public interface Renderer {
    void onSurfaceCreated(GL10 gl, EGLConfig config);  // 表面创建时调用,初始化资源
    void onSurfaceChanged(GL10 gl, int width, int height);  // 表面尺寸变化时调用,设置视口
    void onDrawFrame(GL10 gl);  // 绘制每一帧
}

开发者只需实现该接口,专注于OpenGL渲染逻辑(如纹理加载、矩阵变换、绘制调用等),而GLSurfaceView会在合适的时机(由GLThread调度)自动调用这些方法。这种设计遵循了单一职责原则,降低了视图管理与渲染逻辑的耦合。

生命周期管理与状态恢复

在Android应用生命周期中,Surface可能因Activity暂停、屏幕旋转等原因销毁重建。

GLSurfaceView针对OpenGL场景强化了生命周期管理:

  1. 暂停与恢复机制

    • onPause():暂停渲染线程,根据setPreserveEGLContextOnPause配置决定是否保留EGL上下文
    • onResume():恢复渲染线程,重建EGL上下文(若已释放)并重启渲染
  2. EGL上下文丢失处理

    当设备休眠唤醒等场景导致EGL上下文丢失时,GLSurfaceView会自动触发onSurfaceCreated回调,通知开发者重建OpenGL资源(如纹理、缓冲等),避免渲染异常。

调试与线程通信支持

为简化OpenGL开发调试,GLSurfaceView提供了专门的调试功能:

  • DEBUG_CHECK_GL_ERROR:每次GL调用后检查错误并抛出异常
  • DEBUG_LOG_GL_CALLS:记录所有GL调用到系统日志

同时,为解决UI线程与渲染线程的通信问题,提供了queueEvent方法:

java 复制代码
public void queueEvent(Runnable r) {
    mGLThread.queueEvent(r);
}

通过该方法可将任务提交到渲染线程执行,避免多线程操作OpenGL资源导致的同步问题。

EglHelper

GLSurfaceView通过内部类EglHelper封装了所有与EGL相关的底层操作,为开发者屏蔽了复杂的EGL细节。

EglHelper的定位与核心职责

EglHelperGLSurfaceView的私有静态内部类,其核心职责是管理EGL生命周期的全流程,包括:

  • EGL实例初始化与终止
  • 显示设备(Display)的获取与管理
  • 渲染配置(EGLConfig)的选择
  • 渲染上下文(EGLContext)的创建与销毁
  • 渲染表面(EGLSurface)的创建、绑定与销毁
  • OpenGL接口(GL)的实例化与调试包装
  • EGL错误处理与日志记录

通过这些封装,EglHelperGLSurfaceView无需直接操作EGL API,同时为上层渲染逻辑提供了稳定的OpenGL环境。

核心成员

EglHelper的成员变量集中存储了EGL交互所需的核心对象,其设计体现了EGL的核心概念:

java 复制代码
private WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef; // 弱引用避免内存泄漏
EGL10 mEgl; // EGL10接口实例
EGLDisplay mEglDisplay; // 关联的显示设备
EGLSurface mEglSurface; // 渲染表面(与SurfaceView的Surface绑定)
EGLConfig mEglConfig; // 渲染配置(包含颜色格式、缓冲等参数)
EGLContext mEglContext; // OpenGL渲染上下文(状态存储容器)

其中,mGLSurfaceViewWeakRef使用弱引用关联GLSurfaceView,既保证了EglHelper能访问GLSurfaceView的配置(如EGLConfigChooser),又避免了因强引用导致的GLSurfaceView无法被垃圾回收的内存泄漏问题。

核心方法

1. 初始化EGL环境:start()方法

start()EglHelper的核心初始化方法,负责建立EGL的基础环境,流程可分为5个关键步骤:

步骤1:获取EGL实例
java 复制代码
mEgl = (EGL10) EGLContext.getEGL();

通过EGLContext.getEGL()获取EGL10接口实例,这是所有EGL操作的入口。

步骤2:获取默认显示设备
java 复制代码
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
    throw new RuntimeException("eglGetDisplay failed");
}

EGL通过"显示设备"(Display)与物理屏幕关联,这里获取系统默认显示设备。若获取失败(返回EGL_NO_DISPLAY),则抛出异常终止初始化。

步骤3:初始化显示设备
java 复制代码
int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
    throw new RuntimeException("eglInitialize failed");
}

调用eglInitialize初始化显示设备,同时获取EGL的主版本号和次版本号(存储在version数组中)。初始化失败会直接抛出异常。

步骤4:选择EGL配置
java 复制代码
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
    mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
}

通过GLSurfaceView中配置的EGLConfigChooser选择合适的渲染配置(EGLConfig)。EGLConfig定义了渲染表面的颜色格式(如RGB分量位数)、深度缓冲、模板缓冲等关键参数,是后续创建上下文和表面的基础。

步骤5:创建EGL上下文
java 复制代码
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
    throwEglException("createContext");
}

通过EGLContextFactory创建EGL上下文(EGLContext)。EGL上下文是OpenGL状态的存储容器,包含纹理、着色器、矩阵等所有渲染状态,是OpenGL渲染的核心。若创建失败(返回EGL_NO_CONTEXT),则通过throwEglException抛出详细错误信息。

2. 创建渲染表面:createSurface()方法

渲染表面(EGLSurface)是OpenGL的绘制目标,与SurfaceViewSurface绑定,其创建流程如下:

步骤1:销毁已有表面
java 复制代码
destroySurfaceImp(); // 确保旧表面被释放,避免资源泄漏
步骤2:创建新表面
java 复制代码
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
if (view != null) {
    mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(
        mEgl, mEglDisplay, mEglConfig, view.getHolder()
    );
}

通过EGLWindowSurfaceFactory创建与SurfaceHolderSurfaceView的表面持有者)关联的窗口表面。SurfaceHolder提供了Surface的底层句柄,使EGL表面能与Android的窗口系统绑定。

步骤3:绑定上下文与表面
java 复制代码
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
    logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
    return false;
}

调用eglMakeCurrent将EGL上下文与当前表面绑定,此后所有OpenGL操作(如绘制、纹理加载)都会作用于该表面。若绑定失败(通常因Surface已销毁),则记录警告并返回false

3. 生成OpenGL接口:createGL()方法

createGL()用于创建可供开发者使用的OpenGL接口实例(GL),并支持调试增强:

java 复制代码
GL gl = mEglContext.getGL(); // 从EGL上下文获取基础GL接口
// 应用GL包装器(如自定义扩展)
if (view.mGLWrapper != null) {
    gl = view.mGLWrapper.wrap(gl);
}
// 启用调试功能(错误检查、调用日志)
if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) {
    gl = GLDebugHelper.wrap(gl, configFlags, log);
}

通过GLDebugHelper,开发者可开启OpenGL调用的错误检查(每次调用后检查glGetError)和日志记录,极大简化调试过程。这是EglHelper对开发者友好的重要体现。

4. 提交渲染结果:swap()方法

OpenGL通常使用双缓冲机制(前缓冲用于显示,后缓冲用于绘制),swap()方法负责将后缓冲的渲染结果交换到前缓冲,完成画面显示:

java 复制代码
public int swap() {
    if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
        return mEgl.eglGetError(); // 交换失败返回错误码
    }
    return EGL10.EGL_SUCCESS;
}

eglSwapBuffers是显示渲染结果的关键调用,失败通常意味着表面已失效(如Surface被销毁)。

5. 资源释放:destroySurface()finish()方法

EGL资源需及时释放以避免内存泄漏,EglHelper提供了两级释放机制:

  • 表面释放(destroySurface()

    通过destroySurfaceImp()解除上下文与表面的绑定(eglMakeCurrent设为EGL_NO_SURFACE),并调用工厂方法销毁表面:

    java 复制代码
    private void destroySurfaceImp() {
        if (mEglSurface != null) {
            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
            view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
            mEglSurface = null;
        }
    }
  • 完整释放(finish()

    不仅销毁表面,还会销毁EGL上下文(通过EGLContextFactory)并终止显示设备,彻底释放所有EGL资源:

    java 复制代码
    public void finish() {
        if (mEglContext != null) {
            view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
            mEglContext = null;
        }
        if (mEglDisplay != null) {
            mEgl.eglTerminate(mEglDisplay);
            mEglDisplay = null;
        }
    }

6. 错误处理机制

EGL操作可能因配置错误、资源不足等原因失败,EglHelper提供了完善的错误处理:

  • throwEglException():将EGL错误码转换为可读信息并抛出运行时异常,终止异常流程。
  • logEglErrorAsWarning():将错误记录为警告日志,适用于非致命错误(如表面暂时不可用)。
  • formatEglError():将错误码与操作名称组合为格式化字符串(如"eglMakeCurrent failed: EGL_BAD_SURFACE"),便于调试。

GLThread实现

内部的GLThread类则是实现异步渲染、线程隔离和资源管理的核心。

GLThread的定位与核心职责

GLThreadGLSurfaceView的私有静态内部类,继承自Thread,专门负责OpenGL渲染的所有操作。其核心职责包括:

  • 管理OpenGL渲染的独立线程,与UI线程隔离,避免渲染操作阻塞UI响应
  • 协调EGL上下文(EGLContext)和渲染表面(EGLSurface)的创建、绑定与销毁
  • 触发渲染器(Renderer)的生命周期回调(onSurfaceCreatedonSurfaceChangedonDrawFrame
  • 处理外部事件(如queueEvent提交的任务)和渲染请求(requestRender
  • 维护线程间同步,处理暂停/恢复、Surface创建/销毁等状态变化

通过这些职责,GLThread将复杂的渲染逻辑封装在独立线程中,为开发者提供了简洁的上层接口。

核心成员

GLThread的成员变量可分为状态标识资源引用同步工具三类,共同支撑渲染线程的运行逻辑:

类型 关键变量 作用描述
状态标识 mPausedmHasSurface 标记当前是否暂停、是否持有有效的Surface(渲染载体)
状态标识 mHaveEglContextmHaveEglSurface 标记EGL上下文和表面是否已创建并可用
状态标识 mRequestRendermRenderMode 标记是否需要触发渲染、渲染模式(持续渲染/按需渲染)
资源引用 mEglHelper 用于管理EGL资源的辅助类(上下文、表面等)
资源引用 mGLSurfaceViewWeakRef 弱引用指向GLSurfaceView,避免内存泄漏同时获取配置信息
同步工具 sGLThreadManager 全局锁对象,用于线程间同步(wait/notify)
事件队列 mEventQueue 存储需在GL线程执行的外部任务(通过queueEvent提交)

这些变量通过sGLThreadManager的同步机制进行保护,确保多线程环境下的状态一致性。

核心流程

1. 初始化与启动:run()guardedRun()

GLThread的生命周期从run()方法开始,其核心逻辑封装在guardedRun()中:

java 复制代码
@Override
public void run() {
    setName("GLThread " + getId());
    try {
        guardedRun(); // 核心逻辑入口
    } catch (InterruptedException e) {
        // 正常退出
    } finally {
        sGLThreadManager.threadExiting(this); // 线程退出清理
    }
}

guardedRun()是渲染线程的主循环,负责初始化EGL辅助工具、维护渲染循环、处理状态变化和事件:

java 复制代码
private void guardedRun() throws InterruptedException {
    mEglHelper = new EglHelper(mGLSurfaceViewWeakRef); // 初始化EGL辅助类
    // 初始化状态变量...
    while (true) { // 无限循环,直到收到退出信号
        synchronized (sGLThreadManager) {
            // 状态检查与更新(暂停、Surface变化、EGL资源释放等)
        }
        // 执行渲染或事件处理
    }
}
2. 核心循环:状态管理与渲染触发

guardedRun()中的无限循环是GLThread的核心,可分为同步块内的状态处理同步块外的渲染执行两部分。

(1)同步块内:状态检查与准备

同步块(synchronized (sGLThreadManager))内主要处理线程状态更新和渲染准备,关键逻辑包括:

  • 退出检查 :若mShouldExittrue,直接退出循环终止线程。

  • 事件队列处理 :若mEventQueue非空,取出事件并在同步块外执行(确保事件在GL线程运行)。

  • 暂停状态更新 :根据mRequestPaused更新mPaused,并通知其他线程状态变化。

  • EGL资源释放 :当需要释放EGL上下文(mShouldReleaseEglContext)或上下文丢失(lostEglContext)时,调用stopEglSurfaceLocked()stopEglContextLocked()释放资源。

  • Surface状态处理 :当Surface丢失(!mHasSurface)时,释放EGL表面;当Surface重新获取(mHasSurface)时,更新状态并通知等待线程。

  • 渲染就绪判断 :通过readyToDraw()判断是否满足渲染条件:

    java 复制代码
    private boolean readyToDraw() {
        return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
            && (mWidth > 0) && (mHeight > 0)
            && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY));
    }

    即:非暂停、有有效Surface、尺寸有效、且需要渲染(主动请求或持续模式)。

  • EGL资源准备 :若就绪且无EGL上下文,通过mEglHelper.start()创建上下文;若有上下文但无表面,标记需创建表面(createEglSurface = true)。

(2)同步块外:渲染执行与回调触发

当同步块内完成准备后,同步块外执行实际渲染操作,步骤如下:

  • 执行外部事件 :若从事件队列取出event,执行事件(如开发者通过queueEvent提交的任务)。
  • 创建EGL表面 :若createEglSurfacetrue,调用mEglHelper.createSurface()创建与Surface绑定的EGL表面,失败则标记表面无效。
  • 创建GL接口 :通过mEglHelper.createGL()获取GL10实例,供渲染器使用。
  • 触发渲染器回调
    • 若上下文刚创建(createEglContext),调用onSurfaceCreated通知渲染器上下文就绪。
    • 若尺寸变化(sizeChanged),调用onSurfaceChanged通知渲染器尺寸更新。
    • 调用onDrawFrame执行实际绘制逻辑。
  • 交换缓冲 :通过mEglHelper.swap()交换前后缓冲,将绘制结果显示到屏幕。
  • 错误处理:若交换缓冲失败,根据错误码处理(如上下文丢失则标记需重建)。
3. 线程同步机制

GLThread通过sGLThreadManager(全局锁对象)实现线程间同步,核心机制包括:

  • 等待(wait) :当不满足渲染条件时,通过sGLThreadManager.wait()让线程进入等待状态,释放CPU资源。
  • 通知(notifyAll) :当状态变化(如Surface创建、渲染请求)时,调用notifyAll()唤醒等待线程重新检查状态。
  • 状态保护 :所有状态变量(如mPausedmHaveEglContext)的读写均在同步块内进行,避免多线程竞争导致的状态不一致。

例如,当UI线程调用requestRender()时,会在同步块内设置mRequestRender = true并通知GLThread

java 复制代码
public void requestRender() {
    synchronized(sGLThreadManager) {
        mRequestRender = true;
        sGLThreadManager.notifyAll(); // 唤醒GLThread检查渲染条件
    }
}
4. 渲染模式与触发逻辑

GLThread支持两种渲染模式(通过mRenderMode控制):

  • RENDERMODE_CONTINUOUSLY :持续渲染,只要满足readyToDraw()条件,就不断执行onDrawFrame
  • RENDERMODE_WHEN_DIRTY :按需渲染,仅当requestRender()被调用(mRequestRender = true)时才触发渲染。

两种模式的切换通过setRenderMode()实现,内部通过更新mRenderMode并通知线程生效。

5. 资源释放与生命周期管理

GLThread在多种场景下会释放EGL资源,确保内存安全:

  • 暂停时 :若preserveEGLContextOnPausefalse(默认),则释放EGL上下文和表面。
  • Surface销毁时:释放EGL表面(因Surface是EGL表面的载体)。
  • 上下文丢失时 :当eglSwapBuffers返回EGL_CONTEXT_LOST,释放旧上下文并重建。
  • 线程退出时 :在finally块中调用stopEglSurfaceLocked()stopEglContextLocked()彻底释放资源。

与渲染器(Renderer)的交互

GLThreadRenderer回调的直接触发者,三者的交互关系如下:

  1. GLSurfaceView通过setRenderer()注册渲染器实例。
  2. GLThread在EGL上下文创建后,调用renderer.onSurfaceCreated()
  3. 当Surface尺寸变化时,调用renderer.onSurfaceChanged()
  4. 每次渲染循环中,调用renderer.onDrawFrame()执行绘制逻辑。

这种设计将渲染逻辑与线程管理解耦,开发者只需实现Renderer接口即可专注于绘制逻辑,无需关注线程和EGL细节。

总结

GLSurfaceViewSurfaceView基础上,通过以下核心扩展实现了对OpenGL渲染的全面支持:

  1. 封装EGL上下文、配置和表面管理,屏蔽底层图形接口细节
  2. 内置渲染线程(GLThread),实现渲染与UI线程的解耦
  3. 定义Renderer接口,分离渲染逻辑与视图管理
  4. 提供灵活的渲染模式、生命周期管理和调试工具
相关推荐
AOwhisky2 小时前
Python 学习笔记(第一期与第二期)——基础语法——核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发
STLearner3 小时前
ICML 2026 | LLM×Graph论文总结[1]【图基础模型,文本属性图,多模态属性图,图对齐,图提示学习,关系深度学习
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
什巳5 小时前
JAVA练习278- 和为 K 的子数组
java·学习·算法·leetcode
louisgeek5 小时前
Android Studio 和 Git
android
碎光拾影5 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
我命由我123457 小时前
复利极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
AOwhisky7 小时前
Python 学习笔记(第三期)——流程控制核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发·流程控制
tyqtyq227 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
小心亦新8 小时前
STM32学习13 定时器1中断
stm32·嵌入式硬件·学习
我命由我123459 小时前
72 法则极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法