Android 之Kotlin封装Okhttp3和gson实现网络请求和下载

一、首先添加项目的Kotlin支持,这个不概述了,网上很多我主要就是讲述我怎么写的Kotlin网络请求

二、使用的开源项目

implementation 'com.squareup.okhttp3:okhttp:3.14.2'
implementation 'com.google.code.gson:gson:2.8.5'

其次还得加上

 compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
}

三、封装网络请求

1、先看看使用方式

2、实现

1)先看目录结构

2)创建OkHttphelper的单例Kotlin文件

在init方式中初始化需要网络请求的资源

 init {
        okHttpClient = OkHttpClient().newBuilder()
            .readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
            .writeTimeout(60, TimeUnit.SECONDS)//设置写的超时时间
            .connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
            //https支持
            .hostnameVerifier { hostname, session -> true }
            .sslSocketFactory(initSSLSocketFactory(), initTrustManager())
            .build()
        mHandler = Handler(Looper.getMainLooper())
        mGson = Gson()
    }

支持ssl的方法

  private fun initSSLSocketFactory(): SSLSocketFactory {
        var sslContext: SSLContext? = null
        try {
            sslContext = SSLContext.getInstance("SSL")
            val xTrustArray = arrayOf(initTrustManager())
            sslContext.init(
                null,
                xTrustArray, SecureRandom()
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }


        return sslContext!!.socketFactory
    }

    private fun initTrustManager(): X509TrustManager {
        return object : X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return arrayOf()
            }

            override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
            }

            override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
            }
        }
    }

3)创建枚举类定义请求的类型(get,post,上传,下载)

enum class HttpMethodType {
    GET, POST, UPLOAD_FILE, DOWNLOAD_FILE
}

4)创建BaseCallback方法,所有的请求处理类都会继承他

open class BaseCallback<T> {
    var mType: Type? = null

    init {
        mType = getSuperclassTypeParameter(javaClass)
    }

    companion object {
        internal fun getSuperclassTypeParameter(subClass: Class<*>): Type {

            val superclass = subClass.genericSuperclass
            if (superclass is Class<*>) {
                throw  RuntimeException("Missing type parameter.")
            }
            val parameterizedType = superclass as ParameterizedType

            return `$Gson$Types`.canonicalize(parameterizedType.actualTypeArguments[0])
        }
    }


    open fun onRequestBefore(request: Request) {}
    open fun onFailure(request: Request, e: IOException) {}
    open fun onSuccess(response: Response, t: Any) {}
    open fun onError(response: Response, code: Int, e: Exception) {}
    open fun onResponse(response: Response) {}
    open fun onProgress(response: Response, total: Long, current: Long) {}

}

5)创建Requet

private fun buildRequest(url: String, params: MutableMap<String, String>?, methodType: HttpMethodType): Request {
        val builder = Request.Builder()
        builder.url(url)

        when (methodType) {
            HttpMethodType.GET -> {
                mHttpMethodType = HttpMethodType.GET
                builder.get()
            }
            HttpMethodType.POST -> {
                mHttpMethodType = HttpMethodType.POST
                builder.post(buildFormData(params))
            }
            HttpMethodType.UPLOAD_FILE -> {
                mHttpMethodType = HttpMethodType.UPLOAD_FILE
                builder.post(buildFileData(params))
            }
            HttpMethodType.DOWNLOAD_FILE -> {
                mHttpMethodType = HttpMethodType.DOWNLOAD_FILE
                try {
                    this.mParams?.clear()
                    this.mParams = params
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        return builder.build()

    }

6)创建doRequest方法因为下载需要进度条,所有区分请求

 private fun doRequest(request: Request, callback: BaseCallback<*>) {
        callback.onRequestBefore(request)
        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback.onFailure(call.request(), e)
            }

            override fun onResponse(call: Call, response: Response) {
                if (mHttpMethodType == HttpMethodType.DOWNLOAD_FILE) {
                    if (response.isSuccessful) {

                        val length = response.body()!!.contentLength()
                        if (length == 0L) {
                            return
                        }
                        var inputS: InputStream? = null
                        var fos: FileOutputStream? = null
                        val buff = ByteArray(2048)
                        var len: Int = 0
                        var flag: Boolean = true

                        val localPath = mParams?.get("localPath")

                        val downEntity = DownLoadEntity(
                            "" + mParams?.get("localPath"),
                            "" + mParams?.get("serverPath")
                        )

                        try {
                            inputS = response.body()!!.byteStream()
                            var current: Long = 0



                            if (TextUtils.isEmpty(localPath)) {
                                callback.onResponse(response)
                                throw RuntimeException("localPath is miss")
                            }
                            val file = File(localPath)

                            if (file.exists()) {
                                file.delete()
                            }
                            fos = FileOutputStream(file)

                            while (flag) {
                                len = inputS.read(buff)
                                flag = len != -1
                                if (flag) {
                                    callBackProgress(callback, response, length, current)
                                    current += len.toLong()
                                    fos.write(buff, 0, len)
                                }
                            }
                            fos.flush()

                        } catch (e: Exception) {
                            callback.onResponse(response)
                            e.printStackTrace()
                        } finally {
                            try {
                                inputS?.close()
                                fos?.close()
                            } catch (e: Exception) {
                                callback.onResponse(response)
                                e.printStackTrace()
                            }

                            callback.onResponse(response)
                            callBackSuccess(callback, response, downEntity)
                        }
                    } else {
                        callBackError(callback, response, response.code(), null)
                    }
                } else {
                    callback.onResponse(response)

                    if (response.isSuccessful) {

                        if (callback.mType == String::class.java) {
                            val resultStr = response.body()!!.string()

                            Log.i("wxf", "server_data->" + resultStr)

                            callBackSuccess(callback, response, resultStr)
                        } else if ("" + callback.mType == "byte[]") {
                            callBackSuccess(callback, response, response.body()!!.bytes())
                        } else {
                            try {
                                val resultStr = response.body()!!.string()

                                Log.i("wxf", "server_data->" + resultStr)

                                val mObject = Gson().fromJson<Any>(resultStr, callback.mType)
                                callBackSuccess(callback, response, mObject)
                            } catch (e: Exception) {
                                e.printStackTrace()
                                callBackError(callback, response, response.code(), e)
                            }

                        }
                    }
                }

            }


        })
    }

