Android系统源码分析-进程间通信机制binder(四):从Native层到Driver层

Binder通信过程中的用户空间线程池的管理 在Binder通信机制里,客户端与服务端之间的通信是在专门的IPC通信线程中进行的。这些线程构成一个线程池。线程的创建和销毁是在用户空间进行的,而对线程的控制是在驱动层进行的,即驱动控制线程池中线程的生命,而线程本身则是运行在用户空间的。驱动层是通过BR_SPAWN_LOOPER向用户空间发送创建新线程的命令。 线程池的大小可以设置。默认情况下,线程池只有一个主线程,它在进程生命周期期间是不会退 阅读详情

1.内容简介: 

本篇继续分析Android Binder机制相关源代码,主要从Native(c++)层到driver层的相关组件已经代码调用流程。

前面的几篇,可以移步至:

1). Android系统源码分析-进程间通信机制binder(一):守护进程servicemanager
2)  . Android系统源码分析-进程间通信机制binder(二):binder内存映射

3)  . Android系统源码分析-进程间通信机制binder(三):从framework层到Native层

2.Native Binder架构: 

 在Android系统源码分析-进程间通信机制binder(三):从framework层到Native层一文中,已经分析了整个Binder架构,以及Binder在framework层的相关组件。现在,来看一下在native层的相关组件,如图:

代码位置:

头文件目录:/frameworks/native/libs/binder/include/binder/,
具体实现的cpp文件目录:/frameworks/native/libs/binder/ 。

其中,

1)IInterface(在IInterface.h头文件中):该类是一个虚基类(可以看作是java中的接口,但又不完全一样,因为C++中的虚基类和java中的接口有相同点但又有不同电),用户必须继承该类,并且实现onAsBinder函数。它的声明如下:

class IInterface : public virtual RefBase
{
public:
            IInterface();
            sp<IBinder>         asBinder();
            sp<const IBinder>   asBinder() const;
            
protected:
    virtual                     ~IInterface();
    virtual IBinder*            onAsBinder() = 0;
};

可以看到,onAsBinder() = 0; 的返回知识IBinder的指针,而且,virtual关键字表示需要子类必须重写该方法。

2)BnInterface(在IInterface.h头文件中):是一个模版类,而且是BBinder的子类,需要重写BBinder的onTransact方法;同时,BnInterface还继承了INTERFACE。在C++中,一个累可以继承自多个父类。

3)BpInterface(在IInterface.h头文件中):是一个模版类,而且是BpRefBase的子类,需要重写BpRefBase的remote方法,而且BpRefBase中还有IBinder类型的数据乘员mRemote,它指向的就是服务的实现方;同时,BpInterface还继承了INTERFACE。

BnInterface和BpInterface的声明如下:

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;

protected:
    virtual IBinder*            onAsBinder();
};

// ----------------------------------------------------------------------

template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};

4)IBinder(IBinder.h):它是BBinder和BpBinder的父类,是一个虚基类,提供transact函数供子类实现;

class IBinder : public virtual RefBase
{
    ......

    virtual status_t        transact(   uint32_t code,
                                        const Parcel& data,
                                        Parcel* reply,
                                        uint32_t flags = 0) = 0;
    ......
}

5)BBinder(Binder.h):IBinder的子类,必须实现transact函数,它代表着本地对象;

6)BpBinder(BpBinder.h):IBinder的子类,必须实现transact函数,它代表着远程对象;

7)ProcessState和IPCProcessState:用于提供Binder进程间通信的管理,包括和Binder驱动的交互。

8)Parcel:是java到Native层的数据结构;在Binder进程间通信时,需要进行传递的数据就封装在Parcel实例中。

9)INTERFACE: 是类模版的指代。在C++中,用template<class T>来声明一个类模版。所以,这里的INTERFACE其实相当于template<class T>中的T。具体是哪个类,答案就是T的子类。在Android Binder Native机制中,INTERFACE就是实现Binder服务接口的基类。

Binder库的编译:

