Android多线程之 HandlerThread 源码分析

Android多线程之HandlerThread详解 一 HandlerThread介绍 当系统中有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Android 提供了 HandlerThread,HandlerThread 是在线程中创建一个 Looper 循环器,让 Looper 轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。 二 工作原理 HandlerThread 继承 Thread,在 Thread 开始执 阅读详情

Android多线程之 HandlerThread 源码分析

一、前言

这主要是一个讲解 Android 中多线程的系列,全部文章如下:

前面写了 Android 中的消息机制,也就是 Handler 的消息机制。不了解的可以去看下 Handler系列—源码分析。

本章就来使用和分析下 HandlerThread。

二、什么是 HandlerThread?

我们看下官方的描述:

可以看到 HandlerThread 是继承于 Thread 类的,说明我们可以使用它开启一个子进程去执行一些操作。

再看下官方说明:

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

这个类可以方便的开启一个带有 Looper 的新线程。这个 looper 可以去创建一个 Handler,但是还是必须得调用 start() 方法。

我们大概知道 HandlerThread 更加方便了我们在 Android 中进行多线程消息传递。

下面我们通过实例了解下:

public class HandlerThreadTestActivity extends AppCompatActivity {

    private static final String TAG = "HandlerThreadTest";

    private TextView mShow;

    private HandlerThread mHandlerThread = new HandlerThread("MyHandlerThread");

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread_test);
        mShow = findViewById(R.id.show);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message message) {
                super.handleMessage(message);
                final int what = message.what;
                mShow.setText("得到的 what 为: "+what );
                Log.d(TAG, "handleMessage: " + message.what);
            }
        };
        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessage(mHandler.obtainMessage(1));
            }
        });

        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessage(mHandler.obtainMessage(2));
            }
        });

        findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessage(mHandler.obtainMessage(3));
            }
        });
    }
}

运行程序,不出意外会得到如下异常:

原因已经很清楚了,我们在子线程中做了更新 UI 的操作。

下面来分析下原因:

由于 HandlerThread 是继承于 Thread 的,所以在实例化的 HandlerThread 对象是一个子线程,我们在创建 Handler 的时候,传入了 mHandlerThread 对象所在的线程的 Looper 对象,一个线程中只能有一个 Looper 对象,进而使 mHandler 和 子线程进行了绑定,所以这个 mHandler 是属于 mHandlerThread 的。

大家都知道只有主线程的 Handler 对象才能够进行更新 UI 的操作,所以在 mHandler 中执行 UI 操作时肯定不行的。

所以我们需要这样写:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        mShow.setText("得到的 what 为: "+what );
    }
});

代码 才能真正的运行。

通过上面的分析,大家应该能够知道 HandlerThread 是什么了。

  • 由于继承于 Thread ,所以 HandlerThread 就是一个线程类;
  • HandlerThread 内部拥有一个 Looper 循环,可以直接传递给 Handler 使用

其实 HandlerThread 就是一个包含了 Looper 的 Thread,可以免去我们在子线程中创建 Handler 的时候写一些多余的代码。

比如我们在上篇文章的 2-1 章节 章节中创建一个子线程中的 Handler :

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler threadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}).start();

使用了 HandlerThread 以后我们只需要:

private HandlerThread mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();
Handler threadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

这样看上去是不是清晰了许多?就是省去了创建 Looper 和 开启 loop 的操作

三、HandlerThread 解析

由于这个类的代码很少,我们都看下。

1.成员变量和构造方法

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
}

可以看到,HandlerThread 的成员变量很简单,

  • mPriority 是优先级
  • mTid 是线程的标示符,主要和线程的优先级有关
  • mLooper 是内部持有的 Looper 对象
  • mHandler 是与之绑定的 Handler 对象

构造方法也只有两个:

  1. 第一个构造函数
    第一个只需要传入线程的名称就行了,会把线程的优先级设置了默认的

  2. 第一个构造函数
    第二个则根据自己的需要传入线程的名称和优先级,根据注释可以知道,这个优先级必须是从 android.os.Process 类中获取,不能从 Thread 类中获取

