Android 面试题型个人汇总

Android 面试题型总结

android 四大框架层

Linux 内核
Android 运行库
应用框架
应用层

面向对象

封装:把一个的对象的属性私有化,提供被其他类访问的属性的方法,比如set,get。
继承:多态:重写和重载
重写:继承父类,方法名参数返回值相同,存在于父类和子类中,被定义为final的类不能被重写。
重载:方法名相同,参数的顺序,类型,个数不同。存在于父类子类同类中

继承和接口的区别:

1 继承只能继承一个类,而接口可以实现多个。
2 继承指的是拥有父类方法和属性。比如父类是动物,子类可以申明为猫,狗等具体的动物,实现表达的是拥 有某种功能,比如Serializable代表可序列化的。

Activity的四种启动方式

1、standard是系统默认模式。
Standard模式是每次都会创建新的Activity对象,当点击返回按钮时,他会将栈顶(当前Activity)消灭,然
后跳到下一层,不过此时在这个Activity中再次点击按钮创建对象时。
2、singleTop模式
singleTop模式在每次启动的时候会检测栈顶是不是要启动的activity,如果是,则不创建新的activity;否则,创建新的activity
3、singleTask模式
singleTask模式在每次启动的时候检测栈中是否存在将要启动的activity,如果存在,则会让要启动的activity之上的元素出栈,销毁要启动的activity之上的activity;如果不存在,创建出新的activity
4、singleInstance模式
singleInstance模式类似于浏览器,singleInstance模式在每次启动的时候会检测栈中是否存在将要启动的activity,如果存在,则将要启动的activity放到栈顶,确保至多有一个,与singleInstance不同的是,singleInstance模式不会销毁要启动的activity之上的activity

   个人小计:
   *standard:怎么样都要创建
    singleTop:顶上不是target Activity,new一个
    singleTask:顶上不是target Activity,移除target之上的,把自己变成top。
    singleInstance:开辟私有的task,完全独立于程序的其他activity的task。*

设置方式

     <activity android:name=".MainActivity" android:launchMode="standard" />  

RxJava的map 和flatmap 区别

   1 返回值上面:
      map变换后可以返回任意值,而flatMap则只能返回ObservableSource类型
      变换后的输出:
    map只能进行一对一的变换,而flatMap则可以进行一对一,一对多,多对多的变换,具体的变换规则根         据我们设置的变换函数mapper来定

自定义view 的刷新

  1. 自定义 View 继承自 View 类,然后重写他的onMeasure() 、onDraw()、和 onLayout()方法。
    在 onMeasure()方法中测量出自定义控件的宽和高并且调用setMeasuredDimension(width, height)方法将宽高配置好 (宽高)
    然后调用 onLayout()方法来确定自定义控件在布局中的位置,(位置)
    最后调用 onDraw()方法来将自定义 view 绘制在布局中。(画图)

UNSPECIFIED(未指定),父控件对子控件不加任何束缚,子元素可以得到任意想要的大小,这种MeasureSpec一般是由父控件自身的特性决定的。比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示,这个时候,size一般就没什么意义。

EXACTLY(完全),父控件为子View指定确切大小,希望子View完全按照自己给定尺寸来处理,跟上面的场景1跟2比较相似,这时的MeasureSpec一般是父控件根据自身的MeasureSpec跟子View的布局参数来确定的。一般这种情况下size>0,有个确定值。

AT_MOST(至多),父控件为子元素指定最大参考尺寸,希望子View的尺寸不要超过这个尺寸,跟上面场景3比较相似。这种模式也是父控件根据自身的MeasureSpec跟子View的布局参数来确定的,一般是子View的布局参数采用wrap_content的时候。
刷新
若需改变它的宽高,并且还要改变它的内容。需要使用到 view 的 requestLayout()方法及 invalidate ()方法。注意,须先调用 requestLayout() 方法再调用 invalidate ()方法。
具体原因是,在调用 requestLayout() 方法时,view 只会执行 onMeasure(先)及 onLayout(后)方法,而调用 invalidate ()方法时,view 会调用onDraw()方法。调用完这两个方法自定义控件就可以重绘及更新了。
若只改变宽高调用 requestLayout() 方法即可,若只更新内容调用 invalidate ()方法。

