Android Framework重要服务之InputManagerService(三) inputDispatcher_android carservice与inputdispatcher关系

if (injectionResult == InputEventInjectionResult::PENDING) {
    return false;
}
...

// 找到目标窗口之后开始分发事件
if (conflictingPointerActions) {
    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
                               "conflicting pointer actions");
    synthesizeCancelationEventsForAllConnectionsLocked(options);
}
dispatchEventLocked(currentTime, entry, inputTargets);
return true;

}


针对`Touch事件`和`非Touch事件`,有两种不同的逻辑来寻找焦点窗口:`findTouchedWindowTargetsLocked`中有很多关于分屏和多`display id`的处理逻辑,相比`findFocusedWindowTargetsLocked`而言代码逻辑稍显复杂,为了聚焦事件分发的主线流程,我们以`findFocusedWindowTargetsLocked`为例来说明焦点窗口的寻找过程;


### 1.3 findFocusedWindowTargetsLocked


`findFocusedWindowTargetsLocked`用于寻找焦点窗口,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry& entry,
std::vector& inputTargets,
nsecs_t* nextWakeupTime) {
std::string reason;
// 寻找输入事件的display id,由于Android支持多屏设备,所以可能会有多个display id,默认为0
int32_t displayId = getTargetDisplayId(entry);
// 根据display id查找InputWindowHandle
std::shared_ptr focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// 根据display id查找InputApplicationHandle
sp focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);

// 如果focusedWindowHandle和focusedApplicationHandle同时为空,代表当前即无焦点应用也无焦点窗口,所以直接将此事件丢弃
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
    ALOGI("Dropping %s event because there is no focused window or focused application in "
          "display %" PRId32 ".",
          EventEntry::typeToString(entry.type), displayId);
    return INPUT_EVENT_INJECTION_FAILED;
}

// 焦点应用不为空而焦点窗口为空
if (focusedWindowHandle == nullptr && focusedApplicationHandle != nullptr) {
    // 未设置焦点窗口缺失的超时时间
    if (!mNoFocusedWindowTimeoutTime.has_value()) {
        // We just discovered that there's no focused window. Start the ANR timer
        // 获取超时时间,默认为5s
        const nsecs_t timeout = focusedApplicationHandle->getDispatchingTimeout(
                DEFAULT_INPUT_DISPATCHING_TIMEOUT.count());
        // 记录超时时间
        mNoFocusedWindowTimeoutTime = currentTime + timeout;
        // 记录等待焦点窗口的焦点应用
        mAwaitedFocusedApplication = focusedApplicationHandle;
        ALOGW("Waiting because no window has focus but %s may eventually add a "
              "window when it finishes starting up. Will wait for %" PRId64 "ms",
              mAwaitedFocusedApplication->getName().c_str(), ns2ms(timeout));
        *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
        return INPUT_EVENT_INJECTION_PENDING;
    } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
        // 如果已经超时,则直接丢弃该事件
        ALOGE("Dropping %s event because there is no focused window",
              EventEntry::typeToString(entry.type));
        return INPUT_EVENT_INJECTION_FAILED;
    } else {
        // 还未到超时时间则继续等待
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

// 重置超时时间和等待的焦点应用
resetNoFocusedWindowTimeoutLocked();

// Check permissions.
if (!checkInjectionPermission(focusedWindowHandle, entry.injectionState)) {
    return INPUT_EVENT_INJECTION_PERMISSION_DENIED;
}

// 焦点窗口已经paused
if (focusedWindowHandle->getInfo()->paused) {
    ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
    return INPUT_EVENT_INJECTION_PENDING;
}

// 成功找到焦点窗口后,将其添加到inputTargets
addWindowTargetLocked(focusedWindowHandle,
                      InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
                      BitSet32(0), inputTargets);

// Done.
return INPUT_EVENT_INJECTION_SUCCEEDED;

}


这里有两个非常重要的map:`mFocusedWindowHandlesByDisplay`和`mFocusedApplicationHandlesByDisplay`,它们分别存储着当前每个`display`对应的焦点应用和焦点窗口,这些map在`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h`被声明:



// key为display id,value为InputWindowHandle即焦点窗口
std::unordered_map<int32_t, sp> mFocusedWindowHandlesByDisplay
GUARDED_BY(mLock);

// key为display id,value为InputApplicationHandle即焦点应用
std::unordered_map<int32_t, sp> mFocusedApplicationHandlesByDisplay
GUARDED_BY(mLock);


### 1.4 setInputWindowsLocked


