安卓应用程序的基础介绍

本文摘要 

|---应用程序组件

      |---组件的启动

|---配置文件

      |---声明组件

      |---声明应用程序的要求

|---应用程序的资源文件


正文

安卓----音译于英文单词Android, 其英文原始意思是”机器人”. 大陆地区习惯称其为”安卓”.

安卓是基于Linux平台的开源操作系统,主要使用于便携设备, 比如手机和平板电脑. 

安卓应用是用Java语言来写的. 安卓应用的安装包以 “.apk” 为后缀. 应用程序写好之后, 安卓SDK工具会将其编译到一个安卓包(Android package)中, 这个包就是前面提到的, 以“.apk” 为后缀的安装包. 安卓SDK工具所编译的内容包括: 实现一个应用程序(App)所用到的全部代码, 数据, 以及资源文件(比如图片, 动画, 自定义控件等等), 由这些代码和资源文件编译而来的“.apk” 文件包就被看做是一个安卓应用, 也就是我们日常说的 “App”, 它可以被安卓设备下载并安装. 虽然我们接触最多的安卓设备是智能手机, 但事实上, 除了手机, 安卓设备还有更广阔的定义, 比如远程医疗设备, 安卓电视, 智能冰箱, 远程空调等物联网设备. 

一旦在设备上成功安装, 那么, 每一个安卓应用程序都会乖乖的呆在属于自己的 “小天地”里, 这个“小天地”, 就是 “security sandbox”, 也就是 “安全沙盒”. 

---安卓操作系统是一种多用户的Linux系统, 在这个系统中, 每一个应用程序都是一个用户. 

--- 安卓系统会默认地为每一个应用程序签发一个独一无二的Linux用户ID. 这个ID仅仅是系统自己使用的, 应用程序是不会知道这个ID的. 系统会为应用程序中的所有文件设置权限, 这样, 只有该应用程序的用户ID才可以获得这些文件. 

---每一个进程都拥有自己的虚拟机(VM---virtual machine), 所以每个应用程序的代码都各自运行, 互相隔离. 

---默认情况下, 每一个应用程序都运行在属于自己的Linux进程中. 应用程序的任意一个组件需要被执行时, 安卓就会开启相应的进程; 同样, 当组件不再被需要了, 或者当内存吃紧, 系统为了保证其他应用程序的运行而回收内存时, 安卓就会关闭相应的进程. 

基于以上规则, 安卓系统实现了 “最小特权原则” (principle of least privilege). 意思就是: 每一个应用程序都默认地 只能 获取到有任务要完成的那个组件. 这就建立了一种非常安全的环境, 在这种环境下, 没有拿到权限的应用程序是无法使用相应的系统资源的. 

然而, 要想让数据在应用程序之间分享, 或者想让应用程序获得系统服务, 也还是有一些办法的: 

---可以让两个应用程序共享同一个Linux用户ID, 在这种情况下, 这两个应用程序就可以获取彼此的文件了. 为了节省系统资源, 拥有相同用户ID的应用程序还可以在同一个Linux进程中运行并且共用同一个虚拟机(VM).  (注意, 想要达到这种效果, 还得用相同的证书来签署这些应用程序.)

---应用程序可以对设备中的数据提出获取申请, 比如, 设备持有人的电话本, 短信, 可拔插存储设备(如SD卡), 摄像头, 蓝牙等等. 一个应用程序要用到的所有权限, 必须要在用户安装应用程序时就得到授权. 也就是说, 安装应用的人得点击 “同意” 或 “授权” 字样的按钮, 如果不同意授权的话, 应用程序是无法安装到设备(手机) 上的. 

以上内容简单解释了安卓应用程序是如何存在于系统中的. 本文余下部分将向你介绍以下内容:

l--安卓应用程序的核心组件.

2--配置文件----可以用来为应用程序声明组件, 还可以用于对设备性能提出要求.

3--与代码分离的资源文件, 这种分离制度可以使应用程序在不同的设备上有更好的表现效果. 


应用程序组件                                                            