Binder库最终会编译成一个动态链接库:libbinder.so,供其他进程链接使用。

对应的Android.mk编译文件:/frameworks/native/libs/binder/Android.mk,如下:

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# we have the common sources, plus some device-specific stuff
sources := \
    Binder.cpp \
    BpBinder.cpp \
    IInterface.cpp \
    IMemory.cpp \
    IPCThreadState.cpp \
    IPermissionController.cpp \
    IServiceManager.cpp \
    MemoryDealer.cpp \
    MemoryBase.cpp \
    MemoryHeapBase.cpp \
    Parcel.cpp \
    PermissionCache.cpp \
    ProcessState.cpp \
    Static.cpp

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_LDLIBS += -lpthread
LOCAL_MODULE := libbinder
LOCAL_SHARED_LIBRARIES := liblog libcutils libutils
LOCAL_SRC_FILES := $(sources)
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_LDLIBS += -lpthread
LOCAL_MODULE := libbinder
LOCAL_SRC_FILES := $(sources)
include $(BUILD_STATIC_LIBRARY)

3.Native Binder代码分析:

在介绍完各个组件功能后,来看一下这些组件的代码执行流程。

IPCThreadState负责了与驱动通信的细节处理,它也是初始化Native binder的入口,所以,从这个类入手进行分析。

在 Android系统源码分析-进程间通信机制binder(三):从framework层到Native层 一文中的最后,分析到IPCThreadState这个类。

接着IPCThreadState这个入口继续分析:

IPCThreadState的初始化:

IPCThreadState::IPCThreadState()
    : mProcess(ProcessState::self()),
      mMyThreadId(androidGetTid()),
      mStrictModePolicy(0),
      mLastTransactionBinderFlags(0)
{
    pthread_setspecific(gTLS, this);
    clearCaller();
    mOrigCallingUid = mCallingUid;
    mIn.setDataCapacity(256);
    mOut.setDataCapacity(256);
}

ProcessState采用单利模式(Singleton),在一个进程中,只会存在一个实例。通过ProcessState::self()函数来获取这个实例,从而调用到ProcessState的构造函数。ProcessState的构造函数,如下:

ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

首先,open_driver():

static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);
    if (fd >= 0) {
        fcntl(fd, F_SETFD, FD_CLOEXEC);
        int vers;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        size_t maxThreads = 15;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}

分析:open_driver的主要作用:

1)打开binder驱动设备文件“/dev/binder”;

2)检测版本号;

3)设置最大线程数。

都是通过ioctl系统调用完成的。

然后,mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

IPCThreadState类中的关键几个方法说明如下:

transact:    供代理类Proxy发送数据到驱动,并读取返回结果;

sendReply:    供Server端写回请求的返回结果;

waitForResponse:    发送请求后等待响应结果;

talkWithDriver:    通过ioctl BINDER_WRITE_READ来与驱动通信;

writeTransactionData:    写入一次事务的数据;

executeCommand:    处理binder_driver_return_protocol协议命令;

freeBuffer:    通过BC_FREE_BUFFER命令释放Buffer。

线程管理: 

再来分析一下ProcessState中对线程的管理,看一下setThreadPoolMaxThreadCount的实现:
 

status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
    status_t result = NO_ERROR;
    if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) == -1) {
        result = -errno;
        ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
    }
    return result;
}

DEFAULT_MAX_BINDER_THREADS就是Binder服务支持的最大线程数。

驱动在运行过程中,会根据需要,并在没有超过上限的情况下,通过BR_SPAWN_LOOPER命令通知进程创建线程:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;
    
    switch (cmd) {
    case BR_ERROR:
        result = mIn.readInt32();
        break;
        
    case BR_OK:
        break;
        
    case BR_ACQUIRE:
        ......
    
    case BR_TRANSACTION:
        
            ......
        
    case BR_SPAWN_LOOPER:
        mProcess->spawnPooledThread(false);
        break;
        
    default:
        printf("*** BAD COMMAND %d received from Binder driver\n", cmd);
        result = UNKNOWN_ERROR;
        break;
    }

    if (result != NO_ERROR) {
        mLastError = result;
    }
    
    return result;
}