view事件分发

事件分发需要三个重要方法共同完成
DispatchTouchEvent(): 事件分发方法,他是决定了是由自己的onTouchEvent方法消费还是分发给子View让子View的dispatchTouchEvent来处理。
OnInterceptTouchevent(): true表示拦截了事件,那么事件将不再往下传递而是调用view本身的ontouchevent()方法,返回false表示不做拦截,事件将下发到子view的ondispatchevent()方法
Ontouchevent(): 包含action_up,action_down,action_move,返回true表示事件被消费,本次的事件终止,返回false表示事件没有被消费,将调用父view的ontouchevent()方法。
ViewGroup是view的子类,可以包含子view(或者viewGroup),view本身是不存在分发的,所以也没有拦截事件(onInterceptTouchevent),它只能在ontouchevent()方法中进行处理消费或者不消费。

点击事件的产生:activity-window-viewgroup-view

在这里插入图片描述

Activity生命周期

Oncreat:创建的时候调用,加载布局资源,初始化activity。
Onrestart:表示activity正在启动,当activity从不可见重新到可见的时候调用(比如从a跳到b,再从b回到a的时候,a会调用这个方法)
Onstart:表示activity正在启动,activity可见但是还在加载数据,不能与用户进行交互
Onresume:activity可见并且可与用户进行交互
Onpause:暂停
Onstop:停止
Ondestory:activity被销毁,在这个方法里面主要就是做一些回收工作和资源的释放。

Onstart和onstop配对:是否在前台
Onrestart和onresume配对:用户是否可见(不可见)

Onpause调用:home键,onpause—onstop—onrestart—onresume

内存溢出和内存泄漏

  内存溢出:                                                               内存泄露:
  oom (out of memory)                                                     memoryLeak
  应用所需的内存大于系统给予分配的内存空间               未使用的对象无法被垃圾回收器回收

常见的MemoryLeak分析

1.1 频繁的使用static关键字修饰
很多初学者非常喜欢用static类static变量,声明赋值调用都简单方便。由于static声明变量的生命周期其实是和APP的生命周期一样的(进程级别)。大量的使用,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static的合理使用一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。

1.2 BitMap隐患
Bitmap的不当处理极可能造成OOM,绝大多数情况应用程序OOM都是因这个原因出现的。Bitamp位图是Android中当之无愧的胖子,所以在操作的时候必须小心。
1.2.1 及时释放recycle。由于Dalivk并不会主动的去回收,需要开发者在Bitmap不被使用的时候recycle掉。
1.2.2 设置一定的压缩率。需求允许的话,应该去对BItmap进行一定的缩放,通过BitmapFactory.Options的inSampleSize属性进行控制。如果仅仅只想获得Bitmap的属性,其实并不需要根据BItmap的像素去分配内存,只需在解析读取Bmp的时候使用BitmapFactory.Options的inJustDecodeBounds属性。
1.2.3 最后建议大家在加载网络图片的时候,使用软引用或者弱引用并进行本地缓存,推荐使用android-universal-imageloader或者xUtils。

1.3 页面背景图
在布局和代码中设置背景和图片的时候,如果是纯色,尽量使用color;如果是规则图形,尽量使用shape画图;如果稍微复杂点,可以使用9patch图;如果不能使用9patch的情况下,针对几种主流分辨率的机型进行切图。

1.4 View缓存
在ListView和GridView中,列表中的很多项(convertView)是可以重用的,不需要每次getView就重新生成一项。另外,页面的绘制其实是很耗时的,findViewById也比较慢。所以不重用View,在有列表的时候就尤为显著了,经常会出现滑动很卡的现象。

1.5 引用地狱
Activity中生成的对象原则上是应该在Activity生命周期结束之后就释放的。Activity对象本身也是,所以应该尽量避免有appliction进程级别的对象来引用Activity级别的对象,如果有的话也应该在Activity结束的时候解引用。如不应用applicationContext在Activity中获取资源。
Service也一样。

