Android中SurfaceView使用详解

SurfaceView 与 TextureView 详解 播放视频或者渲染其他的动画的时候,有两个 View 组件可供选择,SurfaceView 和 TextureView。在 Android 7.0 上系统 Surfaceview 的性能比 TextureView 更有优势,支持对象的内容位置和包含的应用内容同步更新,平移、缩放不会产生黑边。 在7.0以下系统如果使用场景有动画效果,可以选择性使用TextureView。 阅读详情
AI摘要

本文详细介绍了Android中的SurfaceView组件,解释了其与普通View的区别,包括多线程绘图能力、双缓冲机制等特性,并提供了使用实例,如绘制正弦曲线和实现绘图板功能。

1.什么是SurfaceView?

    Surface意为表层、表面,顾名思义SurfaceView就是指一个在表层的View对象。为什么说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在“表层”的上面,而它就是充当“表层”本身。SDK的文档 说到:SurfaceView就是在窗口上挖一个洞,它就是显示在这个洞里,其他的View是显示在窗口上,所以View可以显式在 SurfaceView之上,你也可以添加一些层在SurfaceView之上。 从API中可以看出SurfaceView属于View的子类 它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和3D的效果都可以实现。创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,比如:SurfaceView的改变 、SurfaceView的创建 、SurfaceView 销毁等,我们可以在相应的方法中做一些比如初始化的操作或者清空的操作等等。

Android系统提供了View进行绘图处理,我们通过自定义的View可以满足大部分的绘图需求,但是这有个问题就是我们通常自定义的View是用于主动更新情况的,用户无法控制其绘制的速度,由于View是通过invalidate方法通知系统去调用view.onDraw方法进行重绘,而Android系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16ms,如果在16ms内View完成不了执行的操作,用户就会看着卡顿,比如当draw方法里执行的逻辑过多,需要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。而SurfaceView相当于是另一个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。

2.如何使用SurfaceView?

        首先SurfaceView也是一个View,它也有自己的生命周期。因为它需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶 段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。  

SurfaceView它的绘制原理是绘制前先锁定画布(获取画布),然后等都绘制结束以后在对画布进行解锁 ,最后在把画布内容显示到屏幕上。       

通常情况下,使用以下步骤来创建一个SurfaceView的模板:

(1)创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口:SurfaceHolder.Callback和Runnable.代码如下:

	
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable

通过实现这两个接口,就需要在自定义的SurfaceView中实现接口的方法,对于SurfaceHolder.Callback方法,需要实现如下方法,其实就是SurfaceView的生命周期:

   @Override
    public void surfaceCreated(SurfaceHolder holder) {
      

    }
   @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
   
    }

对于Runnable接口,需要实现run()方法,

 @Override
    public void run() {

}

(2)初始化SurfaceView

在自定义的MySurfaceView的构造方法中,需要对SurfaceView进行初始化,包括SurfaceHolder的初始化、画笔的初始化等。在自定义的SurfaceView中,通常需要定义以下三个成员变量:

    private SurfaceHolder mHolder;
    private Canvas mCanvas;//绘图的画布
    private boolean mIsDrawing;//控制绘画线程的标志位
  SurfaceHolder,顾名思义,它里面保存了一个对Surface对象的引用,而我们执行绘制方法本质上就是操控Surface。SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期。(说到底 SurfaceView的生命周期其实就是Surface的生命周期)例如使用 SurfaceHolder来处理生命周期的初始化。

初始化代码如下:

 public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
    }

    private void initView() {
        mHolder = getHolder();//获取SurfaceHolder对象
        mHolder.addCallback(this);//注册SurfaceHolder的回调方法
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }

(3)使用SurfaceView

通过SurfaceHolder对象的lockCanvans()方法,我们可以获取当前的Canvas绘图对象。接下来的操作就和自定义View中的绘图操作一样了。需要注意的是这里获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此,之前的绘图操作都会被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。

