Android 相关源码分析

安卓mediasoup webrtc h264 编解码相关源码分析 mediasoup H264 支持 安卓 webrtc 视频采集流程源码分析 安卓选择软编码硬编码流程 mediasoup openh264 软编码源码分析 mediasoup 视频 H264 硬编码 解码器初始化流程 openh264 提供编解码相关函数 阅读详情

…

}

…

RequestBuilder requestBuilder =

new RequestBuilder(

httpMethod,

baseUrl,

relativeUrl,

headers,

contentType,

hasBody,

isFormEncoded,

isMultipart);

…

return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();

…

if (callFactory == null) {

callFactory = new OkHttpClient();

}

…

okhttp3.Call call = callFactory.newCall(requestFactory.create(args));

至此 Retrofit 已经组装好 okhttp3.Call 了,已经可以用了

而 Retrofit 灵活的一点是可以对这个 okhttp3.Call 进行封装,如封装成 retrofit2.Call、CompletableFuture、io.reactivex.Observable 等,这样你就可以随意选择自己喜欢的异步技术进行网络请求了

Retrofit 利用 CallAdapter.Factory 完成对 okhttp3.Call 的封装,利用 Converter.Factory 完成对请求响应的解析

Retrofit 默认有两个 CallAdapter.Factory,一个可以把 okhttp3.Call 封装成 CompletableFuture,一个可以把 okhttp3.Call 封装成 retrofit2.Call

List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);

callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

List<? extends CallAdapter.Factory> defaultCallAdapterFactories(

@Nullable Executor callbackExecutor) {

DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);

return hasJava8Types

? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
singletonList(executorFactory);

}

Retrofit 默认有两个转换器,一个可以把响应转成 ResponseBody,一个可以把响应转成 Optional

converterFactories.add(new BuiltInConverters());

converterFactories.addAll(this.converterFactories);

converterFactories.addAll(platform.defaultConverterFactories());

…

List<? extends Converter.Factory> defaultConverterFactories() {

return hasJava8Types ? singletonList(OptionalConverterFactory.INSTANCE) : emptyList();

}