ProcessState::spawnPooledThread的实现: 

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        int32_t s = android_atomic_add(1, &mThreadPoolSeq);
        char buf[16];
        snprintf(buf, sizeof(buf), "Binder_%X", s);
        ALOGV("Spawning new pooled thread, name=%s\n", buf);
        sp<Thread> t = new PoolThread(isMain);
        t->run(buf);
    }
}

PoolThread的声明:(代码位于ProcessState.cpp文件中)

class PoolThread : public Thread
{
public:
    PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};

joinThreadPool就将创建的线程添加到线程池里。

这样,一个线程的初始化就完成了。


Android 12 HAL开发避坑实录:手把手教你用AIDL在Native层实现一个可开机自启动的Service 本文详细介绍了在Android 12 HAL开发中,如何通过AIDL在Native层实现一个可开机自启动的Service。从环境准备、AIDL接口定义、服务实现到VINTF集成与SELinux策略配置,提供了完整的实战指南,帮助开发者避开常见陷阱,提升开发效率。 阅读详情

相关推荐

Android Binder进程间通信机制

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。还有。

2401_85112655的博客 1108

android Binder详解 (2)

2.3 SampleService启动(main函数实现) service的启动实际就是需要编写一个可执行程序来运行service。 还是以surfaceflinger为例,surfaceflinger的main函数在frameworks/native/service/surfacefllinger/main_surfaceflinger.cpp:

sky的专栏 2399

AIDL for HALs实战

Google 在Android 11引入了`AIDL for HALs`,旨在代替HIDL原先的作用。在之后的Android版本推荐使用AIDL 实现Hal层的访问。

qq_40731414的博客 1万+

Android Automotive(七) VehicleService

Android Automotive(七) VehicleService

Jun_P的博客 5111

从零打造音乐会级特雷门琴:原理、设计与调试全解析

电子乐器的工作原理通常基于信号生成与调制。特雷门琴作为世界上最早的电子乐器之一,其核心原理是利用射频振荡器和电容传感,将演奏者的手势变化转化为音高与音量信号。这种技术通过外差式混频实现音高控制,并利用压控放大器实现细腻的音量动态,其工程价值在于将抽象的空间位置信息精确映射为连续的音频参数。在音乐制作、声音艺术和交互装置等领域,这类技术为创造独特的演奏体验提供了基础。本文聚焦于如何从电路层面实现一台高稳定性的特雷门琴,详细阐述了从LC振荡器设计、天线场形优化到音色混合检波等关键环节,并分享了基于LTspice

weixin_34046298的博客 170

RM机器人麦轮底盘实战:从零搭建到PID调参全记录(附避坑指南)

本文详细记录了RM机器人麦轮底盘从零搭建到PID调参的全过程,涵盖硬件组装、开发环境搭建、底盘解算和PID调参等关键步骤,并附有实用的避坑指南。特别针对麦轮控制、底盘解算和电机调试等核心问题提供了专业解决方案,帮助开发者快速提升机器人性能。

bush的博客 372

Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击

摘要:本节主要来讲解Android10.0 Binder的驱动层分析 阅读本文大约需要花费30分钟。 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! [Android取经之路] 的源码都基于Android-Q(10.0) 进行分析 [Android取经之路] 系列文章: 《系统启动篇》 Android系统架构...

IngresGe 的专栏 8607

PHP+MYSQL宠物主题动态网页设计源码+数据库,大学生期末大作业,PHP课程设计

PHP+MYSQL宠物主题动态网页设计源码+数据库,大学生期末大作业,PHP课程设计提示:本文采用PHP对数据库进行操作:本文就介绍了PHP实现对MySQL数据库的增删改查操作基础内容。

900

安卓系统SystemServer启动源码分析

主线支线。

2601_95605400的博客 229