应用程序组件是每一个安卓应用必不可少的组成部分. 每一个组件都可以作为系统进入应用程序的入口. 但是对于用户(人)来讲, 并非所有的组件都是进入程序的真实入口, 有一些组件的启动需要依赖另一个组件, 但是, 每一个组件都以其自身的实例的形式存在着, 并且各自扮演着特定的角色----每一个组件都是一个应用程序独特的组成部分, 它们帮助应用程序实现了所有的功能.

应用程序组件一共有四种: Activity, Service, Content provider, Broadcast receiver. 每一种组件都有具体的作用, 而且每一种组件都有具体的生命周期用来规定该组件如何被创建和销毁. 

下面对这四种安卓组件进行逐一介绍:

Activities----交互界面

一个activity 代表屏幕上展现的一个单屏的用户交互界面. 比如, 一个邮箱App有三个activity, 一个用来显示未读邮件的列表, 一个用来写邮件, 还有一个用来阅读邮件. 尽管这三个activity在邮箱App中紧密配合, 协同工作, 给用户带来良好的交互体验, 但是它们三个彼此仍是独立的. 并且, 只要邮箱App允许, 其他应用程序就可以随意开启这三个activity中的任意一个. 比如, 一个相机App可以开启邮箱App中负责写邮件的那个activity, 这样, 用户就可以利用邮件来分享拍摄的照片了. 

创建一个activity,必须要继承Activity类。更多内容请见安卓四大组件 之 Activity》

Services----服务

Service 的作用是: 在后台实现一些长期运行的操作; ‚实现远程进程调用. 与Activity不同, Service与用户之间是没有交互界面的, 它只在后台运行. 比如, 用户在操作一个安卓应用(比如相册)时, 另一个安卓应用----音乐播放器的service就可以在后台播放着音乐, 这样, 用户就可以边听音乐边操作手机(比如欣赏照片); 再比如, 当用户操作某个activity时, service可以在后台下载网络数据, 这样, 下载工作就不会干扰这个activity与用户之间的正常交互, 保证了良好用户体验. 其他组件(如activity) 可以开启service并让其完成某个任务, 或者干脆让service与自己绑定, 用来实现自己与service的交互.

Content providers----内容提供者

Content provider的作用是将应用程序的数据提供给其他应用程序. 在你的应用程序可以方便获取的前提下, 你可以将数据存储在文件系统中,SQLite数据库中, 网上, 或者任何持久性存储设备上. 那么, 别的应用程序如何对你的应用程序中的数据进行查询, 检索, 甚至修改的操作呢? 途径就是通过Content provider, 当然, 别的应用能对你的数据进行哪些操作, 还是由你来指定操作的权限. 比如, 安卓系统提供了一个Content provider, 这个Content provider管理着用户电话本中的数据. 那么, 任意一个拥有相应权限的应用程序都可以对这个Content provider的联系人数据部分(比如ContactsContract.Data)进行检索, 进而读取或写入具体某个联系人的信息. 

同样, Content provider也可以读写那些只属于你的应用程序的数据, 这种数据对于你的应用程序来说, 是私有的(private), 而且不可以被其他应用程序获取到. 比如, Note Pad示例应用就是使用一个content provider 来保存note. 

Broadcast receivers----广播接收者

Broadcast receiver组件可以对系统范围内的消息广播做出回应. 很多广播都是来自于系统的, 比如, 屏幕关闭了, 电池电量低了, 或者拍到了一张照片等等, 都是由系统发出广播来通知应用程序. 同样, 应用程序也可以发出广播, 比如, 当数据已经下载到本地设备上了, 下载数据的应用就可以通过发广播来通知别的应用赶快过来获取想要的数据. 尽管broadcast receiver不提供能够与用户交互的界面, 但是, 它却可以创建一个状态通知栏(a status bar notification) 来提醒用户: 一个广播事件发生啦!  然而更常见的情况是: broadcast receiver 仅仅是通向其他组件的一个 “入口”, 而且broadcast receiver的工作内容往往很少. 例如, 它可能会基于某个事件来发起一个service 去完成一些任务. 