7)创建以上四个请求方法

 fun get(url: String, baseCallback: BaseCallback<*>) {
        doRequest(buildRequest(url, null, HttpMethodType.GET), baseCallback)
    }

    fun post(url: String, params: MutableMap<String, String>?, baseCallback: BaseCallback<*>) {
        doRequest(buildRequest(url, params, HttpMethodType.POST), baseCallback)
    }

    /**
     *  this map must have localFilePath parma
     */
    fun upLoadFile(url: String, params: MutableMap<String, String>, callback: BaseCallback<*>) {
        doRequest(buildRequest(url, params, HttpMethodType.UPLOAD_FILE), callback)
    }

    /**
     *  this must have localPath parma
     */
    fun downLoadFile(url: String, params: MutableMap<String, String>?, callback: BaseCallback<*>) {

        if (params != null) {
            if (TextUtils.isEmpty(params.get("localPath"))) {
                throw RuntimeException("map localPath is miss")
            } else {
                mParams?.put("localPath", "" + params?.get("localPath"))
            }
            if (!TextUtils.isEmpty(url)) {
                mParams?.put("serverPath", url)
            }
        }

        doRequest(buildRequest(url, params, HttpMethodType.DOWNLOAD_FILE), callback)
    }

四、所有类的完整代码

1)DownLoadEntity

data class DownLoadEntity(
    val localPath: String,
    val serverPath: String
)

2)FileCallBack

abstract class FileCallBack<T> : BaseCallback<DownLoadEntity> {
    internal var dialog: ProgressDialog? = null
    private var msg: String? = null


    constructor(context: Context) {
        if (dialog == null) {
            dialog = ProgressDialog(context)
            dialog!!.setCanceledOnTouchOutside(false)
        }
        dialog!!.setMessage("加载中......")
    }

    constructor(context: Context, message: String) {
        if (dialog == null) {
            dialog = ProgressDialog(context)
            dialog!!.setCanceledOnTouchOutside(false)
        }
        this.msg = message
        dialog!!.setMessage(message)
    }



    override fun onRequestBefore(request: Request) {
        showDialog()
    }

    override fun onFailure(request: Request, e: IOException) {
        dissmissDialog()
    }


    override fun onResponse(response: Response) {
        dissmissDialog()
    }

    override fun onProgress(response: Response, total: Long, current: Long) {
        try {
            if (TextUtils.isEmpty(msg)) {
                msg = "加载中"
            }
            if (dialog != null && dialog!!.isShowing)
                dialog!!.setMessage(msg + (current * 1f / total * 100).toInt() + "%")
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    private fun dissmissDialog() {
        if (dialog != null && dialog!!.isShowing) {
            dialog!!.dismiss()
            dialog = null
        }
    }

    private fun showDialog() {
        if (dialog != null && !dialog!!.isShowing) {
            dialog!!.show()
        }
    }
}

3)OkHttpHelper

object OkHttpHelper {