[](()Retrofit 分析

Retrofit 最大个贡献是改变了描述 API 的方式,尤其是描述 RESTful API 的方式,让客户端对 API 的调用更加的简单、直观、安全

Retrofit 这种 “ 接口 + 抽象方法 + 注解 ” 的方式虽然可以实现 API 的描述,但是不能可视化,不能结构化,不能文档化,不能直接 mock,不能自动化测试,不能指定公共参数。所以我觉得换成 “ 配置文件(JSON?) + GUI 插件 ” 等其它方式要更好一点

[](()ViewStub 29


只是一个占位,继承 View,不绘制(setWillNotDraw(true)),且宽高为 0(setMeasuredDimension(0, 0)),inflate() 就是从父容器中移除自己并 inflate 给定的 view 到自己的本来的位置(index 和 layoutParams),由于移除后就不知道自己的父容器了所以 inflate() 只能调用一次。ViewStub 的 setVisibility() 方法一般不建议使用,如果用,那么在没 inflate() 的情况下会自动调用 inflate()

[](()Handler 29


Looper.prepare(); 可以给一个普通线程关联一个消息队列,Looper.loop(); 开始循环处理消息队列中的消息,new 一个 Handler 可以发送和处理消息,创建 Handler 需要指定 Looper,如果不指定那么表明是针对当前线程的 Looper 的,主线程有个创建好的 Looper.getMainLooper() 单例可以直接用

Looper.loop(); 是个死循环,循环获取队列中的消息,转发给消息的 target 去处理,也就是当初发送它的 Handler 去处理

而 Handler 处理消息的线程就是它关联的 Looper 所在的线程,也就是说创建 Handler 时传的 Looper 在哪个线程调用了 Looper.loop();,那么就在哪个线程回调 handleMessage()

for (;😉 {

Message msg = queue.next(); // might block

…

msg.target.dispatchMessage(msg);

}

queue.next() 最终调用的是名为 nativePollOnce() 的 native 方法,而该方法使用的是 epoll_wait 系统调用,表示自己在等待 I/O 事件,线程可以让出 CPU,等到 I/O 事件来了才可以进入 CPU 执行

而每次有新消息来的时候 enqueueMessage(),最终都会调用名为 nativeWake() 的 native 方法,该方法会产生 I/O 事件唤醒等待的线程

所以 nativePollOnce() / nativeWake() 就像对象的 wait() / notify() 一样,死循环并不会一直占用 CPU,如果没有消息要处理,就让出 CPU 进入休眠,只有被唤醒的时候才会进入 CPU 处理工作

IdleHandler 可以在消息队列中的消息都处理完了,进入休眠之前做一些工作,所以可以利用 Looper.myQueue().addIdleHandler() 做一些延迟任务,如在主线程中延迟初始化一些大对象或做一些可能耗时的操作

Handler 的延迟发消息功能如 sendMessageDelayed(),postDelayed() 是通过延迟唤醒实现的,在消息入队的时候就确定好消息要唤醒的时间,即 msg.when = SystemClock.uptimeMillis() + delayMillis,插入自己在队列中应该出现的位置,在取下一个消息时延迟 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE) 时间去取即可

线程池技术保持线程活跃也是通过 epoll 机制实现的,死循环中阻塞地从队列中取消息,利用 LockSupport.park() 或 Condition 的 await() 就能让线程保持活跃的同时让出 CPU 等待事件

[](()RecyclerView 1.1.0


[](()术语

目的: 在有限的窗口中显示大量的数据

Adapter: 为数据集中的数据项提供对应的 View

Position: 数据项在数据集中的位置

Index: View 在容器中的位置

Binding: 为 position 位置的数据准备对应 View 的过程

Recycle (view): 之前使用过的 View 可能会被放到缓存中,之后在显示相同类型数据时可以直接拿出来重用

Scrap (view): 进入临时 detached 状态的 View,不用完全 detached 就能重用

Dirty (view): 在被显示前必须重新绑定的 View

LayoutManager 维护的 LayoutPosition 理论上和 Adapter 维护的 AdapterPosition 是一样的,但是对数据集 position 的修改是马上就起效的,而修改布局的 position 需要一定时间。所以最好在写 LayoutManager 的时候用 LayoutPosition,写 Adapter 的时候用 AdapterPosition

如果数据集发生了变化,而你想通过 Diff 算法提升性能(只刷新必要的 View),那么可以直接使用 ListAdapter 这个 Adapter,它会在后台线程中比较新 List 和 旧 List 从而自动完成局部更新。或者在自己的 Adapter 中直接使用 AsyncListDiffer 实现。再或者直接使用 DiffUtil 工具比较列表也可以,更灵活,但也更繁琐

如果列表是有序的,那么使用 SortedList 可能比使用 List 要更好一些

[](()onMeasure

作为容器,RecyclerView 要干两件事,在 onMeasure() 中确定自己和孩子的尺寸,在 onLayout() 中布局孩子的位置

如果没指定 LayoutManager 或者它的宽高都是 MeasureSpec.EXACTLY 的,那么就跟普通 View 一样默认测量就行了

if (mLayout == null) {

defaultOnMeasure(widthSpec, heightSpec);

return;

}

if (mLayout.isAutoMeasureEnabled()) {

final int widthMode = MeasureSpec.getMode(widthSpec);

final int heightMode = MeasureSpec.getMode(heightSpec);

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

final boolean measureSpecModeIsExactly =

widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

if (measureSpecModeIsExactly || mAdapter == null) {

return;

}

if (mState.mLayoutStep == State.STEP_START) {

dispatchLayoutStep1();

}

mLayout.setMeasureSpecs(widthSpec, heightSpec);

mState.mIsMeasuring = true;

dispatchLayoutStep2();

mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

if (mLayout.shouldMeasureTwice()) {

mLayout.setMeasureSpecs(

MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),

MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));

mState.mIsMeasuring = true;

dispatchLayoutStep2();

mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

}

} else {

if (mHasFixedSize) {

mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

return;

}

…

}

大部分 LayoutManager 的 isAutoMeasureEnabled() 都是 true,表示使用 RecyclerView 的 自动测量机制 进行测量,此时,LayoutManager#onMeasure(Recycler, State, int, int) 内部只是调用了 defaultOnMeasure(),不要重写这个方法。false 的话就得重写

第一步 dispatchLayoutStep1() 主要处理 Adapter 的更新和动画运行 processAdapterUpdatesAndSetAnimationFlags();,保存动画过程中的 View 状态 mViewInfoStore.addToPreLayout(holder, animationInfo);,必要的话还会预布局并保存信息 recordPreLayoutInformation()

第二步 dispatchLayoutStep2() 对孩子进行真正的测量和布局 mLayout.onLayoutChildren(mRecycler, mState);

你会发现 dispatchLayoutStep2() 可能被调用多次,你也会发现 dispatchLayoutStep1() 和 dispatchLayoutStep2() 既有测量的功能也有布局的功能,虽然在 measure 里布局有点奇怪,但是在真正 layout 的时候能省这两步时间

[](()onLayout


onLayout() 中只是调用 dispatchLayout() 方法真正开始对子 View 进行布局

void dispatchLayout() {

if (mAdapter == null) {

Log.e(TAG, “No adapter attached; skipping layout”);

return;

}

if (mLayout == null) {

Log.e(TAG, “No layout manager attached; skipping layout”);

return;

}

mState.mIsMeasuring = false;

if (mState.mLayoutStep == State.STEP_START) {

dispatchLayoutStep1();

mLayout.setExactMeasureSpecsFrom(this);

dispatchLayoutStep2();

} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()

|| mLayout.getHeight() != getHeight()) {

// First 2 steps are done in onMeasure but looks like we have to run again due to

// changed size.

mLayout.setExactMeasureSpecsFrom(this);

dispatchLayoutStep2();

} else {

// always make sure we sync them (to ensure mode is exact)

mLayout.setExactMeasureSpecsFrom(this);

}

dispatchLayoutStep3();

}

最后一步 dispatchLayoutStep3(); 记录并开始 View 动画 mViewInfoStore.process(mViewInfoProcessCallback);,然后做一些必要的清理工作

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 [](()onDraw

onDraw() 中只需要绘制 ItemDecoration 即可:

mItemDecorations.get(i).onDraw(c, this, mState);

但是对于边界装饰的绘制或者 ItemDecoration#onDrawOver() 的实现就要重写 draw() 方法了

mItemDecorations.get(i).onDrawOver(c, this, mState);

[](()缓存

在第二步布局时 dispatchLayoutStep2() 会调用 mLayout.onLayoutChildren(mRecycler, mState);,所以在 onLayoutChildren() 中完成 View 的获取

以 LinearLayoutManager 的 onLayoutChildren() 为例,它的布局算法就是先检查孩子和其它变量,寻找锚点坐标,然后从尾到头的方向填充以及从头到尾的方向填充(fill()),然后滚动以满足需要

fill() 是一个神奇的方法,它可以在指定方向上填充满子 View

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,

RecyclerView.State state, boolean stopOnFocusable) {

…

while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

…

layoutChunk(recycler, state, layoutState, layoutChunkResult);

…

}

…

}

layoutChunk() 中就是取 View 的过程 View view = layoutState.next(recycler);

View next(RecyclerView.Recycler recycler) {

if (mScrapList != null) {

return nextViewFromScrapList();

}

final View view = recycler.getViewForPosition(mCurrentPosition);

mCurrentPosition += mItemDirection;

return view;

}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,

boolean dryRun, long deadlineNs) {

…

if (mState.isPreLayout()) {

holder = getChangedScrapViewForPosition(position);

}

if (holder == null) {

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

}

if (holder == null) {

if (mAdapter.hasStableIds()) {

holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),

type, dryRun);

}

if (holder == null && mViewCacheExtension != null) {

final View view = mViewCacheExtension

.getViewForPositionAndType(this, position, type);

if (view != null) {

holder = getChildViewHolder(view);

}

}

if (holder == null) {

holder = getRecycledViewPool().getRecycledView(type);

}

if (holder == null) {

holder = mAdapter.createViewHolder(RecyclerView.this, type);

}

}

…

return holder;

}

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {

final int scrapCount = mAttachedScrap.size();

for (int i = 0; i < scrapCount; i++) {

final ViewHolder holder = mAttachedScrap.get(i);

if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position

&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {

return holder;

}

}

if (!dryRun) {

View view = mChildHelper.findHiddenNonRemovedView(position);

if (view != null) {

final ViewHolder vh = getChildViewHolderInt(view);

mChildHelper.unhide(view);

mChildHelper.detachViewFromParent(layoutIndex);

scrapView(view);

return vh;

}

}

final int cacheSize = mCachedViews.size();

for (int i = 0; i < cacheSize; i++) {

final ViewHolder holder = mCachedViews.get(i);

if (!holder.isInvalid() && holder.getLayoutPosition() == position

&& !holder.isAttachedToTransitionOverlay()) {

if (!dryRun) {

mCachedViews.remove(i);

MTK安卓sim卡相关源码分析 最近由于一个sim卡相关的需求,就去了解了一下Android Sim卡相关的一些代码.在此记录一下. 简要说一下需求吧,需要在插拔卡的时候弹出对话框,提供界面让用户选择开启默认卡数据链接或者转移到另一张卡开启数据链接. 这个主要就是监听sim卡的状态.sim卡的状态.一般网上搜到的都是广播--"android.intent.action.SIM_STATE_CHANGED" 阅读详情

相关推荐

DolphinScheduler任务状态异常排查:从僵尸任务到数据库修复全流程

本文详细介绍了DolphinScheduler中僵尸任务的识别、诊断与修复全流程,包括数据库层面的SQL操作指南、系统恢复验证方法以及预防措施。针对任务状态异常问题,提供了从基础排查到高级源码分析的完整解决方案,帮助运维人员有效管理系统资源和工作流调度。

weixin_27966471的博客 132

OkHttp基本使用(五)拦截器

本篇介绍OkHttp的拦截器的使用。 首先我们需要了解什么事拦截器。打个比方,镖局押着一箱元宝在行走在一个山间小路上,突然从山上下来一群山贼拦住了镖局的去路,将镖局身上值钱的东西搜刮干净后将其放行。其中山贼相当于拦截器,镖局相当于一个正在执行任务的网络请求,请求中的参数就是镖局携带的元宝。拦截器可以将网络请求携带的参数进行修改验证,然后放行。这里面其实设计了AOP编程的思想(面向切面编程)。 拦截器的作用和好处。 在介绍拦截器的作用和好处之前,我们还是要回到山贼这个角色上,如果让你做一次山贼,你会在什么地方

chuyouyinghe的专栏 2286

【2021最新版】Dubbo面试题总结(40道题含答案解析)

文章目录1、为什么要用Dubbo?2、Dubbo的整体架构设计有哪些分层?3、默认使用的是什么通信框架,还有别的选择吗?4、服务调用是阻塞的吗?5、一般使用什么注册中心?还有别的选择吗?6、默认使用什么序列化框架,你知道的还有哪些?7、服务提供者能实现失效踢出是什么原理?8、服务上线怎么不影响旧版本?9、如何解决服务调用链过长的问题?10、说说核心的配置有哪些?11、Dubbo推荐用什么协议?12、同一个服务多个注册的情况下可以直连某一个服务吗?13、画一画服务注册与发现的流程图?14、Dubbo集群容错有

程序媛小琬的博客 1万+

新Java网络编程(更新中)

JAVA网络编程

keke的博客 1082

完整复现YOLOv8:包括训练、测试、评估、预测阶段【本文源码已开源,地址在文章末尾】

(这里主要是在colab.research.google.com实现的,本地的配置也是类似的方法)YOLOv8是一种用于目标检测的深度学习算法。主要参考:https://github.com/ultralytics/ultralytics/blob/main/README.zh-CN.md。yolov8的权重:https://github.com/ultralytics/assets/releases。yolov8的文档:https://v8docs.ultralytics.com/

源代码杀手的博客 1万+

PyCharm配置SSH远程解释器第3步时弹框报错:com.google.gson.stream.MalformedJsonException: Expected value at l...如何解决?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

**My Coding Family** 1927

Android之CompletableFuture一异步编程常用方法

CompletableFuture的使用

qq_46269365的博客 1680

2025年全国青少年信息素养大赛初赛真题(算法创意实践挑战赛C++初中组:文末附答案)

2025年全国青少年信息素养大赛初赛真题(算法创意实践挑战赛C++初中组:文末附答案)

王老师青少年编程 3390

okhttp之Request

转载请以链接形式标明出处: 本文出自:103style的博客 base on 3.12.0 目录 Request 简介 Request相关的配置方法 Header介绍 Request 简介 Request即我们构建的每一个HTTP请求。通过配置请求的 地址、http方法、请求头 等信息。 使用方法: Request request = new Request.Builder() ...

103style 2260

近五年体内微/纳米机器人赋能肿瘤精准治疗综述:以 GBM 为重点

摘要 微/纳米机器人在实体瘤治疗领域展现出突破性潜力,通过主动运动能力解决传统纳米药物递送效率低、渗透不足等核心问题。2021-2025年间形成三条技术路线:1) 磁驱、声驱等运动增强递送实现深层渗透;2) 与免疫治疗融合实现原位免疫唤醒;3) 针对胶质母细胞瘤开发跨屏障递送系统。研究重点从单一运动性能转向"运动-分布-疗效"的系统评价,需建立标准化指标评估运动学参数、肿瘤分布均匀性和安全性。未来挑战在于临床转化的可制造性、闭环操控及生物相容性优化,特别是对血脑屏障穿透等复杂场景的系统集

AllenLV的博客 5099

linux那些事之numa balance

numa balance numa balance是页迁移技术最重要也是最开始使用应用的技术,主要是为解决由于调度器进程在两个node迁移造成访问内存性能问题。 numba balance通过页迁移技术,将不在运行节点的内存迁移到所运行的节点上。对应numa balance要注意几点: 防止进程在两个节点之前来回切换造成频繁进行页迁移(ping pang现场)从而影响性能。numa balance本身就是为了提高性能,不能因为造成频繁页迁移而对性能进行一个反向优化。 多个线程分布在不同线程 访.

weixin_42730667的博客 3907

Java发送HTTP请求的工具类

一个用于发送HTTP请求的工具类,支持携带参数的GET、POST、DELETE等方法 工具类OkHttpUtils: import okhttp3.*; import okhttp3.Request.Builder; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.conc.

Xylon的博客 3597

【华为OD机试真题 新系统】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 2025C卷】959、 水库溃坝填补 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2025e卷,od机试,2025E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2025华为od,如何备战华

KJ.JK 2932

安卓9.0trim相关源码分析

1、trim是什么? 一条ATA指令,由操作系统发送给SSD主控制器,告诉它哪些数据占有的地址是“无效的”。 其实就是操作系统将一部分的控制权交给了SSD主控制器,让操作系统与SSD主控制器有一个互动。因为闪存需要先擦除才能再次写入数据,要得到空闲的闪存空间,SSD必须复制所有“有效”页到新的“空白页”里,并且擦除旧块,即垃圾回收;避免了在操作系统要重新写入数据时, SSD才会知...

javac_jj的博客 2614

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Flutter Android 端 FlutterView 相关流程源码分析

背景 前面文章我们分析了 flutter 在 android 端编译命令相关流程,也分析了 Flutter App dart 代码入口开始到三棵核心树诞生流程,按照源码顺序,我们接下来需要先分析一下 Flutter Android 端 framework 平台实现代码(非 native engine 引擎部分),下面以一个纯 Flutter App 为例展开分析。 工程结构及 API 变更 对于一个纯 flutter app 来说,当我们在 yaml 依赖中添加两个 flutter plugin 依赖,然后

工匠若水 7931

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

[Android 源码分析] dumpsys 调试工具相关介绍

[Android 源码分析] dumpsys 工具相关介绍引言常见用法源码分析 引言 dumpsys 是 Android 系统中的一个可执行程序,可以用于查询系统状态、定位问题,调试过程中经常会用到。 常见用法 指令 作用 dumpsys -l 列举所有支持 dumpsys 指令的服务 dumpsys activity top 查询最上层的应用 dumpsys media.camera 查询 Camera 的信息和使用情况 dumpsys meminfo 查看内存使用情况

a572423926的博客 1979

IEM模型,地表粗糙度模拟

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

android gui源码分析,Android View 相关源码分析之四 LinearLayout源码分析

LinearLayout 源码分析measure过程主要过程根据布局方向选择measure过程分支初始化相关变量对View进行第一次测量mTotalLength的再次测量二次测量部分View和对为测量的子View进行测量protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//判断布局方向if (mOrient...

weixin_33595018的博客 225
上一篇: Android应用-猜数字小游戏
下一篇: Android 面试:事件分发8连问
Java364102
博客等级 码龄5年 12粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值