绘制的时候,在surfaceCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停的进行绘制,在绘制的逻辑中通过lockCanvas()方法获取Canvas对象进行绘制,通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。整体代码模板如下:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView  extends SurfaceView
        implements SurfaceHolder.Callback, Runnable {

    // SurfaceHolder
    private SurfaceHolder mHolder;
    // 用于绘图的Canvas
    private Canvas mCanvas;
    // 子线程标志位
    private boolean mIsDrawing;

    public MySurfaceView(Context context) {
        super(context);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        //mHolder.setFormat(PixelFormat.OPAQUE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder,
                               int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
        }
    }
    //绘图操作
    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            // draw sth绘制过程
        } catch (Exception e) {
        } finally {
            if (mCanvas != null)
                mHolder.unlockCanvasAndPost(mCanvas);//保证每次都将绘图的内容提交
        }
    }
}

这里说一个优化的地方,这就是在run方法中。

在我们的draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环draw() 耗费了1000毫秒 ,第二次循环draw() 耗时2000毫秒。很明显这样就会造成运行刷新时间时快时慢,可能出现卡顿现象。为此最好保证每次刷新的时间是相同的,这样可以保证整体画面过渡流畅。

 	/**每30帧刷新一次屏幕**/
        public static final int TIME_IN_FRAME = 30;
	@Override
	public void run() {
	    while (mIsRunning) {
 
		/**取得更新之前的时间**/
		long startTime = System.currentTimeMillis();
 
		/**在这里加上线程安全锁**/
		synchronized (mSurfaceHolder) {
		    /**拿到当前画布 然后锁定**/
		    mCanvas =mSurfaceHolder.lockCanvas();
		    draw();
		    /**绘制结束后解锁显示在屏幕上**/
		    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
		}
 
		/**取得更新结束的时间**/
		long endTime = System.currentTimeMillis();
 
		/**计算出一次更新的毫秒数**/
		int diffTime  = (int)(endTime - startTime);
 
		/**确保每次更新时间为30帧**/
		while(diffTime <=TIME_IN_FRAME) {
		    diffTime = (int)(System.currentTimeMillis() - startTime);
		    /**线程等待**/
		    Thread.yield();
		}
 
	    }
	}
这里说一下Thread.yield(): 与Thread.sleep(long millis):的区别:

Thread.yield(): 是暂停当前正在执行的线程对象 ,并去执行其他线程。

Thread.sleep(long millis):则是使当前线程暂停参数中所指定的毫秒数然后在继续执行线程。

3.SurfaceView的使用实例
(1)正弦曲线

要绘制一个正弦曲线,只需要不断修改横纵坐标的值,并让他们满足正弦函数即可。因此,我们需要一个Path对象来保存正弦函数上的坐标点,在子线程的while循环中,不断改变横纵坐标值。代码如下:

public static final int TIME_IN_FRAME = 30;
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
         while(mIsDrawing){
             draw();
//             x+=1;
//             y=(int)(100*Math.sin(x*2*Math.PI/180)+400);
//             mPath.lineTo(x,y);
         }
        
        /**取得更新结束的时间**/
        long endTime = System.currentTimeMillis();

        /**计算出一次更新的毫秒数**/
        int diffTime  = (int)(endTime - startTime);

        /**确保每次更新时间为30帧**/
        while(diffTime <=TIME_IN_FRAME) {
            diffTime = (int)(System.currentTimeMillis() - startTime);
            /**线程等待**/
            Thread.yield();
        }
    }

(2)画图板

我们也可以通过使用SurfaceView来实现一个简单的绘图板,绘图的方法与View中进行绘图所使用的方法一样,也是通过Path对象记录手指滑动的路径来进行绘图。在SurfaceView的onTouchEvent()方法中记录Path路径,代码如下所示:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                break;

        }
        return true;//表示此View拦截处理触摸事件
    }
并在draw方法中进行绘制:

 private void draw() {
        try{
            mCanvas=mHolder.lockCanvas();//获取Canvas对象进行绘制
            //SurfaceView背景
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath,mPaint);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (mCanvas!=null){
                mHolder.unlockCanvasAndPost(mCanvas);//保证绘制的画布内容提交
            }
        }
    }