2、onLooperPrepared 方法

继续往下看,会看到一个空实现的方法

/**
 * Call back method that can be explicitly overridden if needed to execute some
 * setup before Looper loops.
 */
protected void onLooperPrepared() {
}

根据注释可以知道,这个方法是给我们在创建 Looper 之后,开启 Looper 循环之前的一些操作使用的。

也就是说在 Looper.prepare() 之后 ,在 Looper.loop() 之前需要做的操作,写在这里,上一篇 Handler 源码解析的文章有写到,这里就不多说了。

3、核心 run 方法

由于继承于 Thread,要重写 Thread 的核心方法 run() 方法。

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

这里首先会调用 Looper.prepare() 创建一个新的 Looper 对象,关于里面的内容,如果看过上篇文章,应该就知道了。

创建完 Looper 以后,通过 Looper 的 Looper.myLooper() 赋值给其成员变量 mLooper。

Process.setThreadPriority(mPriority); 就是设置线程的优先级,
onLooperPrepared(); 就是上面的空方法
Looper.loop(); 开启 Looper 循环,不断的查看 MessageQueue 中有没有新消息
最终执行完以后把 mTid置为 -1。

可能你发现了 notifyAll(); 这个方法,那么是干嘛的呢?其实我在 Java多线程 有讲到:

Object 类中提供了 wait() 方法,导致当前线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法,才会继续执行。

其实这个地方的 notifyAll() 和下面的 getLooper() 代码是有关的。

4. 获取 Looper

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    
    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

代码中先判断了是线程是否存活,如果不存活,就直接返回 null.

然后我们就看到了的上面提到的 wait() 方法,也就是说如果这个线程没有执行 run() 方法之前,就去调用 getLooper() 是拿不到的。只有执行了 run() 方法了以后,执行了 notifyAll 才会继续解除这里的 wait 等待,才能正确的获取到 mLooper 对象。

5. 其他方法

@NonNull
public Handler getThreadHandler() {
    if (mHandler == null) {
        mHandler = new Handler(getLooper());
    }
    return mHandler;
}

getThreadHandler 获取一个和当前线程关联的 Handler 对象

public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}

quit 退出 Handler 线程的循环

public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely();
        return true;
    }
    return false;
}

quitSafely 安全的退出 Handler 线程的循环

上面的 quit 和 quitSafely 都调用了 MessageQueue 中的方法:

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
}
  1. quit 调用了 removeAllMessagesLocked();

    作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。

  2. quitSafely 调用了 removeAllFutureMessagesLocked()

    该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

无论调用了 quit 还是 quitSafely,线程中的 Looper 都不会接受新的消息了,消息队列终结,这时候再通过 Handler 调用 sendMessage 或 post 等方法发送消息时均返回 false ,表示消息没有成功放入消息队列 MessageQueue 中,因为消息队列已经退出了。

四、小结

至此 HandlerThread 的源码已经分析完了,相对来说 HandlerThread 就是做了一些封装,理解和用起来都是不难的。

下篇文章看下 AsyncTask。

告别代码移植烦恼:STM32CubeMX 6.4.0 + STM32F407ZGT6 + YT8512C PHY芯片的LWIP网络配置全攻略 本文详细介绍了如何使用STM32CubeMX 6.4.0配置STM32F407ZGT6与YT8512C PHY芯片的LWIP网络适配,包括硬件特性解析、STM32CubeMX基础配置、专用驱动适配和LWIP协议栈定制化配置,帮助开发者解决PHY芯片差异导致的网络问题。 阅读详情

相关推荐

WPS用户看过来:避开格式错乱坑,用WPS Office完美制作红头文件(2024最新版)

本文详细解析了WPS Office与Word在红头文件制作中的核心差异,提供了2024年最新版的零误差实战指南。从字体处理、页面布局到红头制作关键步骤,帮助用户避开格式错乱坑,确保跨设备显示一致。特别针对WPS用户优化,解决字体丢失、版式错位等常见问题。