    private val okHttpClient: OkHttpClient
    private val mHandler: Handler
    private val mGson: Gson
    private var mHttpMethodType: HttpMethodType? = null
    private var mParams: MutableMap<String, String>? = null

    init {
        okHttpClient = OkHttpClient().newBuilder()
            .readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
            .writeTimeout(60, TimeUnit.SECONDS)//设置写的超时时间
            .connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
            //https支持
            .hostnameVerifier { hostname, session -> true }
            .sslSocketFactory(initSSLSocketFactory(), initTrustManager())
            .build()
        mHandler = Handler(Looper.getMainLooper())
        mGson = Gson()
    }

    private fun initSSLSocketFactory(): SSLSocketFactory {
        var sslContext: SSLContext? = null
        try {
            sslContext = SSLContext.getInstance("SSL")
            val xTrustArray = arrayOf(initTrustManager())
            sslContext.init(
                null,
                xTrustArray, SecureRandom()
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }


        return sslContext!!.socketFactory
    }

    private fun initTrustManager(): X509TrustManager {
        return object : X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                return arrayOf()
            }

            override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
            }

            override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
            }
        }
    }

    private fun buildFormData(params: Map<String, String>?): RequestBody {
        val body = FormBody.Builder()
        Log.i("wxf", "params::" + params.toString())
        if (params != null) {
            params.forEach {

                try {
                    if (!TextUtils.isEmpty(it.key)) {
                        body.add(it.key, it.value)
                    }
                } catch (e: Exception) {
                }
            }
        }

        return body.build()
    }

    private fun buildFileData(params: Map<String, String>?): RequestBody {
        val builder = MultipartBody.Builder()
        builder.setType(MultipartBody.FORM)
        if (params != null) {
            params.forEach {
                try {
                    if (!TextUtils.isEmpty(it.key)) {
                        if (it.key == "file") {
                            val file = File(it.value)
                            builder.addFormDataPart(
                                it.key, file.name,
                                RequestBody.create(null, file)
                            )
                        } else {
                            builder.addFormDataPart(it.key, "" + it.value)
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }


        return builder.build()
    }

    private fun buildRequest(url: String, params: MutableMap<String, String>?, methodType: HttpMethodType): Request {
        val builder = Request.Builder()
        builder.url(url)

        when (methodType) {
            HttpMethodType.GET -> {
                mHttpMethodType = HttpMethodType.GET
                builder.get()
            }
            HttpMethodType.POST -> {
                mHttpMethodType = HttpMethodType.POST
                builder.post(buildFormData(params))
            }
            HttpMethodType.UPLOAD_FILE -> {
                mHttpMethodType = HttpMethodType.UPLOAD_FILE
                builder.post(buildFileData(params))
            }
            HttpMethodType.DOWNLOAD_FILE -> {
                mHttpMethodType = HttpMethodType.DOWNLOAD_FILE
                try {
                    this.mParams?.clear()
                    this.mParams = params
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        return builder.build()

    }

    private fun callBackError(callback: BaseCallback<*>, response: Response, code: Int, e: Exception?) {
        if (mHttpMethodType != HttpMethodType.DOWNLOAD_FILE) {
            mHandler.post { callback.onError(response, code, e!!) }
        } else {
            callback.onError(response, code, e!!)
        }

    }

    private fun callBackSuccess(callback: BaseCallback<*>, response: Response, mObject: Any) {

        if (mHttpMethodType != HttpMethodType.DOWNLOAD_FILE) {
            mHandler.post {
                callback.onSuccess(response, mObject)
            }
        } else {
            callback.onSuccess(response, mObject)
        }

    }

    private fun callBackProgress(callback: BaseCallback<*>, response: Response, total: Long, current: Long) {
        mHandler.post { callback.onProgress(response, total, current) }

    }

    private fun doRequest(request: Request, callback: BaseCallback<*>) {
        callback.onRequestBefore(request)
        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                callback.onFailure(call.request(), e)
            }

            override fun onResponse(call: Call, response: Response) {
                if (mHttpMethodType == HttpMethodType.DOWNLOAD_FILE) {
                    if (response.isSuccessful) {

                        val length = response.body()!!.contentLength()
                        if (length == 0L) {
                            return
                        }
                        var inputS: InputStream? = null
                        var fos: FileOutputStream? = null
                        val buff = ByteArray(2048)
                        var len: Int = 0
                        var flag: Boolean = true

                        val localPath = mParams?.get("localPath")

                        val downEntity = DownLoadEntity(
                            "" + mParams?.get("localPath"),
                            "" + mParams?.get("serverPath")
                        )

                        try {
                            inputS = response.body()!!.byteStream()
                            var current: Long = 0



                            if (TextUtils.isEmpty(localPath)) {
                                callback.onResponse(response)
                                throw RuntimeException("localPath is miss")
                            }
                            val file = File(localPath)

                            if (file.exists()) {
                                file.delete()
                            }
                            fos = FileOutputStream(file)

                            while (flag) {
                                len = inputS.read(buff)
                                flag = len != -1
                                if (flag) {
                                    callBackProgress(callback, response, length, current)
                                    current += len.toLong()
                                    fos.write(buff, 0, len)
                                }
                            }
                            fos.flush()

                        } catch (e: Exception) {
                            callback.onResponse(response)
                            e.printStackTrace()
                        } finally {
                            try {
                                inputS?.close()
                                fos?.close()
                            } catch (e: Exception) {
                                callback.onResponse(response)
                                e.printStackTrace()
                            }

                            callback.onResponse(response)
                            callBackSuccess(callback, response, downEntity)
                        }
                    } else {
                        callBackError(callback, response, response.code(), null)
                    }
                } else {
                    callback.onResponse(response)

                    if (response.isSuccessful) {

                        if (callback.mType == String::class.java) {
                            val resultStr = response.body()!!.string()

                            Log.i("wxf", "server_data->" + resultStr)

                            callBackSuccess(callback, response, resultStr)
                        } else if ("" + callback.mType == "byte[]") {
                            callBackSuccess(callback, response, response.body()!!.bytes())
                        } else {
                            try {
                                val resultStr = response.body()!!.string()

                                Log.i("wxf", "server_data->" + resultStr)

                                val mObject = Gson().fromJson<Any>(resultStr, callback.mType)
                                callBackSuccess(callback, response, mObject)
                            } catch (e: Exception) {
                                e.printStackTrace()
                                callBackError(callback, response, response.code(), e)
                            }

                        }
                    }
                }

            }


        })
    }

    fun get(url: String, baseCallback: BaseCallback<*>) {
        doRequest(buildRequest(url, null, HttpMethodType.GET), baseCallback)
    }

    fun post(url: String, params: MutableMap<String, String>?, baseCallback: BaseCallback<*>) {
        doRequest(buildRequest(url, params, HttpMethodType.POST), baseCallback)
    }

    /**
     *  this map must have localFilePath parma
     */
    fun upLoadFile(url: String, params: MutableMap<String, String>, callback: BaseCallback<*>) {
        doRequest(buildRequest(url, params, HttpMethodType.UPLOAD_FILE), callback)
    }

    /**
     *  this must have localPath parma
     */
    fun downLoadFile(url: String, params: MutableMap<String, String>?, callback: BaseCallback<*>) {

        if (params != null) {
            if (TextUtils.isEmpty(params.get("localPath"))) {
                throw RuntimeException("map localPath is miss")
            } else {
                mParams?.put("localPath", "" + params?.get("localPath"))
            }
            if (!TextUtils.isEmpty(url)) {
                mParams?.put("serverPath", url)
            }
        }

        doRequest(buildRequest(url, params, HttpMethodType.DOWNLOAD_FILE), callback)
    }


}

enum class HttpMethodType {
    GET, POST, UPLOAD_FILE, DOWNLOAD_FILE
}

4)SpotsCallBack

abstract class SpotsCallBack<T> : BaseCallback<T> {
    private var dialog: ProgressDialog? = null
    private var isShow: Boolean = true

    constructor(mContext: Context) {
        try {
            if (dialog == null)
                dialog = ProgressDialog(mContext)
            setMessage("加载中......")
        } catch (e: Exception) {
//            e.printStackTrace()
        }
    }

    constructor(mContext: Context, isShow: Boolean) {
        try {
            if (dialog == null)
                dialog = ProgressDialog(mContext)
            this.isShow = isShow
            setMessage("加载中......")
        } catch (e: Exception) {
//            e.printStackTrace()
        }

    }

    constructor(context: Context, message: String) {
        try {
            if (dialog == null)
                dialog = ProgressDialog(context)
        } catch (e: Exception) {
//            e.printStackTrace()
        }

        setMessage(message)
    }



    override fun onRequestBefore(request: Request) {
        if (isShow) showDialog()
    }

    override fun onFailure(request: Request, e: IOException) {
        dismissDialog()
    }

    override fun onProgress(response: Response, total: Long, current: Long) {

    }

    abstract override fun onSuccess(response: Response, t: Any)

    abstract override fun onError(response: Response, code: Int, e: Exception)

    override fun onResponse(response: Response) {
        dismissDialog()
    }

    private fun showDialog() {
        try {
            if (dialog != null && !dialog!!.isShowing)
                dialog?.show()
        } catch (e: Exception) {
//            e.printStackTrace()
        }

    }

    private fun dismissDialog() {
        try {
            if (dialog != null && dialog!!.isShowing)
                dialog?.dismiss()
        } catch (e: Exception) {
//            e.printStackTrace()
        }

    }

    private fun setMessage(message: String) {
        try {
            if (dialog != null && !TextUtils.isEmpty(message))
                dialog!!.setMessage(message)
        } catch (e: Exception) {
//            e.printStackTrace()
        }

    }
}

五、注意

1、在kotlin中也可以写basejson类,只不过需要写为open类

 open class BaseJson {
    var code: Int?=null
    var msg: String?=null
    var message: String?=null
    var errorCode: String?=null
    var score: Int?=null
}

2、使用的时候可以直接继承就好

data class UpdateVersionclazz(
    val ret: UpdateRet
) : BaseJson() {
    data class UpdateRet(
        val version: String,
        val version_no: String,
        val url: String,
        val forced: String
    )
}

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Kotlin 中使用 OkHttp3 下载文件并带有下载进度,可以通过以下步骤实现: 1. 添加 OkHttp3 依赖 在 app module 的 build.gradle 文件中添加以下代码: ``` dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' } ``` 2. 创建 OkHttp3 客户端 在代码中创建一个 OkHttpClient 客户端: ``` val client = OkHttpClient() ``` 3. 创建下载请求 使用 OkHttp3 的 Request.Builder 创建一个下载请求,并设置下载 URL 和保存文件的路径: ``` val request = Request.Builder() .url("https://example.com/file.zip") .build() ``` 4. 创建下载监听器 定义一个回调接口,用于监听下载进度: ``` interface DownloadListener { fun onDownloadProgress(progress: Int) } ``` 在代码中实现这个接口,并在其中更新下载进度,例如: ``` val listener = object : DownloadListener { override fun onDownloadProgress(progress: Int) { runOnUiThread { // 更新下载进度条 progressBar.progress = progress } } } ``` 5. 发起下载请求 使用 OkHttpClient 的 newCall 方法发起下载请求,并在 enqueue 方法中传入一个 Callback 参数,该参数将在下载完成时回调: ``` client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 下载失败 } override fun onResponse(call: Call, response: Response) { val inputStream: InputStream = response.body?.byteStream() ?: return // 保存文件并更新下载进度 val totalSize: Long = response.body?.contentLength() ?: -1 var downloadedSize: Long = 0 val outputStream = FileOutputStream("/storage/emulated/0/Download/file.zip") val buffer = ByteArray(8192) while (true) { val bytes = inputStream.read(buffer) if (bytes == -1) break outputStream.write(buffer, 0, bytes) downloadedSize += bytes val progress = (downloadedSize * 100 / totalSize).toInt() listener.onDownloadProgress(progress) } outputStream.close() inputStream.close() // 下载完成 } }) ``` 这段代码中,我们首先从 response.body 中获取输入流并创建输出流,然后使用循环逐段读取输入流的数据,再将其写入输出流,并计算下载进度,最后调用 DownloadListener 的 onDownloadProgress 方法更新下载进度。在下载完成后,我们需要关闭输入流和输出流,以及在 onFailure 方法中处理下载失败的情况。 6. 完整代码 最终的代码应该类似于这样: ``` interface DownloadListener { fun onDownloadProgress(progress: Int) } val client = OkHttpClient() val request = Request.Builder() .url("https://example.com/file.zip") .build() val listener = object : DownloadListener { override fun onDownloadProgress(progress: Int) { runOnUiThread { // 更新下载进度条 progressBar.progress = progress } } } client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 下载失败 } override fun onResponse(call: Call, response: Response) { val inputStream: InputStream = response.body?.byteStream() ?: return // 保存文件并更新下载进度 val totalSize: Long = response.body?.contentLength() ?: -1 var downloadedSize: Long = 0 val outputStream = FileOutputStream("/storage/emulated/0/Download/file.zip") val buffer = ByteArray(8192) while (true) { val bytes = inputStream.read(buffer) if (bytes == -1) break outputStream.write(buffer, 0, bytes) downloadedSize += bytes val progress = (downloadedSize * 100 / totalSize).toInt() listener.onDownloadProgress(progress) } outputStream.close() inputStream.close() // 下载完成 } }) ``` 注意,这段代码中保存文件的路径是硬编码的,你需要根据实际需求修改它。另外,为了更新 UI,我们需要在 onDownloadProgress 方法中使用 runOnUiThread 方法,以确保在主线程中执行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值