1.6 BroadCastReceiver、Service 解绑
绑定广播和服务,一定要记得在不需要的时候给解绑。

1.7 handler 清理
在Activity的onDestroy方法中调用handler.removeCallbacksAndMessages(null);取消所有的消息的处理,包括待处理的消息;

1.8 Cursor及时关闭
在查询SQLite数据库时,会返回一个Cursor,当查询完毕后,及时关闭,这样就可以把查询的结果集及时给回收掉。

1.9 I/O流
I/O流操作完毕,读写结束,记得关闭。

1.10 线程
线程不再需要继续执行的时候要记得及时关闭,开启线程数量不易过多,一般和自己机器内核数一样最好,推荐开启线程的时候,使用线程池。

1.11 String/StringBuffer
当有较多的字符创需要拼接的时候,推荐使用StringBuffer。

广播和服务的区别

1.注册方式不同
2.广播生命周期较短,不能执行耗时操作,服务的生命周期较长可以做耗时操作

静态广播和动态广播的区别

静态广播在清单文件中注册,activity销毁之后仍然可以接收到广播,
动态广播在activity中注册,activity销毁之后就接收不到广播了,在acitivity销毁的时候记得销毁广播。

Service能不能更新ui

虽然service运行在主线程,但是service不能直接更新ui,可以使用广播通知activity去修改。(不能使用eventbus,eventbus不支持进程间的通信)

动态service和静态service区别

静态service在清单文件中注册,通过startService()启动服务,
生命周期 oncreate()------>onstartcommand()------->ondestory()

动态service在activity中注册,通过bindservice()和service建立联系,
生命周期oncreate()----->onbind()------>onunbind()------->ondestory();
静态service启动service之后调用者和service就没有关系了,调用者不能调用service里面的方法,调用者销毁之后服务依然在,动态service()通过bindService()方法

Handler机制

HandlerThread是一个集成了Looper和MessageQueue的线程,当启动handlerThread时,会同时生成Looper和Messager,等待消息进行处理
handler: 先进先出
Looper: 从 messageQueue 里存放或取出message,交给handler ,
messageQueue: 存放message的库

Recyclerview和Listview的区别

相同点:
1.ViewHolder是用来保存视图引用的类,无论是ListView亦或是RecyclerView。

2.ListView、Recyclerview的Adapter中,getView是最重要的方法,它将视图跟position绑定起来,是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。

ListView:

  1. 继承重写BaseAdapter类;
  2. 自定义ViewHolder与convertView的优化(判断是否为null);

RecyclerView:
3. 继承重写RecyclerView.Adapter与RecyclerView.ViewHolder
4. 设置LayoutManager,以及layout的布局效果
区别:

  1. ViewHolder的编写规范化,ListView是需要自己定义的,而RecyclerView是规范好的;
  2. RecyclerView复用item全部搞定,不需要想ListView那样setTag()与getTag();
  3. RecyclerView多了一些LayoutManager工作,但实现了布局效果多样化;

布局效果:
ListView 的布局比较单一,只有一个纵向效果;
RecyclerView 的布局效果丰富, 可以在LayoutMananger中设置:线性布局(纵向,横向),表格布局,瀑布流布局
在RecyclerView 中,如果存在的LayoutManager不能满足需求,可以在LayoutManager的API中自定义Layout:
例如:scrollToPosition(), setOrientation(), getOrientation(), findViewByPosition()等等;
空数据处理:
在ListView中有个setEmptyView() 用来处理Adapter中数据为空的情况;但是在RecyclerView中没有这个API,所以在RecyclerView中需要进行一些数据判断来实现数据为空的情况;
局部刷新
在ListView中通常刷新数据是用notifyDataSetChanged() ,但是这种刷新数据是全局刷新的(每个item的数据都会重新加载一遍),这样一来就会非常消耗资源;
RecyclerView中可以实现局部刷新,例如:notifyItemChanged();
但是如果要在ListView实现局部刷新,依然是可以实现的,当一个item数据刷新时,我们可以在Adapter中,实现一个onItemChanged()方法,在方法里面获取到这个item的position(可以通过getFirstVisiblePosition()),然后调用getView()方法来刷新这个item的数据;
动画效果:
在RecyclerView中,已经封装好API来实现自己的动画效果;有许多动画API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;如果我们需要淑贤自己的动画效果,我们可以通过相应的接口实现自定义的动画效果(RecyclerView.ItemAnimator类),然后调用RecyclerView.setItemAnimator() (默认的有SimpleItemAnimator与DefaultItemAnimator);
但是ListView并没有实现动画效果,但我们可以在Adapter自己实现item的动画效果;