4.SurfaceView和View的区别

总的归纳起来SurfaceView和View不同之处有:

1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。

2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。

3. 它执行动画的效率比View高,而且你可以控制帧数。

4. SurfaceView在绘图时使用l了双缓冲机制,而View没有。



参考文献:

《Android群英传》徐宜生著,第6章





Android开发系列:高性能视图组件Surfaceview 在Android应用开发领域,面对视频播放、游戏构建及相机实时预览等高性能需求场景,直接操控图像数据并即时展示于屏幕成为必要条件。性能瓶颈:传统View的绘制任务由UI主线程承担,如果绘制操作过于复杂或需要频繁刷新,就可能导致主线程阻塞,进而影响界面的响应速度和用户交互体验。视觉瑕疵:传统View组件缺乏双缓冲技术的支持,View直接屏幕绘制易引发画面闪烁及图像撕裂。效果局限:传统View组件基于视图层次结构,每个View都被视为一个矩形区域,这使得实现不规则形状、透明度变化等复杂视觉效果变得相对困难。 阅读详情

相关推荐

反思:Google 为何把 SurfaceView 设计的这么难用?

的绘图表面所使用的图形缓冲区是完全由摄像头服务或者视频播放服务来提供的,因此,我们就不可以随意地去访问该图形缓冲区,而是要由摄像头服务或者视频播放服务来访问,因为该图形缓冲区有可能是在专门的硬件里面分配的。但在实际开发中,随着业务场景复杂度的上升,仍然有部分场景是无法被满足的,比如:在页面的最上方播放一条全屏的视频广告。相关的操作,放在不同的线程中执行,而多线程对这一块内存区域的读写,势必会引发内部状态的混乱。的设计中,设计者还考虑到了音视频渲染时,字幕相关业务的场景,因此额外提供了一个。

Trouvailless的博客 1554

安卓surfaceview的使用方式

surfaceview内部机制和外部层次结构在安卓开发中,我们经常会遇到一些需要高性能、高帧率、高画质的应用场景,例如视频播放、游戏开发、相机预览等。这些场景中,我们需要直接操作图像数据,并且实时地显示到屏幕上。view组件是在主线程中进行绘制的,如果绘制过程耗时过长或者频繁刷新,可能会导致主线程阻塞,影响用户交互和界面响应。view组件在绘制时没有使用双缓冲机制,也就是说每次绘制都是直接在屏幕上进行的,这可能会导致绘制过程中出现闪烁或者撕裂的现象。

Makenna's World 5517

Android SurfaceView的基本使用

一、为什么要SurfaceView 大多数情况下我们的自定义View都会选择去继承View或ViewGroup来实现,但是为什么系统还要为我们提供一个SurfaceView呢? 首先我们知道View类如果需要更新视图,必须我们主动的去调用invalidate()或者postInvalidate()方法来再走一次onDraw()完成更新。但是呢,Android系统规定屏幕的刷新间隔为16ms,如果这个View在16ms内更新完毕了,就不会卡顿,但是如果逻辑操作太多,16ms内没有更新完毕,剩下的操作就会丢到

liangtianmeng的专栏 975

【Android音视频开发】【013】SurfaceView生命周期

Activity.onCreate执行 SurfaceView创建SurfaceHolder,创建Surface 同时执行SurfaceHolder.Callback.surfaceCreated和SurfaceHolder.Callback.surfaceChanged 注意了,首次创建Surface,是会同时执行两个回调的 SurfaceView大小发生变化,或发生横竖屏切换 执行SurfaceHolder.Callback.surfaceChanged Activity.onPause执行 销毁Sur

天天向上 Up Up Up (*^▽^*) 1322

重带电粒子的能量歧离(energy straggling)

能量歧离,Bethe-Bloch

G751888629的博客 2298

Win10蓝屏代码critical process died的解决方法

