Android四大组件——Service(整理)

一、概述

       Service是一个需要长期运行但不需要界面与用户交互的组件,其作用是告知系统有后台任务需要执行以及将自身应用程序的一些功能暴露给其他应用程序供其使用。
       特点:若无特别指定,则service运行在创建服务的应用程序的进程中,且所有代码默认都在主线程中运行。

二、生命周期

在这里插入图片描述

图1 生命周期

2.1 进程生命周期

  • 如果service正在调用onCreate()、onStartCommand()以及onDestory()方法,则service所在进程会变为前台进程以确保不会被系统杀死;
  • 一旦service启动,则其所在进程的级别比可见进程的级别低,但比一般的后台进程要高。随着service运行时间增长,其被系统杀死的可能性也增加;
  • service所在进程的级别与绑定service的客户端中最高的级别一致,但可以通过bindService()中的flags参数进行修改。
  • 可以通过调用void startForeground (int id, Notification notification)方法将service变为前台服务。其中,id为notification的唯一标识,不能为0。但在极端情况下,service也有可能会被系统杀死。

2.2 涉及函数

  • bindService( intent, serviceconnection, flags )
  1. ServiceConnection
private ServiceConnection serviceConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

       其中,ComponentName为与service绑定的组件名,IBinder为onBind()返回的对象,包含了暴露出来的可以操作service的方法,一个service只存在一个binder实例。onServiceConnected()方法在onBind()执行成功并返回非空IBinder对象之后被调用,onServiceDisconnected()方法在断开连接时调用,但主动解绑不会调用注:如果activity销毁,则activity与service之间的绑定关系也销毁,当service没有和任何component绑定,且不是通过startService()方法启动时,service会自动销毁。

  1. flags
    0:正常绑定,不进行其它操作;
    BIND_AUTO_CREATE:绑定service,若此时service还未创建,则创建service。
    BIND_ABOVE_CLIENT:service所在进程高于绑定该服务的客户端应用程序。
    BIND_ADJUST_WITH_ACTIVITY:如果该绑定来自一个activity,则service所在进程的级别变化由activity的可见性决定,无论与service绑定的客户端进程是否降低。
    BIND_ALLOW_OOM_MANAGEMENT:提高service进程在OOM(out of memory)下被杀死的可能;
    BIND_WAIVE_PRIORITY:允许将service进程加入后台LRU(least recently used)列表;
    BIND_IMPORTANT:当客户端进程为前台进程时,将service也提升至前台进程。通常service进程只能被客户端提升至可见进程,即使该客户端是前台进程。
  • public IBinder onBind(Intent intent)
    在与activity绑定时会自动调用,返回IBinder类型。IBinder可以在service内部定义继承自Binder的子类,并在子类中实现控制自身service的方法,供activity进行调用。
  • onCreate():在service第一次创建时调用
  • public int onStartCommand(Intent intent, int flags, int startId)
    在调用startService()方法时触发
  1. 返回值:表明service的当前启动状态,其中,可以是表示如果服务被kill掉,系统应该如何去重新启动该服务的参数。这些参数有:
    START_STICKY=1:重新创建服务并启动,但不保留intent,除非再次接收到启动命令;
    START_NOT_STICKY=2:服务被kill掉后不会再重新创建,除非再次接收到启动命令;
    START_REDELIVER_INTENT=3:重新创建服务并启动,并保留最后一次intent;
    START_STICKY_COMPATIBILITY=0:START_STICKY的兼容版本,但是不能保证被清理后onStartCommand方法一定会被重新调用。
  2. 系统提供的参数:
    intent:在调用startService(Intent intent)启动服务时传入;
    flags:启动请求的额外数据,表示当前这次启动的状态,有三个参数:
    0:正常创建;
    START_FLAG_REDELIVERY:表明这次启动是对应着上次onStartCommand()方法返回START_REDELIVER_INTENT后重新创建并执行的启动;
    START_FLAG_RETRY:表明这次启动是因为上次服务未能正常启动或有异常返回值,而再次进行的尝试启动。
    startId:表示当前请求的唯一标识,可供boolean stopSelfResult(startId)来停止服务时使用,stopSelfResult (startId)方法的作用是避免在停止服务时还有当前请求处理过程中新收到的但还未处理完的请求就直接停止服务的情况,stopSelfResult会将自身收到的startId与service最新收到的startId进行比对,二者一致就停止服务并返回true。
  • onDestroy():在服务销毁时调用,可以在此处进行释放资源的操作。
  • onUnbind(intent):当该Service上绑定的所有客户端都断开时会回调该方法!