`setInputWindowsLocked`用于设置焦点窗口,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::setInputWindowsLocked(
const std::vector<sp>& inputWindowHandles, int32_t displayId) {

// 根据displayId从mWindowHandlesByDisplay中查找出当前所有的InputWindowHandle,包括焦点窗口和非焦点窗口
const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
...

sp<InputWindowHandle> newFocusedWindowHandle = nullptr;
bool foundHoveredWindow = false;
for (const sp<InputWindowHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
    // Set newFocusedWindowHandle to the top most focused window instead of the last one
    // 遍历更新之后的所有窗口列表,并将可见并且获得焦点的窗口置为新的焦点窗口
    if (!newFocusedWindowHandle && windowHandle->getInfo()->hasFocus &&
        windowHandle->getInfo()->visible) {
        newFocusedWindowHandle = windowHandle;
    }
    if (windowHandle == mLastHoverWindowHandle) {
        foundHoveredWindow = true;
    }
}

if (!foundHoveredWindow) {
    mLastHoverWindowHandle = nullptr;
}

// 根据displayId从mFocusedWindowHandlesByDisplay中查找出当前的焦点窗口
sp<InputWindowHandle> oldFocusedWindowHandle =
        getValueByKey(mFocusedWindowHandlesByDisplay, displayId);

// 不同的InputWindowHandle有不同的token
if (!haveSameToken(oldFocusedWindowHandle, newFocusedWindowHandle)) {
    if (oldFocusedWindowHandle != nullptr) {
        if (DEBUG_FOCUS) {
            ALOGD("Focus left window: %s in display %" PRId32,
                  oldFocusedWindowHandle->getName().c_str(), displayId);
        }
        // 根据InputWindowHandle中的Token获取到对应的额InputChannel
        sp<InputChannel> focusedInputChannel =
                getInputChannelLocked(oldFocusedWindowHandle->getToken());
        if (focusedInputChannel != nullptr) {
            CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
                                       "focus left window");
            synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
            // 往mInboundQueue里添加失去焦点的FocusEntry
            enqueueFocusEventLocked(*oldFocusedWindowHandle, false /*hasFocus*/);
        }
        // 从mFocusedWindowHandlesByDisplay中移除历史的InputWindowHandle
        mFocusedWindowHandlesByDisplay.erase(displayId);
    }
    if (newFocusedWindowHandle != nullptr) {
        if (DEBUG_FOCUS) {
            ALOGD("Focus entered window: %s in display %" PRId32,
                  newFocusedWindowHandle->getName().c_str(), displayId);
        }
        // 更新mFocusedWindowHandlesByDisplay
        mFocusedWindowHandlesByDisplay[displayId] = newFocusedWindowHandle;
        // 往mInboundQueue里添加得到焦点的FocusEntry
        enqueueFocusEventLocked(*newFocusedWindowHandle, true /*hasFocus*/);
    }

    // 往mCommandQueue里添加焦点改变的CommandEntry,通知上层焦点窗口改变
    if (mFocusedDisplayId == displayId) {
        onFocusChangedLocked(oldFocusedWindowHandle, newFocusedWindowHandle);
    }
}

}


每个`display`都对应若干个`InputWindowHandle`和一个焦点`InputWindowHandle`,此方法会根据传入的`InputWindowHandle`列表来更新`mWindowHandlesByDisplay`和`mFocusedWindowHandlesByDisplay`,调用流程如下所示:



–>frameworks/native/services/surfaceflinger/SurfaceFlinger.onMessageInvalidate
–>frameworks/native/services/surfaceflinger/SurfaceFlinger.updateInputFlinger
–>frameworks/native/services/surfaceflinger/SurfaceFlinger.updateInputWindowInfo
–>frameworks/native/services/inputflinger/IInputFlinger.setInputWindows
–>frameworks/native/services/inputflinger/InputManager.setInputWindows
–>frameworks/native/services/inputflinger/InputDispatcher.setInputWindows
–>frameworks/native/services/inputflinger/InputDispatcher.setInputWindowsLocked


### 1.5 setFocusedApplication


`setFocusedApplication`用于设置焦点应用,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::setFocusedApplication(
int32_t displayId, const std::shared_ptr& inputApplicationHandle) {
if (DEBUG_FOCUS) {
ALOGD(“setFocusedApplication displayId=%” PRId32 " %s", displayId,
inputApplicationHandle ? inputApplicationHandle->getName().c_str() : “”);
}
{ // acquire lock
std::scoped_lock _l(mLock);
// 设置焦点应用
setFocusedApplicationLocked(displayId, inputApplicationHandle);
} // release lock

// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();

}