critical process died”蓝屏错误是Win10操作系统中较为常见的问题之一。当遇到这一问题时,可以尝试通过修复当前系统、检查硬盘槽位或重新安装系统等方法来解决。本文详细介绍了这三种方法的操作步骤和注意事项,希望能帮助大家摆脱这一困扰。同时,也建议大家在日常使用中注意系统的维护和更新,以减少类似问题的发生。

mmoo_python的博客 9533

Android SurfaceView 的使用

SurfaceView 的作用: SurfaceView 适用被动更新,如频繁的刷新,在子线程刷新不会阻塞主线程,适用于界面频繁更新 (View适用主动更新) SurfaceView可以控制刷新频率。 SurfaceView底层利用双缓存机制,绘图时不会出现闪烁问题。 SurfaceView 的使用方法: 一般都是view继承SurfaceView,实现SurfaceHolder.Callback接口 会自动生成3个方法如下 @Override public void surfaceC

小牧在一直在学习,在前进的道路上大家一起学习,进步。 4975

41页PPT | 华为业务流程架构全景视图:全业务域L1-L3级流程全案

TBD、收集和生成线索、验证机会点、TBD、管理销售授权、管理销售项目、管理销售项目群、管理合同要素/模板、验证和分发线索、标前引导、管理开票和回款、客户网络评估、管理销售评审、TBD、管理交付项目群、管理合同文档、管理结构化合同信息、跟踪和培育线索、制定并提交标书、管理合同/PO变更、客户网络规划、管理销售决策。:4.1 管理技术、服务请求、4.2管理备件、服务交付、4.3管理非技术、服务请求、4.4管理客户、投诉。:管理Idea、产生Idea、激发兴趣与协作、孵化、原型和验证、确认Idea与商业化。

zuoan1993的博客 4548

android surfaceview bitmap,Android使用SurfaceView绘制静态图像

使用 SurfaceView 绘图需要为 SurfaceView 对象添加 SurfaceHoloder.Callback 接口,并在该接口的 surfaceCreated() 方法中通过 lockCanvas() 方法获取 Canvas 对象,以此保证当获取 Canvas 时,SurfaceView 对象可用。当绘图工作完成后,通过 SurfaceHoloder.unlockCanvas-And...

weixin_30382087的博客 1668

ArcMap新手必看:Shape属性中的点ZM值到底是什么?如何快速处理

本文深入解析ArcMap中Shape属性中的点ZM值,揭示其在GIS数据中的隐藏维度作用,包括Z值(高程值)和M值(测量值)。针对ZM值可能引发的服务发布问题,提供三种高效处理方案,帮助新手快速掌握数据导入时的ZM值管控策略及精准去除方法。

weixin_42561476的博客 431

【Go语言实战】(26) 分布式搜索引擎

基于go语言的分布式搜索引擎

面向生活编程 2379

Android SurfaceView类的使用

Android提供了View进行绘图处理,View可以满足大部分绘图需求,但在某些时候,却也有心有余而力不足的地方,View是通过刷新来绘制视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新时间间隔为16ms。但是如果在16ms内完成所需执行的操作用户不会感觉到卡顿,而如果执行的操作逻辑太多,特别是频繁的刷新界面,就会不断的阻塞主线程,导致画面卡顿。  Android提供了Su

WannerWang的博客 1031

喷涂机器人系列编程:ABB IRB 5500_(9).常见故障诊断与排除

ABB IRB 5500 喷涂机器人在实际应用中可能会遇到各种故障,但通过系统的诊断和排除方法,可以确保生产线的连续运行。定期维护和保养是预防故障的重要手段,而操作培训和规范则能有效减少人为因素引起的故障。希望本节内容能帮助操作和维护人员提高故障诊断和排除的效率,确保喷涂机器人的稳定运行。如果您有任何疑问或需要进一步的帮助,请联系 ABB 客户服务中心。

zhubeibei168的博客 1044

安卓(Android)surfaceview无法使用gesturedetector的解决办法