深信服aCloud虚拟存储VS3.0实战:从数据分片到仲裁机制的全流程解析

本文深度解析了深信服aCloud虚拟存储VS3.0的核心架构与生产级实践。从数据分片、条带化写入、多副本机制到智能缓存分层,详细阐述了其如何实现高性能与高可靠性的平衡。同时,深入剖析了动态均衡、平滑扩容以及故障恢复与仲裁机制,为运维人员和技术决策者提供了在超融合基础架构(HCI)中部署和管理虚拟存储的实战指南。

net55的博客 588

LeRobot 机器人学习(Robot Learning)教程(二)经典机器人学

本文探讨了机器人学中基于学习的方法与传统动力学方法的对比与发展趋势。文章指出,传统方法依赖精确的动力学模型和大量人工调参,面临跨任务泛化能力弱、工程复杂度高、物理建模局限性等问题。随着机器学习技术的进步和机器人数据集的丰富,基于学习的方法展现出三大优势:(1)能够实现跨任务与实体的泛化;(2)降低对人类专业知识的依赖;(3)利用历史数据优化性能。文中以平面机械臂为例,分析了传统逆运动学方法的计算复杂性和环境适应性挑战,并指出基于学习的方案在处理高维感知输入、复杂接触动力学等方面具有明显优势。文章认为,结合物

2506_90492529的博客 1066

Android Binder传输大文件

重点转移到服务端,众所周知,binder服务端是可以多线程处理任务的,binder多个线程是共用一块buffer内存,所以减少多线程数量,即可减少多个线程争抢一块内存的情况。我这里用的是android系统服务的ndk后端,使用`ABinderProcess_setThreadPoolMaxThreadCount` 来设置最大线程数,我这里设定为5,但是依旧会出现上面的报错。

xiaokangss的博客 1171

C语言从句柄到对象 (一) —— 全局变量的噩梦与“多实例”的救赎

本文探讨了嵌入式开发中句柄(Handle)的概念与应用。通过电机控制案例,分析了传统单例模式在扩展性上的局限,并展示了如何通过句柄实现代码复用:将电机状态封装为结构体,通过指针(句柄)传递实例数据,使同一套逻辑可操作多个实例。文章比较了单例模式与句柄模式的适用场景,建议底层驱动库默认采用句柄模式以提高扩展性。最后指出当前实现仍存在数据暴露风险,预告下期将介绍不透明指针技术实现更安全的封装。全文揭示了句柄作为C语言实现模块化和面向对象设计的关键作用。

一路往蓝-Anbo 的博客 1340

HAL: 将 HIDL 接口改造为 Stable AIDL

Android HAL 将 HIDL 接口改造为 Stable AIDL

fufudayo的博客 1万+

90. 将 SAP ABAP 内表内容本地导出成 Excel 文件

这里文件扩展名为 csv,全称是 Comma-Separated Values,意思是这种类型的文件内容,由逗号分隔。除了 ASC 之外,另一个可用的选项是 BIN,适用于 PDF,EXE 等二进制文件的导出。支持海量的输入和输出参数,我们这个简单的本地导出场景里,大部分参数都用不上。这个分隔符的添加逻辑,编写在上图红色高亮矩形框内,使用的语法是。本教程有学习者向我提问,询问如何将 ABAP 某某函数的某某输出参数的值,导出成本地 Excel 文件。把这个内表的内容,下载到本地,以本地文件的方式保存。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1742

SPICE仿真软件:HSPICE_(20).HSPICE仿真结果的后处理

HSPICE仿真结果的后处理是一个多步骤的过程,包括读取结果文件、可视化、分析和导出。通过使用命令行工具、Python脚本和HSPICE内置工具,可以高效、准确地完成这些任务。Python脚本的灵活性和强大的数据分析能力使其成为后处理的理想选择。自动化处理可以显著提高工作效率,减少人为错误,而高级分析方法可以更深入地理解仿真结果,为电路设计提供有力支持。

kkchenjj的博客 1382

Android Binder ProcessState & IPCThreadState相关介绍