嵌套滚动机制:
在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制;
ListView就没有实现嵌套滚动机制;

Service 和IntentService 的区别

service 开启线程执行任务
intentservice 本身有线程来执行耗时操作

设计模式

单例模式 :我们常使用的, 自己创建自己,只能new一次, 懒汉式和饿汉式,详情百度。
适配器模式:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所 造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
组合模式: 如我们常建的bean里包含多种格式,集合类,string类,int 类,布尔类
观察者模式: 订阅关系,被订阅者发生变动被触发时提示订阅者做相应的处理。
**状态模式:**对象的状态改变时,同时改变其行为,例如QQ:有在线、隐身、忙碌等状态,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。
装饰模式、工厂模式、建造模式等等。自行百度。

MVC / MVP /MVVP 区别

MVC: Model View Controller,是软件架构中最常见的一种框架,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示
MVP : Model View Presenter。view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,view层和presenter层的通信通过接口实现.
MVVP: Model-view-viewModel
presenter层换成了viewmodel层,还有一点就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。

mvp 相比 mvc 的优点:
Activity 代码变得更加简洁,

Dalvik 虚拟机 和 java 虚拟机 (JVM)

在这里插入图片描述
Dalvik和标准Java虚拟机(JVM)之间的首要差别之一,就是Dalvik基于寄存器,而JVM基于栈。
Dalvik和Java之间的另外一大区别就是运行环境——Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个 Dalvik应用作为一个独立的Linux进程执行。
(1)虚拟机很小,使用的空间也小;
(2)Dalvik没有JIT编译器;
(3)常量池已被修改为只使用32位的索引,以简化解释器;
(4)它使用自己的字节码,而非Java字节码。

android图片处理

图片压缩的几种方式:

  1. 质量压缩
  2. 尺寸压缩
  3. 采样率压缩
  4. libjpeg库来进行压缩
    质量压缩用于本地想服务器上传图片,或者保存到本地的时候
    尺寸压缩 用于本地缓存缩略图
    采样率压缩 设置图片的采样率,降低图片像素,减少内存使用,不用考虑图片放入内存后的释放,
    根据BitmapFactory. Options里的两个参数设置
    像素压缩可以按照当前view的大小进行压缩,压缩成缩略图显示在view中,减少内存的消耗,防止OOM,同时保证图片不失真。
    设置opts.inJustDecodeBounds= true时,图片的读取不会写入内存,可以获取图片的大小,从而设置需要压缩的Optios参数;opts.inJustDecodeBounds= false时图片的读取会写入内存中。

APP启动过程

在这里插入图片描述
启动流程:
①点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
②system_server进程接收到请求后,向zygote进程发送创建进程的请求;
③Zygote进程fork出新的子进程,即App进程;
④App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
⑤system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
⑥App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
⑦主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
⑧到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

参考链接:https://www.jianshu.com/p/a72c5ccbd150

HashMap的内部实现原理

HashMap概述:

HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  1. HashMap的数据结构:

在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
在这里插入图片描述
从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
https://zhangshixi.iteye.com/blog/672697
https://blog.csdn.net/mbshqqb/article/details/79799009

HashMap的工作原理
  HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。

AsyncTask的内部实现原理

完全参照: https://www.jianshu.com/p/37502bbbb25a在这里插入图片描述

进程间通信方式