weixin_26800111的博客 214

HandlerThread 简介

可以使用HandlerThread来处理本地的IO读写的工作,因为本地IO操作大多数耗时都是毫秒级的,所以对于HandlerThread这种单线程 + 队列的形式,不会产生阻塞。优先级越高的线程,获的的CPU资源越多,反之越少。所以如果其中某一个线程 任务执行时间过长,就会导致后续的任务都会被延迟处理。由于HandlerThread是单线程同步队列的,所以不适合处理网络IO的操作。是没有必要设置这么高的优先级的,因而需要我们根据需求,来设置合理的优先级别。线程的优先级,一定程度的优化线程。

m0_49508485的博客 828

C# 开发 WPS 插件实战:从环境搭建到功能实现(附完整代码)

本文提供了一份详尽的C#开发WPS插件实战指南,涵盖从环境搭建到功能实现的完整流程。通过COM技术,开发者可以创建自定义插件,为WPS Office扩展自动化功能,如文档处理与格式转换。文章包含核心概念解析、Ribbon界面集成、调试部署方法及完整代码示例,帮助开发者快速上手。

rust6ferris的博客 927

HandlerThread的使用方法和原理

我们如果想要在子线程中使用Handler,必须要手动创建一个Looper(原理已经在前面关于Handler和Looper的博客中介绍过:http://blog.youkuaiyun.com/whsdu929/article/details/52487605):new Thread(){ @Override public void run() { Looper.prepare();

AndroidConqueror的博客 716

移相变压器Simulink建模与HVDC系统仿真实践

移相变压器是电力电子领域实现精确相位调节的核心设备,其通过特殊绕组设计改变输出电压相位角,在解决环网潮流不均、阻尼功率振荡等场景具有关键作用。本文基于MATLAB/Simulink环境,详细解析移相变压器的建模原理与技术实现,包括磁路耦合特性模拟、参数计算方法和控制策略设计。针对HVDC高压直流输电和FACTS柔性交流输电系统等典型应用,提供仿真模型搭建技巧与问题排查指南,特别涵盖新能源并网场景下的相位调节优化方案。通过实测数据验证,所建模型在稳态精度和动态响应方面均满足工程要求,为电力系统稳定性分析提供有

weixin_28278377的博客 270

从原理到调试:手把手教你用Matlab解码互补格雷码与相移码(附数据与常见错误排查)

本文详细介绍了如何使用Matlab解码互补格雷码与相移码,涵盖从原理到调试的全过程。通过实际案例和代码示例,帮助读者掌握结构光三维重建中的相位解包裹技术,并提供了常见错误排查与优化建议,提升解码效率与准确性。

weixin_29090917的博客 218

自定义工作线程 HandlerThread + new Handler(handlerThread.getLooper())

是一个继承自 Thread 的类,它在其内部初始化了一个 Looper,因此它是一个可以运行消息循环的线程。这意味着你在 HandlerThread 中创建的 Handler 可以处理其内部消息队列中的消息,而这些消息的处理将在 HandlerThread 所代表的工作线程中完成,而不是在主线程。HandlerThread + Handler 结合使用通常是在需要执行耗时任务(例如文件读写、大量计算、长时间等待的网络请求等),为了避免阻塞UI线程,会创建一个工作线程(如使用。

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

AI 大模型应用数据中心的数据清洗工具

1. 背景介绍 在人工智能大模型应用的浪潮中,数据清洗作为数据预处理的重要环节,对于提升模型性能和可靠性具有至关重要的作用。数据中心作为人工智能模型的运行环境,面临着海量数据流和多样化的数据类型,如何高效、准确地进行数据清洗,成为应用大模型的关键问题之一。本文将详细介绍AI大模型应用数据中心的数据清洗工具,包括核心概念、算法原理、具体操作步骤、应用场

SuperAGI2025 1803

Android HandlerThread用法

参考文章:http://www.cnblogs.com/hnrainll/p/3597246.html 一、HandlerThread简介 首先我们来看看为什么我们要使用HandlerThread? 在我们的应用程序当中为了实现同时完成多个任务,所以我们会在应用程序当中创建多个线程。为了让多个线程之间能够方便的通信,我们会使用Handler实现线程间的通信。 下面我们看看如何在线程当中

红娃子 5962

Three.js 完整实战项目:构建一个 3D 商品展示空间

Three.js 作为强大的 WebGL 框架,广泛应用于交互式 3D 场景开发,尤其适合创建沉浸式商品展示空间。本文将通过一个完整的实战项目,展示如何构建一个 3D 商品展示空间,涵盖项目架构、资源组织、多模型切换、交互热点绑定、移动端适配和帧率优化。项目以展示虚拟商品(如家具或电子产品)为核心,结合用户交互,基于 Vite、TypeScript 和 Tailwind CSS,支持 ES Modules,确保响应式布局,遵循 WCAG 2.1 可访问性标准。本文适合希望通过综合项目掌握 Three.js

zimin的专栏 1462

腾讯Android开发面试准备指南及参考答案

腾讯元宝高级Android开发职位面试准备指南 该职位要求扎实的Kotlin基础、架构设计能力和性能优化经验,重点关注Android客户端开发。面试流程约3轮,支持线上面试,流程较快。候选人需准备以下核心内容: 基础技术:Kotlin特性、Android开发生命周期、多线程编程 架构设计:MVVM/MVI模式应用、组件化解耦方案 性能优化:流畅度提升、内存管理、启动时间优化等实战案例 加分技能:Jetpack Compose开发、音视频处理、跨平台开发经验 建议结合项目经验准备具体案例,突出技术深度和问题解

郑伟强dev的专栏 1193

Android多线程:HandlerThread学习指南(含具体使用、原理 & 源码分析)

前言 多线程的应用在Android开发中是非常常见的,常用方法主要有: 继承Thread类 实现Runnable接口 Handler HandlerThread AsyncTask 今天,我将献上一份全面 & 详细的HandlerThread机制的学习指南,内容包括:定性认知、定量使用、工作原理 & 源码分析,希望你们会喜欢。 目录 1. 定义 一个Andro...

Carson带你学Android 2939

YOLOv11【第二章:核心模块进阶篇·第28节】DynamicHead动态检测头设计!

🏆 本文收录于专栏 《YOLOv11实战:从入门到深度优化》。本专栏围绕 YOLOv11 的改进、训练、部署与工程优化 展开,系统梳理并复现当前主流的 YOLOv11 实战案例与优化方案,内容目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。

**My Coding Family** 1万+

Android多线程-HandlerThread用法与源码解析

使用HandlerThread好处在Android开发中,我们经常使用多线程编程。而多线程有多种方式实现。其中我们用得最多、也是最早接触的是使直接开一个线程Thread。new Thread(){ @Override public void run() { //do something here } }.start();先抛开这样开线程的弊端,因为在有时做一些

codetend的博客 675

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

【Android+多线程】IntentService 知识总结:应用场景 / 使用步骤 / 源码分析

IntentService的定义、功能、应用场景、使用步骤、与Service和Thread的对比

qq_30885821的博客 719

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android 多线程编程:IntentService & HandlerThread

因为 Android 是使用 Java 开发的,所以当我们谈及 Android 中的多线程,必然绕不过 Java 中的多线程编程。但在这篇文章中,我们不会过多地分析 Java 中的多线程编程的知识。我们会在以后分析 Java 并发编程的时候分析 Java 中的多线程、线程池和并发 API 的用法。 我们先来总结一下 Android 多线程编程的演变过程:首先是 Java 的 Thread。因为本身...

你好,开发者 925

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Android基础 -- Android HandlerThread

1.简介 AndroidThread是一个Android已经封装好的轻量级异步类,用来方便开发者更快的实现多线程、异步通信、消息传递等。 2.使用步骤 // 步骤1:创建HandlerThread实例对象 // 传入参数 = 线程名字,作用 = 标记该线程 HandlerThread mHandlerThread = new HandlerThread("handlerThre...

sinat_33150417的博客 424

多线程(四)、Android多线程使用及AsyncTask源码分析

本篇是多线程系列的第四篇,如果对前三篇感兴趣的也可以去看看。 多线程(一)、基础概念及notify()和wait()的使用 多线程(二)、内置锁 synchronized 多线程(三)、线程池 ThreadPoolExecutor 知识点总结 除了前面的线程池的使用外,在Android中,我们除了通过Thread创建线程外,还可以通过 AsyncTask、IntentServi...

Evan的博客 308

【Android】Handler工作原理 && HandlerThread用法

Handler在多线程通信,Handler通过Message、MessageQueue、Looper和Handler四大组件实现线程间通信,为解决线程重复启动问题,HandlerThread封装了Looper.prepare()和Looper.loop()逻辑,使单个线程可处理多个任务。HandlerThread如何在不同线程间传递任务结果,并分析了其内部synchronized同步机制保证线程安全的原理。最终实现后台线程执行耗时任务后,通过主线程Handler更新UI的效果。

阿华的博客 1302

Android 多线程之HandlerThread 完全详解

转载请注明出处(万分感谢!): http://blog.youkuaiyun.com/javazejian/article/details/52426353 出自【zejian的博客】 关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) android多线程-AsyncTask之工作原理深入解析(下)   之前对线程也写过几篇文章,不过倒是没有针对android,因为

Gabriel的专栏 177

IntentService 面试详解

前两篇文章介绍了AsyncTask与HandleThread,主要是针对Android的多线程编程。在这里补充下线程的知识。 线程是操作系统调度的最小单元,同时也是一种受限的资源(即不能无限制的创建和销毁,创建和销毁都会占用系统资源)。操作系统是通过时间片轮转的方式调度每个线程,因此不可能做到绝对的并行。 进程一般指一个执行单元,在PC或移动设备上指一个程序或者应用。一个进程可以包含一个或多个...

CoderZJ的博客 473

HandlerThread源码分析

HandlerThread源码分析 概述    在之前的文章中介绍过Android应用层的消息机制【点这里 】,也介绍了利用Handler机制实现多线程调度的AsyncTask,接下来介绍一个新成员HandlerThread。从字面上可以看出,HandlerThread可能是与Thread与Handler应用相关的,HandlerThread也确实是封装了Handle...

Leslie_Yu的博客 377

Android HandlerThread源码分析

HandlerThread继承于Thread类,是Android里的一个封装类,可以快速创建一个带有Looper对象的新线程。其本质是Thread+Handler。 HandlerThread可以用来执行多个耗时任务,不需要多次开启线程。

xiangxiongfly 253

Android多线程:HandlerThread详细使用手册

原文地址:https://blog.youkuaiyun.com/carson_ho/article/details/79285760 多线程的应用在Android开发中是非常常见的,常用方法主要有: 继承Thread类 实现Runnable接口 Handler AsyncTask HandlerThread 今天,我将全面解析多线程其中一种常见用法:HandlerThread 由于本文涉及...

wa172126691的博客 261

Android 多线程

多线程笔记 线程基础 线程基本使用api 线程池基础 ReentrantLock使用 阻塞分类 死锁定义实现以及解决方式 生产者消费者问题 AsyncTask源码分析

crazyZhangxl的博客 678

Android多线程:HandlerThread详细使用手册(含实例讲解)

前言 多线程的应用在Android开发中是非常常见的,常用方法主要有: 继承Thread类 实现Runnable接口 Handler AsyncTask HandlerThread 今天,我将全面解析多线程其中一种常见用法:HandlerThread 由于本文涉及多线程知识和Handler源码解析,所以阅读本文前建议先看: Android开发:Handler异步通信机制全面解...

Carson带你学Android 3万+
上一篇: 使用编译时注解实现简易的 ButterKnife 效果
下一篇: Android 多线程之 AsyncTask使用源码分析
Sean_css
博客等级 码龄11年 32粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值