之前几篇博客我们介绍了传统Binder的使用方法,包括c层和java层,这篇博客我们主要介绍下ProcessState和IPCThreadState类的相关方法。 一、正常demon binder调用流程 在一个传统的demon中,如果我们要使用Binder通信,代码大致如下: int main(int argc, char** argv) { sp proc(P

kc58236582的博客 3833

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

RK3576 Android14 增加HAL层的AIDL

注意: 每个类型定义都必须使用@VintfStability进行注释。

weixin_56061795的博客 3265

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

aidl android.mk,AIDL 后端  |  Android 开源项目  |  Android Open Source Project

AIDL 后端是生成桩代码的目标。在使用 AIDL 文件时,您始终是采用特定语言在特定的运行时环境中使用这些文件。因此,您应该根据具体情况使用不同的 AIDL 后端。AIDL 的后端如下所示:后端语言API 表面构建系统JavaJavaSDK/SystemApi(稳定*)全部NDKC++libbinder_ndk(稳定*)aidl_interfaceCPPC++libbinder(不稳定)全部这些...

weixin_39819671的博客 1641

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

图解Android - Binder 和 Service

在 Zygote启动过程一文中我们说道,Zygote一生中最重要的一件事就是生下了 System Server 这个大儿子,System Server 担负着提供系统 Service的重任,在深入了解这些Service 之前,我们首先要了解 什么是Service?它的工作原理是什么? 1. Service是什么? 简单来说,Service就是提供服务的代码,这些代码最终体现为一个...

weixin_34301307的博客 828

android binder解析

android binder解析   2013-04-20 09:27:48|  分类: android |  标签:binder  ipc  surfaceflinger  anr  |举报|字号 订阅 一直对android的binder机制比较困惑,出了问题时也手足无措。看了一些资料,感觉似懂非懂,于是萌生了一个想法,一定要自己跟一根binder流程,这样有

u014412834的专栏 1417

从AIDL到Binder:揭秘Android跨进程通信的底层实现与性能优化

本文深入解析Android跨进程通信中AIDL与Binder的底层实现机制,重点探讨AIDL编译后的C++代码生成、Binder驱动交互及内存管理。结合NDK开发实践,提供线程池配置、异步回调处理和内存优化等可落地的性能优化方案,帮助开发者构建高性能Android系统服务。

s4t5u6v的博客 65

Android 12 HAL开发避坑实录:从AIDL接口定义到SELinux策略的完整实战

本文详细解析了Android 12 HAL开发中的关键步骤与常见问题,从AIDL接口定义到SELinux策略配置的全流程实战指南。特别针对native层开发中的编译错误、服务注册失败等典型问题提供解决方案,帮助开发者高效避坑,确保硬件抽象层的稳定性和性能。

weixin_29294975的博客 226

安卓之AIDL的钩子一览、treble合规操作

摘要:谷歌为厂商扩展AIDL接口提供标准方案,要求在保持原生接口不变的前提下,通过附加扩展接口方式实现新功能。关键步骤包括创建独立扩展类、使用AIBinder_setExtension绑定扩展、通过全局注册提供服务,并严格避免修改原生接口。该方法可确保VTS测试合规,同时避免版本升级时的合并冲突。官方提供了NDK/Java/CPP/Rust四种后端的API实现方式,并通过vibrator案例演示了完整流程。特别注意智能指针的正确使用方式,否则会导致扩展功能失效。

u012673734的专栏 814

Android系统源码分析-进程间通信机制binder(二):binder内存映射

Binder在Android系统中用于进程间通讯。为了实现用户空间和内核空间的数据交换,Android Binder机制采用了内存映射的方式,具体映射到/dev/binder这个设备驱动文件节点上。

记录和分享程序人生的点点滴滴 8468
上一篇: Android系统源码分析-进程间通信机制binder(三):从framework层到Native层
下一篇: C++基础#8:类层次中的类转换
liranke
博客等级 码龄20年 4939粉丝 594原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liranke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值