安卓系统的一个独特之处就是: 任何一个应用程序都可以开启另外一个应用程序中的组件. 举个例子: 假设你的应用程序名字是myApp, 它没有拍照功能; 另外一个名字为cameraApp的应用程序中有拍照功能. 而你的应用程序myApp又想要为用户提供拍照功能, 那么, 你并不需要自己写组件来实现拍照功能, 你只需要开启cameraApp 中负责拍照的组件来完成拍照即可. 当照片拍摄完毕, 照片还是被返回到你的应用程序----myApp中. 从用户的角度来看, 拍照功能简直就是myApp中自带的功能, 他们不会感觉到实际上拍照功能是由另一个程序组件完成的. 

每当一个组件首次被系统开启, 系统就会为这个组件所在的应用程序开启一个进程, 并且实例化该组件所需要的类. 比如, 你的应用程序开启相机应用中负责拍照的activity来拍摄一张照片, 那么该activity所在的进程实际上属于相机应用, 而不属于你的应用. 所以, 不同于其他系统下的应用, 安卓应用没有类似于 “main()” 方法的具体的程序入口. 

在安卓系统中, 每个应用都各自运行在属于自己的进程中, 而且是带着文件权限运行, 该文件权限能够防止一个应用程序随意进入别的应用程序. 所以你的应用也不可以直接调用另一个应用的组件,  但是安卓系统却可以做到! 因此, 要想开启另一个应用中的某个组件, 你必须要给系统传递一个消息, 这个消息用来说明你的意图, 该意图就是 “intent” 参数, 它会帮你 “告诉” 系统, 你想要开启的是哪个组件. 然后, 系统才会帮你开启这个组件. 

组件的启动

在安卓的四大组件中, 有三个是通过一个叫做 “intent” 的异步消息来开启的, 这三个组件分别是: activity, service, 和 broadcast receiver. 无论两个组件是不是属于同一个应用程序, Intent都可以在运行时将这两个单独的组件互相捆绑(你可以把这个Intent理解为要求其他组件为某个任务“行动”起来的 “传令兵” ).

使用Intent对象可以创建一条intent, 这条intent中定义了一个指令, 该指令说明了到底是要启动某一个具体的组件还是要启动某一种类型的组件. 启动前者需要使用 “显示intent”; 启动后者需要使用 “隐式intent”. 

对于activity来说, 一条intent 明确了要实现的动作, 比如, 浏览或者发送一些东西.

对于service来说, 一条intent可能会指出要操作的数据的URI(组件被开启时可能会需要知道很多信息, URI就是其中之一). 例如, intent可以给某个activity传递一条要求展示图片或打开web页面的请求. 在某些时候, 还可以开启activity来接收一个结果, 在这种情况下, 这个activity也是在Intent中把结果带回来. 比如, 你的应用可以发起一个intent, 引导用户在电话本中选择一个联系人信息, 然后通过这个intent将其带回给你的应用, 那么, 这个返回的intent就包含了一个URI, 这个URI指向被选择的联系人).

对于broadcast receiver来说, intent仅仅规定了要被广播的消息. 比如, 当电池电量低时, 系统会发出一条广播用来提示电池状态, 但该广播只包含一个字符串: “电池电量低” . 

而余下的那个组件----content provider, 是不可以用intent来启动的. 需要注意的是, content provider只有被ContentResolver请求时才会启动. 这个content resolver 可以直接处理所有与content provider相关的数据往来业务, 所以, 处理这部分业务的组件只需要调用ContentResolver中的方法即可. 这种机制消除了content provider 与请求数据的组件之间的抽象层, 从而实现了数据的安全访问.

每种组件的启动方法:

  • 启动Activity: 在startActivity() 或 startActivityForResult() 方法中传入一个Intent, 可以开启一个新的activity或 给一个已经存在的activity派发新的任务. 如果你希望开启的这个activity可以返回一些数据, 请使用后者---startActivityForResult() . 
  • 启动Service: service有两种启动方法----开启(started)和绑定(bound). 开启: 在startService()方法中传入一个Intent, 可以开启一个没有被开启过的新服务或者给已经运行的服务分配新的任务. 绑定: 如果想要与service绑定, 可以使用bindService()方法, 并向该方法传递一个intent.
  • 发送广播可以使用Intent: 将intent作为参数传入这些方法----sendBroadcast(), sendOrderedBroadcast(), 或 sendStickyBroadcast()----就可以发起一个广播了. 
  • 要对content provider进行查询检索, 可以在ContentResolver中调用query()方法.