因为课程项目里用到了surfaceview并且需要在上面响应安卓的手势

Craigm的专栏 1726

10|Git Diff 与增量代码识别:本次到底改了哪些代码?

本文介绍 Git Diff 和增量代码识别,帮助你理解精准测试如何先找到本次真正变化的代码。测试开发工程师。Java 后端开发工程师。质量平台 / DevOps 平台建设者。想了解精准测试落地方法的技术负责人。

专注 AI 精准测试平台实战落地,分享从代码 Diff 到回归决策的完整工程闭环。主张“先建数据链路,再上 AI 智能”,让测试从“靠经验”走向“基于风险”。 160

【OpenClaw从入门到精通】第35篇:电商“龙虾”掘金记——用OpenClaw实现小红书/抖音/淘宝全自动矩阵运营(2026实测版)

2026年,OpenClaw智能体已成为电商运营的“效率革命工具”,其多平台自动化能力让单人可完成传统3-5人的工作量。本文基于真实商业案例(来源:海报新闻、阿里云开发者社区等),系统拆解OpenClaw在小红书、抖音、淘宝的全链路运营方案:从阿里云部署搭建基础环境,到三大平台核心技能安装配置,再到跨平台矩阵工作流落地;同时揭秘代部署、模板售卖、轻定制、内容套利四大变现模式,并详解Token成本控制、安全风险规避等关键问题。文中所有代码均经过2026年3月实测验证,提供完整可复现的技术方案,帮助读者快速搭建

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 2034

Android中SurfaceView的使用示例

SurfaceView在游戏开发中有着举足轻重的地位,它对于画面的控制有着更大的自由度(不像View要用handler来更新,关于View的),但这方面的参考资料并不是太多,能找到的例子都有点喧宾夺主的感觉,不能把使用的流程清晰展示出来,下面是个简单的示例,力求把流程清楚展示,其他的可简则简。 程序效果:用线程画一个蓝色的长方形。Java代码 <a title="复制代码"

乐吾天 3245

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Android的SurfaceView使用

Android的大多数控件都是继承自View的,因此在自定义控件时一般也是继承View类,但是对于高效的,游戏级别的绘图,或者是播放器等要求比较高的地方,普通的View类就 有点吃不开了,这个时候就要用到SurfaceView类。 因为比较高级,所以就该装B,单纯一个继承

lanpy88的专栏 2215

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Android中使用SurfaceView和Canvas来绘制动画

Canvas

闷骚的Gopher 9131

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

android surfaceview的简单使用,Android SurfaceView的基本使用

一、为什么要SurfaceView大多数情况下我们的自定义View都会选择去继承View或ViewGroup来实现,但是为什么系统还要为我们提供一个SurfaceView呢?首先我们知道View类如果需要更新视图,必须我们主动的去调用invalidate()或者postInvalidate()方法来再走一次onDraw()完成更新。但是呢,Android系统规定屏幕的刷新间隔为16ms,如果这个V...

weixin_35755640的博客 563

Android开发简单使用surfaceview

在安卓开发中自定义View是比较普遍使用的,而且对于希望从事安卓开发的程序员来说,自定义View也应该是最安卓开发部分最难的,可能一个程序猿在两年内都在跟学习自定义view打交道。好了,废话不多说,我们来简单的使用一下surfaceview,这里要说明的是surfaceview是运行在子线程的,一般在游戏中使用较多。请看下面的代码: package com.example.surface; i

oDaBenNiu的博客 860

Android中SurfaceView的使用详解

Android中SurfaceView的使用详解 http://blog.youkuaiyun.com/listening_music/article/details/6860786 Android NDK开发 native层获取Surface并显示图像 思路过程 http://www.cnblogs.com/haithink/archive/2013/03/12/2956487.html ...

weixin_30902251的博客 157
上一篇: 华为2016研发工程师编程题---删数
下一篇: Android自定义控件之3D上下翻页效果的倒计时控件
android_cmos
博客等级 码龄12年 177粉丝 73原创
评论 10
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值