三、Activity与Service通信

3.1 进程间通信IPC(interprocess communication)

3.1.1 进程隔离

       android系统中每个应用程序都运行在自己独立的进程中,每个进程有一个虚拟机的实例,而且一个应用程序中的service组件默认是运行在所属应用程序的进程中,但若有其他需求,service也可以设置android:process属性指定独立的进程。如果应用程序中的activity需要与本应用程序中运行在独立进程中的service进行通信或者与其他应用程序中的service进行通信,则会涉及到跨进程通信的问题。

3.1.2 用户空间和内核空间

       应用程序运行在用户空间,但由于进程隔离的缘故,所以不同应用程序之间不能相互访问,而系统内核运行在内核空间,是一片公共但相对独立的空间。应用程序不能随意访问内核空间,但能通过系统调用的方式在受控制的前提下进行访问,这给跨进程通信提供了机会。其中,系统调用指的是Linux内核中设置的一组用于实现各种系统功能的子程序,即由系统提供的函数,运行于内核态。用户可以通过系统调用命令在自己的应用程序中调用它们。
       无论是用户空间还是内核空间,使用的都是虚拟地址空间,并不能直接访问物理空间。 且每开启一个进程,32位系统就会分配一个4G大小的虚拟地址空间,linux系统将其中3G作为该进程的用户空间,而剩下的1G作为内核空间。对于每一个进程来说,它认为自己占用了系统所有的内存空间,空间地址都是从0开始。

3.1.3 可加载内核模块LKM(Loadable Kernel Module)

       binder驱动不属于系统内核,但由于linux的特性,即可加载内核模块,使得利用binder实现跨进程通信成为可能。可加载内核模块指的是一个具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。例如,当系统需要安装新的设备驱动程序(如视频卡,蓝牙设备或USB设备),文件系统驱动程序,甚至系统扩展,必须将这些驱动程序嵌入内核中才能完全正常运行。在某些情况下,要添加驱动程序,必须重建、编辑和重新引导整个内核,但是Linux能够在不经过整个过程的情况下向内核添加一些模块,即可加载内核模块。

3.2 Binder机制

       binder的实现方式是在service中创建binder对象,并将service中的一些可供其他应用或组件使用的方法暴露在binder对象中,当其他应用或组件需要调用service中的方法时,可以向service发送请求,service收到请求后将自身的binder对象的引用返回,供其他应用或组件使用,但实际上该binder引用中只包含了方法的声明,并没有具体功能的实现,当其他应用或组件需要调用service方法时,该binder引用只是负责收集参数并传输给service manager,service manager找到实际的binder对象所在service,并告知service调用binder对象执行相应的方法,将结果返回给binder引用,看上去就像binder引用去计算并返回的结果一样。除非service和发送请求的应用或组件在同一进程,此时返回的是binder实体。 service中有一个binder实体,而在应用程序或组件内部中有一个binder引用,该引用可看作是该binder实体的代理。在binder引用未接收到返回结果之前,应用或组件中发送调用请求的线程一直会处于阻塞状态。 其具体实现过程如下。

3.2.1 主要构成

       binder机制主要由四部分构成,Client、Server、Service Manager和Binder驱动程序。其中,发送请求的应用或组件作为client,提供功能的service作为server,而binder驱动程序建立通讯、传递binder对象等提供底层实现,service manager主要是为识别和匹配client想要的那个binder对象,具体如下:
在这里插入图片描述

图2 通信过程

       server创建binder并将自身功能暴露其中供client使用,但系统中不止一个server能提供binder,这就需要一个标识来区分各自提供的binder,该标识需要给应用开发人员使用,所以需要简单好记,而service manager的作用就是将这种标识和binder对象关联起来,并由系统统一管理,以免出现混淆。只要server要提供binder,就需要在service manager中进行注册。而client要请求某个binder,就需要service manager对标识进行解析。
       但是service manager并不运行在client或者是server进程中,而是独立运行在一个进程。那server向service manager注册binder、client经过service manager调用binder就涉及IPC,这样又需要使用binder与service manager进行通信,即binder注册后client才能找到自己想要的那个binder,而注册binder又需要使用binder。而这个binder是系统中用于server或者client与service manager进行通讯的,为此,需要有个binder不需要注册就能让server和client找到。所以,系统就在内部预先创建并规定了这样一个binder,专门供server和client与service manager通信使用。