配置文件                                                                 

在安卓系统启动应用程序的某个组件之前, 系统必须要通过读取配置文件来知道要启动的组件是否存在. 这个配置文件就是AndroidManifest.xml 文件. 该文件必须放在应用程序工程目录的根目录下.  应用程序中所用到的全部组件, 都必须在配置文件中声明出来.  

除了声明应用程序的组件, 配置文件还可以做很多事情, 比如:

  • 确定应用程序要求的用户权限, 如互联网接入或读取用户的联系人信息等等. 
  • 基于应用程序所使用的API来声明该应用程序要求的最低API Level.
  • 声明应用程序需要的硬件及软件功能, 比如, 是否有摄像头, 是否有蓝牙服务, 或者屏幕是否支持多点触控等等. 
  • 应用程序需要链接的API库(不是安卓框架API哦!), 比如Google Maps library.
  • 其他

声明组件

配置文件最主要的功能就是: 让系统知道应用程序包含哪些组件. 比如, 配置文件是这样声明一个activity的:

<?xml version="1.0" encoding="utf-8"?>                                    

<manifest ... >                                                           

    <application android:icon="@drawable/app_icon.png" ... >              

        <activity android:name="com.example.project.ExampleActivity"      

                  android:label="@string/example_label" ... >             

        </activity>                                                       

        ...                                                               

    </application>                                                        

</manifest>                                                               

 

每个APP应用程序都有一个图标, 在<application>元素中,  “android:icon” 属性就指向这个图标的资源文件. 该资源文件的名字就是 “app_icon.png”.  

在<activity>元素中, “android:name”属性指定了Activity子类的全类名; “android:label” 属性为activity指定了一个用户可见的字符串标签. 

应用程序组件必须用以下方式来声明:

  • 声明activity要使用<activity> 元素
  • 声明service要使用<service>元素
  • 声明broadcast receiver要使用<receiver>元素
  • 声明content provider要使用<provider>元素
你写的源代码中包含的Activity, service, 以及content provider都必须要在配置文件中声明, 如果不声明, 那么这些组件就无法被系统识别到, 进而产生的结果就是----它们都无法运行. 然而, broadcast receiver比较特殊, 它既可以在配置文件中声明, 也可以在代码中动态生成(作为BroadcastReceiver对象)并且通过调用registerReceiver()方法将其注册在系统中.

声明组件的性能 

前面已经提到过: activity, service, broadcast receiver 可以使用Intent来启动. 想要启动哪个组件, 就把该组件的类名作为intent参数的内容, 这种做法就是使用 “显示意图” ---- 显示的指出目标组件的类名. 然而, 还有一个概念叫做 “动作意图” (intent action)----它才是intent真正的强大之处. 使用 “动作意图”, 你只需要简单的描述一下你想要实现的动作类型(或者只是指出你想要实现的这个动作是基于哪一类数据来完成的). 同时, 允许系统在设备中寻找一个可以完成你的 “动作意图” 的组件并开启该组件. 如果设备中不止一个组件可以完成intent中描述的动作, 那么最终由哪个组件来完成, 还得由用户来选择(系统会弹出对话框来提示用户做出选择). 

那么, 系统又是如何判断哪些组件能够完成intent中描述的动作的呢? 其实, 系统是用收到的intent与设备上的其他应用程序的(配置文件中的)intent过滤器(intent filter)进行对比来判断的.

在应用程序的配置文件中声明一个组件时, 你可以选择为该组件声明一个intent过滤器(intent filter), 该过滤器明确的指出该组件的性能----也就是说, 指出了该组件能够对哪些intent(来自其他应用程序)做出响应.

为组件声明intent过滤器的方法:
打开配置文件, 找到目标组件的声明元素, 然后在该元素内部添加一个<intent-filter>子元素即可.
举个例子, 假如有一个邮箱APP, 该APP里有一个activity是用来编辑新邮件的, 工程师在邮箱APP的配置文件中为这个用来编辑新邮件的activity添加了intent过滤器, 过滤器规定该activity可以响应其他APP发过来的关于发送邮件的intent. 也就是说, 当其他APP中的某个activity为了发送邮件而发出符合要求的intent时(ACTION_SEND), 系统就会拿着这个intent与邮箱APP中负责编辑新邮件的activity的intent过滤器进行比对, 如果比对成功, 而且这个intent又是由startActivity()方法传过来的参数, 那么系统就会开启这个编辑新邮件的activity.