https://blog.csdn.net/baidu_29094221/article/details/78852998
Activity可以跨进程调用其他应用程序的Activity;
ContentProvider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),
当然,也可以对其他应用程序的数据进行增、删、改操作;
Broadcast可以向android系统中所有应用程序发送广播,
而需要跨进程通讯的应用程序可以监听这些广播;
Service和Content Provider类似,也可以访问其他应用程序中的数据,
但不同的是,ContentProvider返回的是Cursor对象,
而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。

黄油刀(butterknife)实现原理

String、StringBuffer、StringBuilder区别

String:字符串常量 不适用于经常要改变值得情况,每次改变相当于生成一个新的对象
StringBuffer:字符串变量 (线程安全)
StringBuilder:字符串变量(线程不安全) 确保单线程下可用,效率略高于StringBuffer

rxjava是如何实现线程切换的

加密相关

参照: https://www.jianshu.com/p/5f88e5965839
对称加密(市场上最多的加密方式)
MD5
base64编码
非对称加密
SSH免密码登录(非对称加密)

对称加密:
需要对加密和解密使用相同的密钥的加密算法。(密钥本)
服务端的密钥与客户端的密钥是相同的。
常用算法:DES 3DES
优点:算法公开 速度快
缺点:安全性不高
有相关的加密工具
MD5:
将数据运算为另一固定长度值。(杂凑算法)
平均分成若干份,再在每一份中取固定值,再将其拼接起来。所以MD5是不可逆的。(通过杂凑算法得到密钥)
杂凑算法算出来的所有密钥的长度是相同的,不管字符串有多长。
服务器解析不了MD5的密钥(因此服务器端也不知道客户端的密码,只是保存着MD5后的密钥)
应用场合:

注册登录功能(验证用户信息)(服务器后台是不知道用户的密码的,因为发送给后台的是加密过后的密钥,不可以逆转得到用户的密码)
不能用于支付宝等交易平台,因为MD5是不能解密的。只能用于验证信息。
但事实上MD5也是可以解密的(不安全的),所以可以使用加“盐”加大MD5解密之后的复杂度。或者直接去掉密钥中的随机几个字符。

Base64编码:
对密钥进行base64加密

非对称加密:
服务器端和客户端都有两个密钥,且需要这两个密钥进行加密和解密。分别是公钥和密钥。他们之间的密钥和公钥是不同的。
每一台电脑上都可以通过命令得到公有的和私有的密钥,且每个电脑上的密钥是不同的。
公钥公开,密钥私有,安全性好(最安全)。但加密和解密时间长,速度慢,只适合少部分的加密。
用公钥加密,私钥解密。
如果使用非对称加密:第一件事必须交换公钥。
场合:银行(交换公钥,所以需要速度)

SSH免密码登录(非对称加密)
先进行握手确认双方是熟人。
原理是提前把本地的公钥放到服务器上。
SSH是一种网络协议,用于计算机之间的加密登录。
利用SSH协议可以有效防止远程管理过程中的信息泄露问题。

单例有哪几种?单例为什么要双重检查,优点? 那么双重检查就绝对是安全的嘛?

https://blog.csdn.net/xiao_nian/article/details/79999953

单例模式的定义有饿汉式、懒汉式、懒汉式同步锁、双重校验锁、静态内部类、枚举这几种定义方式

冒泡排序,选择排序,快速排序,二分查找

常见的java 题
https://www.cnblogs.com/xgjblog/p/5431254.html

ArrayList,Vector, LinkedList的存储性能和特性

ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差
LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

sleep() 和 wait() 有什么区别?

1.这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
2.最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
3.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. Sleep需要捕获异常,而wait不需要

抽象类与接口的区别(abstract与interface的区别)

abstract可以修饰抽象方法,而一个类只要有一个抽象方法,就必须用abstract定义该类,即抽象类。
用interface修饰的类,里面的方法都是抽象方法,因此在定义接口的时候,可以直接不加那些修饰,系统会默认的添上去。接口里面的字段都是公有常量,即public static final修饰的字段。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值