3.2.2 接口定义语言AIDL(android interface definition language)

       binder机制中使用的接口语言,可以通过Android SDK的 platform-tools目录下的aidl.exe工具自动生成,在android studio中需要在main文件夹下新建aidl文件夹(与java文件夹同级),并在该文件夹下创建 .aidl文件。该文件中接口和方法前面不要加访问权限修饰符,也不能用static、final。AIDL默认支持Java基本类型,String,List,Map,CharSequence,除此之外的其他类型都需要import声明,对于使用自定义类型作为参数或者返回值,自定义类型需要实现Parcelable接口, 且需要显式import,即便二者在同一个包中。

  1. aidl文件
// IMyAidlInterface.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,            double aDouble, String aString);
}
  1. IInterface
package com.example.myapplication;
// Declare any non-default types here with import statements

public interface IMyAidlInterface extends android.os.IInterface
{
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements com.example.myapplication.IMyAidlInterface
	{
		 ...
	}
	/**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
	public int basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

       该文件在创建aidl文件并编译后会自动生成在build/generated/路径中的某个文件夹下。该java文件包含一个stub的抽象内部类以及在aidl文件中声明的方法,该方法不用具体实现。IInterface的作用就是声明server暴露出来供client调用的相关功能。
3. stub抽象内部类

public static abstract class Stub extends android.os.Binder implements com.example.myapplication.IMyAidlInterface
{
	private static final java.lang.String DESCRIPTOR = "com.example.myapplication.IMyAidlInterface";
	/** Construct the stub at attach it to the interface. */
	public Stub()
	{
		this.attachInterface(this, DESCRIPTOR);
	}
	/**
 	* Cast an IBinder object into an com.example.myapplication.IMyAidlInterface interface,
 	* generating a proxy if needed.
 	*/
	public static com.example.myapplication.IMyAidlInterface asInterface(android.os.IBinder obj)
	{
		...
	}
	@Override 
	public android.os.IBinder asBinder()
	{
		return this;
	}
	@Override 
	public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
	{
		...
	}
	private static class Proxy implements com.example.myapplication.IMyAidlInterface
	{
		...
	}
	static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

       IMyAidlInterface .stub类是个抽象类,继承Binder类并实现aidl定义的接口。stub的作用是在server中创建stub的子类去实现IInterface中声明的方法,将自身的功能暴露出来,该子类作为server的binder实体。在创建stub子类时会调用stub的构造方法,根据stub内置的DESCRIPTOR,将stub子类attach到IInterface。aidl接口中声明的方法在stub存在一个函数编号TRANSACTION_basicTypes,与声明的方法basicTypes()一一对应
4. asInterface

public static com.example.myapplication.IMyAidlInterface asInterface(android.os.IBinder obj)
{
	if ((obj==null)) {
		return null;
	}
	android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
	if (((iin!=null)&&(iin instanceof com.example.myapplication.IMyAidlInterface))) {
		return ((com.example.myapplication.IMyAidlInterface)iin);
	}
	return new com.example.myapplication.IMyAidlInterface.Stub.Proxy(obj);
}