声明应用程序的要求

使用安卓操作系统的终端设备有很多, 而且每个终端设备的参数和性能都不尽相同. 为了避免你写的APP被不符合性能要求的设备安装, 那么就必须在配置文件中明确的指出你的APP所能支持的硬件类型以及软件环境----这一点很重要哦! 其实多数情况下, 这些声明也就是罗列一下, 系统根本不会去读它们. 但是!! 像Google Play这种外部服务还是会读取这些声明的----目的在于: 当用户使用自己的设备搜索APP时, 帮助用户找到与自己的设备性能相匹配的APP.
举个例子, 如果你写的APP要求设备上必须有摄像头, 并且使用的API级别不能低于API Level 7(Android 2.1版本), 那么你就应该在配置文件中将这些要求都声明出来. 这样, 那些不带摄像头的设备以及安卓操作系统版本低于2.1的设备就不能够在Google Play中搜索到你的APP了.
当然了, 你也做出这样的声明: 你的APP会用到摄像头, 但是不强求设备必须带有摄像头. 在这种情况下, 你的APP就必须在运行时识别设备上是否有摄像头, 如果没有, 就要废弃所有用到摄像头的功能.

在你设计和开发APP时, 以下这些设备特性是需要你重点考虑滴:

1-屏幕尺寸和密度

安卓为每一种设备屏幕定义了两种分类标准: 屏幕尺寸和屏幕密度. 前者指屏幕的物理尺寸; 后者指屏幕的物理像素密度, 也就是dpi(dots per inch). 为了简化对屏幕类型的分类, 安卓系统将它们划分为几种类型:

屏幕尺寸分为: small, normal, large, extra large. (小, 正常, 大, 特大)

屏幕密度分为: low density, medium density, high density, extra high density(低密度, 中等 密度, 高密度, 超高密度)

在默认情况下, APP是可以兼容所有尺寸和密度的屏幕的, 因为安卓系统会为APP的UI 布局(layout)和图片资源做适度的适配优化. 但是你也可以用可选的布局(layout)资源单独给某种尺寸的屏幕写一个相应的布局或者为某种密度的屏幕准备相应的图片----要实现这个功能, 必须在配置文件中使用<supports-screens>元素来明确声明出你的APP具体支持哪种尺寸的屏幕. 

2-输入方式构成

许多设备都为用户提供多种输入机制, 比如键盘, 轨迹球, 或者那种周围是上下左右按键,中间是确认键的操纵盘(five-way navigation pad). 如果你的APP要求对输入机制有具体的要求, 那么你得在配置文件中做出声明(用<uses-configuration>元素). 但事实上, 很少有人会对APP的输入机制做出要求.

3-设备的配置

任何安卓设备都不可能包括最齐全的硬件和软件配置, 比如, 有些设备带有摄像头, 有些设备带有光线传感器, 有的设备带有某个版本的OpenGL, 有的设备则有高保真度的触摸屏. 同时, 咱也不能奢望某一种配置能够出现在所有设备上(当然了, 标准的安 卓库还是可以配备在每一个安卓设备上的). 所以, 你得在配置文件中使用<uses-feature>元素来声明你的APP要用到哪些配置.

4-平台版本

不同的安卓设备通常都运行着不同的安卓平台, 比如, 有的安卓设备用的是1.6版本, 有的则用的是2.3版本. 每一个新版本的安卓平台往往都会添加一些新的API, 这些API是老版本所没有的. 为了指明当前用的是哪套API, 每个版本的安卓平台都会指定一个API级别(API Level), 比如, 安卓1.0就是API Level 1; 安卓2.3就是API Level 9. 如果需要用到1.0版本之后新增的API, 那么你得在配置文件中使用<uses-sdk>元素来声明最低API Level.