### 1.6 dispatchEventLocked


`dispatchEventLocked`用于传递输入事件,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
const std::vector& inputTargets) {

for (const InputTarget& inputTarget : inputTargets) {
    // 找到InputChannel对应的Connection
    sp<Connection> connection =
            getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
    if (connection != nullptr) {
        // 如果Connection不为空,则开始准备分发输入事件
        prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
    }
}

}


`ispatchEventLocked`函数根据`InputTarget`查找到对应的`Connection`,它是连接`native`的应用进程的通道,有了它就可以开始准备分发输入事件了。


### 1.7 prepareDispatchCycleLocked


`prepareDispatchCycleLocked`函数用于准备分发输入事件,在这个函数中,将校验连接状态,当连接状态正常时,将入队输入事件,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp& connection,
EventEntry* eventEntry,
const InputTarget& inputTarget) {
// 如果Connection状态不正常,则直接返回不会把输入事件添加到mOutbound队列
if (connection->status != Connection::STATUS_NORMAL) {
return;
}

// 入队输入事件
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);

}


### 1.8 enqueueDispatchEntriesLocked


`enqueueDispatchEntriesLocked`函数用于入队输入事件,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp& connection,
EventEntry* eventEntry,
const InputTarget& inputTarget) {

// 判断Connection的outboundQueue是否为空
bool wasEmpty = connection->outboundQueue.empty();

// 入队输入事件
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_IS);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
                           InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.empty()) {
    // 如果之前outboundQueue为空,经过enqueueDispatchEntryLocked之后不为空,则开始分发事件
    startDispatchCycleLocked(currentTime, connection);
}

}


### 1.9 startDispatchCycleLocked


`startDispatchCycleLocked`用于分发输入事件,相关代码位于`frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp`:



void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp& connection) {

// 经过enqueueDispatchEntryLocked之后,connection->outboundQueue不为空
while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
    DispatchEntry* dispatchEntry = connection->outboundQueue.front();
    // 记录事件分发的时间
    dispatchEntry->deliveryTime = currentTime;
    // 默认超时时间为5s
    const nsecs_t timeout =
            getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
    // 分发超时时间为当前时间加上默认超时时间
    dispatchEntry->timeoutTime = currentTime + timeout;
    
    // Publish the event.
    status_t status;
    EventEntry* eventEntry = dispatchEntry->eventEntry;
    switch (eventEntry->type) {
        case EventEntry::Type::MOTION: {
            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
            ...
            
            // 获取事件签名
            std::array<uint8_t, 32> hmac = getSignature(*motionEntry, *dispatchEntry);

            // 通过Connection的InputPublisher.publishMotionEvent将输入事件发布
            status = connection->inputPublisher
                             .publishMotionEvent(dispatchEntry->seq,
                                                 dispatchEntry->resolvedEventId,
                                                 motionEntry->deviceId, motionEntry->source,
                                                 motionEntry->displayId, std::move(hmac),
                                                 dispatchEntry->resolvedAction,
                                                 motionEntry->actionButton,
                                                 dispatchEntry->resolvedFlags,
                                                 motionEntry->edgeFlags, motionEntry->metaState,
                                                 motionEntry->buttonState,
                                                 motionEntry->classification, xScale, yScale,
                                                 xOffset, yOffset, motionEntry->xPrecision,
                                                 motionEntry->yPrecision,
                                                 motionEntry->xCursorPosition,
                                                 motionEntry->yCursorPosition,
                                                 motionEntry->downTime, motionEntry->eventTime,
                                                 motionEntry->pointerCount,
                                                 motionEntry->pointerProperties, usingCoords);
            reportTouchEventForStatistics(*motionEntry);
            break;
        }
        ...
        
    }

    // 发布完之后将DispatchEntry从Connection的outboundQueue中移除
    connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
                                                connection->outboundQueue.end(),
                                                dispatchEntry));
    // 然后将DispatchEntry加入Connection的waitQueue
    connection->waitQueue.push_back(dispatchEntry);
    if (connection->responsive) {
        // 往AnrTracker中插入一条记录
        mAnrTracker.insert(dispatchEntry->timeoutTime,
                           connection->inputChannel->getConnectionToken());
    }
    traceWaitQueueLength(connection);
}   

}


### 1.10 publishMotionEvent


`publishMotionEvent`函数将事件发布出去,相关代码位于`frameworks/native/libs/input/InputTransport.cpp`:



status_t InputPublisher::publishMotionEvent(
uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
int32_t edgeFlags, int32_t metaState, int32_t buttonState,
MotionClassification classification, float xScale, float yScale, float xOffset,
float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
float yCursorPosition, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
InputMessage msg;
msg.header.type = InputMessage::Type::MOTION;
msg.body.motion.seq = seq;
msg.body.motion.eventId = eventId;
msg.body.motion.deviceId = deviceId;
msg.body.motion.source = source;
msg.body.motion.displayId = displayId;
msg.body.motion.hmac = std::move(hmac);
msg.body.motion.action = action;
msg.body.motion.actionButton = actionButton;
msg.body.motion.flags = flags;
msg.body.motion.edgeFlags = edgeFlags;
msg.body.motion.metaState = metaState;
msg.body.motion.buttonState = buttonState;
msg.body.motion.classification = classification;
msg.body.motion.xScale = xScale;
msg.body.motion.yScale = yScale;
msg.body.motion.xOffset = xOffset;
msg.body.motion.yOffset = yOffset;
msg.body.motion.xPrecision = xPrecision;
msg.body.motion.yPrecision = yPrecision;
msg.body.motion.xCursorPosition = xCursorPosition;
msg.body.motion.yCursorPosition = yCursorPosition;
msg.body.motion.downTime = downTime;
msg.body.motion.eventTime = eventTime;
msg.body.motion.pointerCount = pointerCount;
for (uint32_t i = 0; i < pointerCount; i++) {
msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
}
// 将输入事件封装成InputMessage,并继续通过InputChannel.sendMessage发送消息
return mChannel->sendMessage(&msg);
}


### 1.11 sendMessage


`sendMessage`将事件发送到应用进程,相关代码位于`frameworks/native/libs/input/InputTransport.cpp`:



status_t InputChannel::sendMessage(const InputMessage* msg) {
const size_t msgLength = msg->size();
InputMessage cleanMsg;
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
// 通过socket将InputMessage发送给应用进程
nWrite = ::send(mFd.get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);

return OK;
}


## 总结


至此`InputDispatcher`的工作算是告一段落,事件最终通过`socket`发送给客户端,下面给出inputDispatcher的整体流程图供大家餐参考: ![image.png](https://img-blog.csdnimg.cn/img_convert/e3d9b0495641f30a584f541a934c6259.png)


### 文末


要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。


如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/06e41b3932164f0db07014d54e6e5626.png)  
 相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。  
 ![](https://img-blog.csdnimg.cn/img_convert/711d22109019c66e8bbc9762d31159f6.jpeg)


#### 一、架构师筑基必备技能


1、深入理解Java泛型  
 2、注解深入浅出  
 3、并发编程  
 4、数据传输与序列化  
 5、Java虚拟机原理  
 6、高效IO  
 ……


![在这里插入图片描述](https://img-blog.csdnimg.cn/079bc2315e7e4f73b8fe703c3c51ae8d.png)


#### 二、Android百大框架源码解析


1.Retrofit 2.0源码解析  
 2.Okhttp3源码解析  
 3.ButterKnife源码解析  
 4.MPAndroidChart 源码解析  
 5.Glide源码解析  
 6.Leakcanary 源码解析  
 7.Universal-lmage-Loader源码解析  
 8.EventBus 3.0源码解析  
 9.zxing源码分析  
 10.Picasso源码解析  
 11.LottieAndroid使用详解及源码解析  
 12.Fresco 源码分析——图片加载流程



### 最后

文章所有资料全部已经打包整理好,另外小编手头上整理了大量Android架构师全套学习资料,**Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析**

**资料展示:**

![image](https://img-blog.csdnimg.cn/img_convert/04c3370ed16025256e170bc62707f557.webp?x-oss-process=image/format,png)

![image](https://img-blog.csdnimg.cn/img_convert/03aae6e0b5850a143997ca605f0f1026.webp?x-oss-process=image/format,png)

![image](https://img-blog.csdnimg.cn/img_convert/a18ac2c841047a56a74616d2b074cdb8.webp?x-oss-process=image/format,png)

![image](https://img-blog.csdnimg.cn/img_convert/7c3dbe127c096f2a5e3a22d7fb0b3b4b.webp?x-oss-process=image/format,png)



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

析——图片加载流程



### 最后

文章所有资料全部已经打包整理好,另外小编手头上整理了大量Android架构师全套学习资料,**Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析**

**资料展示:**

[外链图片转存中...(img-PXHq6RZM-1714277240212)]

[外链图片转存中...(img-ADCfcUmx-1714277240212)]

[外链图片转存中...(img-gFS4zUYW-1714277240213)]

[外链图片转存中...(img-sgV6Ea6B-1714277240213)]



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值