       该方法的作用是可以在client中调用bindService(intent, serviceconnection, flag)绑定service时,传入的serviceconnection中的onServiceConnected(componentName, IBinder)回调函数中进行使用,接收binder驱动传来的binder,并将其转化为IInterface类型。在转化成IInterface的过程中,先调用binder的queryLocalInterface()在本地查找IInterface,如果这个binder是本地实体,即在本地创建了stub的子类对象(构造方法中会将stub对象与DESCRIPTOR 关联,DESCRIPTOR 为IInterface的全类名),则可以从本地找到对应的IInterface并返回,如果不是,则需要new一个stub.Proxy对象返回
5. stub.Proxy

private static class Proxy implements com.example.myapplication.IMyAidlInterface
{
	private android.os.IBinder mRemote;
	Proxy(android.os.IBinder remote)
	{
		mRemote = remote;
	}
	@Override 
	public android.os.IBinder asBinder()
	{
		return mRemote;
	}
	public java.lang.String getInterfaceDescriptor()
	{
		return DESCRIPTOR;
	}
	/**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
	@Override 
	public int basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
	{
		android.os.Parcel _data = android.os.Parcel.obtain();
		android.os.Parcel _reply = android.os.Parcel.obtain();
		int _result;
		try {
			_data.writeInterfaceToken(DESCRIPTOR);
			_data.writeInt(anInt);
			_data.writeLong(aLong);
			_data.writeInt(((aBoolean)?(1):(0)));
			_data.writeFloat(aFloat);
			_data.writeDouble(aDouble);
			_data.writeString(aString);
			mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
			_reply.readException();
			_result = _reply.readInt();
		}
		finally {
			_reply.recycle();
			_data.recycle();
		}
		return _result;
	}
}

       Proxy是stub的静态内部类,实现了IInterface,构造方法接收binder并对成员变量进行赋值。其中,Proxy对IInterface的实现是将基本类型写入parcel中,并调用binder代理的transact( code, parcel, parcel, flag) 方法将封装的parcel通过binder驱动传递出去,并调用server中的binder实体(即stub的子类)中的onTransact()方法,其中,flag表示运行方式,0——RPC(remote procedure call)表示在没有获得返回数据前将线程阻塞,FLAG_ONEWAY表示发送完请求后直接返回,不等待返回结果
6. onTransact

@Override 
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
	java.lang.String descriptor = DESCRIPTOR;
	switch (code)
	{
		case INTERFACE_TRANSACTION:
		{
			reply.writeString(descriptor);
			return true;
		}
		case TRANSACTION_basicTypes:
		{
			data.enforceInterface(descriptor);
			int _arg0;
			_arg0 = data.readInt();
			long _arg1;
			_arg1 = data.readLong();
			boolean _arg2;
			_arg2 = (0!=data.readInt());
			float _arg3;
			_arg3 = data.readFloat();
			double _arg4;
			_arg4 = data.readDouble();
			java.lang.String _arg5;
			_arg5 = data.readString();
			int _result =this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
			reply.writeNoException();
			reply.writeInt(_result);
			return true;
		}
		default:
		{
			return super.onTransact(code, data, reply, flags);
		}
	}
}

       该方法将接收的parcel参数解析出来,并根据aidl接口方法的函数编号,调用binder实体(即stub的子类对象,该对象在server中实现了IInterface方法)中对应的方法。

3.2.3 总结

  • IInterface:
    • 定义server中提供的功能;
    • stub静态抽象类,其子类作为server的binder实体;
  • stub
    • DESCRIPTOR:静态字符串常量,为自定义接口的全类名;
    • asInterface:将IBinder类型转化为自定义的IInterface类型,若binder是远程的,则创建一个proxy返回,proxy实现了自定义的IInterface接口。
      ——server和client的区别是:server和client都可以生成一份自定义接口的java文件,而server利用自定义接口创建了stub的子类对象,在stub的构造方法中会将stub对象与descriptor关联。所以可以根据descriptor进行查找,若能找到stub对象,则为server,若不能,则为远程访问,利用自定义接口创建stub.proxy。
    • stub.proxy:实现了自定义IInterface接口的静态类。
    • Stub.TRANSACTION_方法名:静态整型常量,表示自定义接口中的方法编号;
    • onTransact:接收请求数据并解析成参数,根据方法编号调用相应方法,方法执行完后返回相应数据。
  • proxy
    • 自定义接口方法的实现:将参数打包,并调用远程binder的transact()方法发送请求,binder驱动会找到binder实体调用onTransact()方法进行处理。

3.2.4 示例

  1. server创建binder实体
public class DownloadService extends Service {

    private IBinder myBinder = new IMyAidlInterface.Stub() {
    	@Override
    	public void startDownload_service(AppInfo appInfo) {
            ...
        }
		@Override
        public void setDownloadListener(DownloadListener downloadListener){
            ...
        }
		@Override
        public void pauseDownload(AppInfo appInfo){
            ...
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        ...
        return myBinder;
    }
  1. client接收binder
private ServiceConnection serviceConnection=new ServiceConnection() {
	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		mAidl = IMyAidlInterface.Stub.asInterface(service);
	}
	@Override
	public void onServiceDisconnected(ComponentName name) {
		mAidl = null;
	}
};

四、Messenger

4.1 原理

       Messenger可以看作是binder和handler的组合,使用消息队列来存放客户端请求。

  • 创建Messenger
    • 利用handler构造Messenger,成员变量IMessenger mTarget是MessengerImpl。
    • 利用IBinder构造Messenger,成员变量IMessenger mTarget是相应的interface,实体的或代理的。
public final class Messenger implements Parcelable {

    private final IMessenger mTarget;
    
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

	public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
}

final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
            return mMessenger;
        }
        mMessenger = new MessengerImpl();
        return mMessenger;
    }
}

private final class MessengerImpl extends IMessenger.Stub {
    public void send(Message msg) {
        msg.sendingUid = Binder.getCallingUid();
        Handler.this.sendMessage(msg);
    }
}

       其中,IMessenger相当于自定义的IInterface接口,send(message) 是接口中定义的方法,MessengerImpl相当于server中的binder实体,即IMessenger.stub,而stub实现IMessenger接口。

  • 返回IBinder
@Override
public IBinder onBind(Intent intent) {
	return mMessenger.getBinder();
}
public IBinder getBinder() {
	return mTarget.asBinder();
}

       返回binder是在server中进行返回,而在server中是通过handler构造的messenger,所以messenger中的成员变量IMessenger mTarget是MessengerImpl,即server中的binder实体,asBinder()方法返回的是其本身。

4.2 示例

  1. server
public class DownloadService extends Service {

    private static class MessengerHandler extends Handler{
    	@Override
    	public void handleMessage(Message msg) {
			// 处理client发送给server的message
			...
			try {
				//用client端的Messenger返回消息
                msg.replyTo.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
		}
    }
	
	private Messenger mMessenger=new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        ...
        return mMessenger.getBinder();
    }
  1. client
private ServiceConnection serviceConnection=new ServiceConnection() {
	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		Messenger mService=new Messenger(service);
		...
		message.replyTo=mRelyMessenger;
		try {
			//用server中的Messenger向server发送message
			mService.send(message);
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void onServiceDisconnected(ComponentName name) {
		...
	}
};

private Messenger mRelyMessenger=new Messenger(mRelyHandler);

public static class  RelyHandler extends Handler{
	@Override
	public void handleMessage(Message msg) {
		//处理server发送给client的message
		...
	}
}

五、处理耗时操作

5.1 AsyncTask

5.1.1 组成

       抽象类,需要创建子类继承。由两类线程池和一个handler组成。其中,线程池分为以下两类:

  • SERIAL_EXECUTOR:同步线程池,一次只执行一个任务,按顺序执行,其实是在THREAD_POOL_EXECUTOR外利用mtasks对任务进行管理;
  • THREAD_POOL_EXECUTOR:可执行并发任务,线程池在进程内共用,且有上限,超过之后会报错,所以AsyncTask默认使用SERIAL_EXECUTOR。

5.1.2 使用

       在定义子类时,可以设定三个泛型参数,params(需要传入的参数类型)、progress(进度的度量单位)、result(返回结果的数据类型);并且,可以选择重写4个方法:

  • onPreExecute():在执行任务前需要进行的初始化操作,如显示进度条等;更新UI
  • doInBackground(params…):自动开辟子线程执行耗时操作,并根据设定的result返回值类型返回处理结果。不可以更新UI,可以通过调用publishProgress(Progress…) 方法发布进度数据,调用后会立即调用onProgressUpdate(Progress…)方法。params参数通过execute()方法传入。
  • onProgressUpdate(Progress…)更新UI
  • onPostExecute(result):当doInBackground()方法返回处理结果后,会立即调用该方法,可以根据返回结果进行后续的收尾工作,如关闭进度条等。更新UI
  • onCanceled():在调用AsyncTask的子类对象的cancel()方法取消任务时触发。更新UI

       通过调用AsyncTask的子类对象的execute(params…) 或者executeOnExecutor() 方法触发任务执行,并传递参数。

public class DownloadTask extends AsyncTask<Void, Integer, Integer> {

    @Override
    protected Integer doInBackground(Void... strings) {
        ...
        return result;
    }

    @Override
    protected void onPostExecute(Integer integer) {
        super.onPostExecute(integer);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }
    
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected void onCancelled(Integer result) {
        super.onCancelled(result);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

5.2 IntentService

       intentservice也是基于异步消息处理机制实现的,适用场景:

  1. 执行完intent后不再需要service。service会自动停止;
  2. 针对一个任务需要划分多个子任务并依次执行的情况;可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的onHandleIntent()回调方法中执行,并且每次只会执行一个工作线程,依次处理。

       方式:

  1. 创建IntentService的子类,实现onHandleIntent(intent) 方法,该方法会自动开辟子线程;
  2. 必须重写构造方法,一般创建无参构造并调用父类的有参构造。
public class MyIntentService extends IntentService {

    public MyIntentService() {
        super(MyIntentService.class.getName());
    }
    
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
		...
    }
}

参考:
https://www.runoob.com/w3cnote/android-tutorial-service-3.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值