在配置文件中对以上这些项目提出要求还是很重要的, 因为当你在Google Play上发布应用时, Google Play会根据具体的设备情况对众多APP进行过滤, 依照这个原则, 只有那些能够满足你的APP对硬件方面的所有要求的设备才能搜索到你的APP.


应用程序的资源文件                                                      

一个安卓APP不仅仅包括源代码, 还包括资源文件, 而且资源文件跟源代码还不能放在一起, 比如图片, 音频文件, 以及那些与APP的视觉呈现相关的文件(动画效果等). 举个例子, 你需要用XML文件来定义动画, 菜单, 样式, 颜色以及activity的布局(layout). 使用资源文件来做这些事情可以帮你减轻不少工作量----通过提供一系列可替代的资源文件来更新界面效果(而不是通过修改代码这种令人头疼的方式). 同时, 使用资源文件来做这些事也可以让你的APP可以轻松适配各种不同配置的设备(如:适应不同语言环境和不同尺寸的屏幕).

对于安卓工程中包括的每一个资源文件, SDK构建工具都会为其定义一个独一无二的整数型(integer)ID. 利用这个ID, 可以使资源文件被程序的代码所引用, 也可以被XML文件中定义的其他资源文件所引用. 举个例子, 假如在你的应用程序中包含一个图片文件, 文件名是 “logo.png” (该文件保存在res/drawable 目录下), 那么, SDK工具会为该图片文件生成一个资源ID叫做 “R.drawable.logo”, 当你想要引用这个图片时, 或者想要将这张图片插入用户交互界面时, 便可以使用这个资源ID来完成上述动作.

将资源文件与源代码分开存放确实有很多好处, 但是这么做最重要的作用就是: 应用程序可以为不同配置的硬件提供最适合的资源文件. 比如, 可以通过在XML文件中定义字符串的方式来定义UI界面需要显示的文字, 那么你可以将这些字符串翻译成各种语言并将它们按所属语言分别保存在各自的文件中. 然后, 根据“语言限定器(language qualifier)” 以及用户做出的语言设置, 安卓系统就会在UI界面上显示相应的语言文字. [注]: “语言限定器(language qualifier)” 就是你在资源目录名的后方添加的一种用来区分不同语种的名称----比如 “res/values-fr/” 中的 “values-fr” 就是这个限定器, 代表的是法文.

为了让应用程序的资源文件具备可选性和可替代性, 安卓支持多种限定器(qualifier). 限定器(qualifier)是一个短字符串----例如上面提到的“values-fr”---- 该字符串包含于你的资源目录名中, 作用就在于: 明确这些资源文件应该被哪一种配置的设备所使用. 再举个例子, 为了使某个activity能够很好的适配各种不同尺寸和方向的屏幕, 工程师们往往需要为同一个activity创建出几套不同的布局(layout). 比如, 假设一个屏幕的比例是16:9, 当屏幕处于竖屏状态(16是高, 9是宽)时, 布局中的按钮垂直排列比较顺眼; 当屏幕处于横屏状态(9是高, 16是宽)时, 布局中的按钮水平排列比较顺眼. 那么就需要根据屏幕的方向来变换相应的布局, 想要做到这一点, 编程人员需要定义两套不同的布局(layout)文件, 并且在每一个布局(layout)文件的文件名中加入相应的限定器(qualifier). 这样, 系统就会自动根据当前屏幕的方向来展示相应的布局了.

本文总结

-- Activity(界面), service(服务), content provider(内容提供者), broadcast receiver(广播接收者)并称安卓四大组件. 一个安卓应用程序由一个或多个上述应用程序组件构成. 

-- 每种组件在安卓应用程序中分别扮演着各自的角色, 并且每个组件都可以单独被另一个组件开启(甚至可以被其他应用程序中的组件开启). 

-- 一个应用程序中所用到的所有组件, 以及该应用程序的所有要求, 都需要在配置文件中声明出来, 比如该应用程序对设备的最低安卓版本以及硬件配置方面的要求.

-- 在应用程序中, 非代码的资源文件(图片, 字符串, 布局文件等等)都应该为不同配置的设备准备相应的文件, 比如为不同语言环境准备不同的文字字符串文件, 或者为不同尺寸的屏幕准备不同的布局文件等等. 













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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值