移动开发笔记(十五) Jetpack Kotlin DSL

Jetpack

jetpack时一个开发组件的工具集

1.ViewModel

viewModel的一个重要作用就是可以帮助Activity分担一部分工作(当手机发生横竖屏旋转时Activity会被重新创建,而ViewModel的生命周期不会)
先在app/build.gradle文件中添加依赖:

  implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'

为MainActivity创建一个对应的MainViewModel类

class MainViewModel : ViewModel(){
    var counter = 0
}

修改activity_main.xml中的代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextView
        android:id="@+id/infoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="32sp"
        ></TextView>
    <Button
        android:id="@+id/plusOneBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="加一"
        ></Button>
</LinearLayout>

最后修改MainActivity中的代码

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }
    private fun refreshCounter(){
        infoText.text=viewModel.counter.toString()
    }
}

通过ViewModelProviders来获取ViewModel的实例
ViewModelProviders.of(<你的Activity或Fragment 实例>).get(<你的ViewModel>::class.java)
在这里插入图片描述
2.向ViewModel传递参数
修改MainViewModel中的代码


class MainViewModel(countReserved:Int) : ViewModel(){
    var counter = countReserved;
}

向ViewModel传递参数选哟借助ViewModelProvider.Factory
新建一个MainViewModelFactory类,并让它实现ViewModelProvider.Factory接口

class MainViewModelFactory(private val countReserved) :ViewModelProvider.Factory{
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved ) as T
    }
}

我们在界面上新增清零按钮

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

...
    <Button
        android:id="@+id/clearBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="清零"
        ></Button>
</LinearLayout>

最后修改MainActivity中的代码

class MainActivity : AppCompatActivity() {
...
    lateinit var sp : SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
     ...
        sp=getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved",0)
        viewModel = ViewModelProviders.of(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        clearBtn.setOnClickListener {
            viewModel.counter=0
            refreshCounter()
        }
        refreshCounter()
    }
    private fun refreshCounter(){
        infoText.text=viewModel.counter.toString()
    }

    override fun onPause() {
        super.onPause()
        sp.edit{
            putInt("count_reserved",viewModel.counter)
        }
    }
}

在这里插入图片描述

2.Lifecycles

首先创建MyObserver类

class MyObserver :LifecycleObserver{

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart(){
        Log.d("MyObserver","activityStart")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver","activityStop")
    }
}

生命周期事件一共七种ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP和ON_DESTROY。还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期。
在MainActivity中加入代码,就可以帮我们自动感知

lifecycle.addObserver(MyObserver())

在这里插入图片描述
为了能主动感知到Activity的生命周期状态,修改MyObserver

class MyObserver(val lifecycle : Lifecycle) :LifecycleObserver{

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart(){
        Log.d("MyObserver","activityStart")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver","activityStop")
    }
}

有了Lifecyle对象之后,我们可以在任意地方调用lifecyle.currentState来主动获知当前生命周期状态。lifecycle.currentState返回的生命周期状态是一个枚举类型,一共又INITIALIZED,DESTROYED,CREATED,STARTED,RESUMED这五种状态类型,

LiveData

在数据变化的时候通知给观察者。LiveData特别适合于ViewModel结合使用
修改MainViewModel中的代码

class MainViewModel(countReserved:Int) : ViewModel(){
    val counter = MutableLiveData<Int>()
    
    init {
        counter.value=countReserved
    }
    fun plusOne(){
        val count = counter.value ?:0
        counter.value=count+1
    }
    fun clear(){
        counter.value=0
    }
}

MutableLiveData是一种可变的LiveData,它主要有3种读写数据的方式,分别是getValue(),setValue()和postValue()方法。setValue只能在主线程力给LiveData设置数据,postValue()方法用于在非主线程中给LiveData设置数据。
修改MainActivity中的代码

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel

    lateinit var sp : SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
        plusOneBtn.setOnClickListener {
            viewModel.plusOne()
        }
        clearBtn.setOnClickListener {
            viewModel.clear()
        }
        viewModel.counter.observe(this, Observer {count ->
            infoText.text=count.toString()
        })
      ...
    }
    private fun refreshCounter(){
        infoText.text=viewModel.counter.toString()
    }

    override fun onPause() {
        super.onPause()
        sp.edit{
            putInt("count_reserved",viewModel.counter.value ?:0)
        }
    }
}

这里调用了它的observe()方法来观察数据变化。接收两个参数第一个参数是一个LifecycleOwner对象,第二个参数是一个Observer接口,当counter中包含的数据发送变化时,就会回调到这里。
我们可以加入对observe()方法的语法扩展,需要在app/build.gradle文件中添加如下依赖

 implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

我们就可以使用如下语法结构的observe()

  viewModel.counter.observe(this){ count ->
            infoText.text=count.toString()
        }

比较推荐的做法永远不爆露不可变的LiveData给外部。这样非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据

class MainViewModel(countReserved:Int) : ViewModel(){
    val counter : LiveData<Int>
        get() = _counter
    private val _counter = MutableLiveData<Int>()
    init {
        _counter.value=countReserved
    }
    fun plusOne(){
        val count = counter.value ?:0
        _counter.value=count+1
    }
    fun clear(){
        _counter.value=0
    }
}

这里先将议案来的counter变量名改为_counter变量,并给它加上private修饰符,这样_counter变量对于外部就是不可见的。然后我们有重新定义了一个counter,将它生命为不可变的LiveData,并在它的get
()属性方法中返回_counter变量。
当外部调用count变量时,实际上获得是_counter的实例

map 和 switchMap

map()这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换
创建一个User类

data class User(var firstName : String,var lastName : String, var age: Int)

在ViewModel中创建一个相应的LiveData来包含User类型的数据


class MainViewModel(countReserved:Int) : ViewModel(){
    private val userLiveData = MutableLiveData<User>()
    
    val userName : LiveData<String> = Transformations.map(userLiveData){user->
        "${user.firstName} ${user.lastName}"
    }
  ...
}

这里我们调用了Transformations的map()方法来对LiveData的数据类型转换。map()方法接收两个参数:第一个是原始的LiveData对象,第二个参数是一个转换函数。
我们来模拟一种情况(服务器请求数据库查一个User对象),新建一个Repository单例类:

object Repository {
    fun getUser(userId : String) : LiveData<User>{
        val liveData = MutableLiveData<User>()
        liveData.value= User(userId,userId,0)
        return liveData
    }
}

在MainViewModel中定义一个getUser()方法

class MainViewModel(countReserved:Int) : ViewModel(){
...
    fun getUser(userId : String) : LiveData<User>{
        return Repository.getUser(userId)
    }
}

switchMap()方法可以派上用场,修改MainViewModel中的代码

class MainViewModel(countReserved:Int) : ViewModel(){
    private val userLiveData = MutableLiveData<User>()
    ...
    private val userIdLiveData = MutableLiveData<String>()
    val user:LiveData<User> = Transformations.switchMap(userIdLiveData){userId->
        Repository.getUser(userId)
    }
    fun getUser(userId:String){
        userIdLiveData.value=userId
    }

 /*   fun getUser(userId : String) : LiveData<User>{
        return Repository.getUser(userId)
    }
    
  */
}

这里定义了一个新的userIdLiveData对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()党法,用来对另一个可观察的LiveData对象进行转换.
switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,第二个参数是一个转换函数,必须在这个转换函数中返回的LiveData对象转换成另一个刻观察的LiveData.我们只需在转换函数中调用Repository的getUser()方法来得到LiveData对象。
创建一个按钮来获取User

<Button
        android:id="@+id/getUserBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="获取User"
        ></Button>

修改MainActivity中的代码

getUserBtn.setOnClickListener {
            val userId = (0..1000).random().toString()
            viewModel.getUser(userId)
        }
        viewModel.user.observe(this, Observer {user->
            infoText.text = user.firstName
        })

在这里插入图片描述
ViewModel中某个获取数据的方法是没有参数的这样写

class MyViewModel : ViewModel(){
	private val refreshLiveData = MutableLiveData<Any?>()
	val refreshResult = Transformation.switchMap(refreshLiveData){
		Repository.refresh()
	}
	fun refresh(){
	refreshLiveData.value = refreshLiveData.value
	}

}

Room

为Android数据库设计的ORM框架
ORM(Object Relational Mapping)也叫对象关系映射,将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是ORM。
1.使用Room增删改查
Room整体结构由Entity,Dao和Databases这三部分组成
Entity,用于定义实体类
Dao,数据访问对象
Databases,定义数据库关键信息

apply plugin: 'kotlin-kapt'
dependencies {
 implementation 'androidx.room:room-runtime:2.1.0'
    kapt 'androidx.room:room-compiler:2.1.0'
    }

修改User类,完成实体类声明

@Entity
data class User(var firstName : String,var lastName : String, var age: Int){
    
    @PrimaryKey(autoGenerate = true)
    var id:Long = 0
}

使用@Entity将它声明成实体类,在User类中添加一个id字段,并使用@PrimaryKey注解成主键,再把autoGenerate参数指定成true,使得主键的值是自动生成的。
新建一个UserDao接口

@Dao
interface UserDao {
    @Insert
    fun insertUser(user : User) : Long
    
    @Update
    fun updateUser(newUser : User)
    
    @Query("select * from User")
    fun loadAllUsers() : List<User>
    
    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age : Int) : List<User>
    
    @Delete
    fun deleteUser(user : User)
    
    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName : String) : Int
}

定义Database,新建一个Appdatabase类

@Database(version = 1,entities = [User::class])
abstract class AppDatabase : RoomDatabase(){
    
    abstract fun userDao() : UserDao
    
    companion object{
        private var instance : AppDatabase?=null
        
        @Synchronized
        fun getDatabases(context: Context) :AppDatabase{
            instance?.let { 
                return it
            }
            return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
                .build().apply { 
                    instance=this
                }
        }
    }
}

我们在companion object结构体中编写了一个单例类,原则上只有一份AppDatabase实例。用Room.databaseBuilder()方法来构建Appdatabase实例。databaseBuilder()方法接收3个参数,注意第一个参数一定要使用applicationoContext,第二个参数是AppDatabase的Class类型,第三个是数据库名。
修改activity_main.xml中的代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

 ...
    <Button
        android:id="@+id/getUserBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="获取User"
        ></Button>
    <Button
        android:id="@+id/addDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="添加数据"
        ></Button>
    <Button
        android:id="@+id/updateDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="更新数据"
        ></Button>
    <Button
        android:id="@+id/deleteDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="删除数据"
        ></Button>
    <Button
        android:id="@+id/quertDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="查询数据"
        ></Button>
</LinearLayout>

最后修改MainActivity中的代码

....
        val userDao = AppDatabase.getDatabases(this).userDao()
        val user1 = User("垃圾佬","王",40)
        val user2 = User("高富帅","李",13)
    addDataBtn.setOnClickListener { 
            thread { 
                user1.id=userDao.insertUser(user1)
                user2.id=userDao.insertUser(user2)
            }
        }
        updateDataBtn.setOnClickListener { 
            thread { 
                user1.age=18
                userDao.updateUser(user1)
            }
        }
        deleteDataBtn.setOnClickListener { 
            thread { 
                userDao.deleteUserByLastName("王")
            }
        }
        quertDataBtn.setOnClickListener { 
            thread { 
                for (user in userDao.loadAllUsers()){
                    Log.d("MainActivity",user.toString())
                }
            }
        }

在这里插入图片描述
在这里插入图片描述
Room默认是不允许在主线程中进行数据库操作的,为了测试方便,提供了一种简单的方法

 Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
              	.allowMainThreadQueries()
                .build()

建议只在测试环境下使用
2.Room的数据库升级
在开发阶段,提供了一种简单粗暴的方法

 Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
              	.fallbackToDestructiveMigration()
                .build()

但是Room会将当前数据库销毁重建,导致数据库中所有数据全部丢失

新创一张Book表,创建Book实体类

@Entity
class Book(var name:String,var pages : Int) {

    @PrimaryKey(autoGenerate = true)
    var id : Long=0
}

再创建一个BookDao类

@Dao
interface BookDao {
    @Insert
    fun insertBook(book:Book) : Long
    
    @Query("select * from Book")
    fun loadAllBooks() : List<Book>
}

最后修改AppDatabase中的代码

@Database(version = 2,entities = [User::class,Book::class])
abstract class AppDatabase : RoomDatabase(){

    abstract fun userDao() : UserDao
    abstract fun bookDao() :BookDao				<-------修改这里

    companion object{
        val MIGRATION_1_2 = object : Migration(1,2){        <-------修改这里
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("create table Book (id integer primary key autoincrement not null," +
                        "name text not null," +
                        "pages integer not null)")
            }
        }

        private var instance : AppDatabase?=null


        @Synchronized
        fun getDatabases(context: Context) :AppDatabase{
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
                .addMigrations(MIGRATION_1_2)     <------修改这里
                .build().apply {
                    instance=this
                }
        }
    }
}

假如增加新的列

@Entity
class Book(var name:String,var pages : Int,var author : String) {

    @PrimaryKey(autoGenerate = true)
    var id : Long=0
}

修改AppDatabases中的代码

@Database(version = 3,entities = [User::class,Book::class])
abstract class AppDatabase : RoomDatabase(){
...
  
        val MIGRATION_2_3 = object : Migration(2,3){
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("alter table Book add column author text not null default 'unknown'")
            }
        }
           @Synchronized
        fun getDatabases(context: Context) :AppDatabase{
            instance?.let {
                return it
            }
         return Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
                .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                .build().apply {
                    instance=this
                }
        }
    }
}

WorkManager

implementation 'androidx.work:work-runtime:2.2.0'

第一步定义一个后台任务,这里创建一个SimpleWorker类

class SimpleWorker(context : Context,params : WorkerParameters) : Worker(context,params) {
    override fun doWork(): Result {
        Log.d("SimpleWork","SimpleWork正在干活")
        return Result.success()
    }
}

doWork()方法不会在主线程中执行,可以放心执行耗时任务。doWork方法要求返回一个Result对象,用于表现运行结果,成功就返回Result.success()失败就返回Result.failuer()。还有一个Result.retry()方法,它其实也代表失败,只是可以结合WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务。
最基本的配置

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()

OneTimeWorkRequest.Builder是WorkRequest.Builder的子类。WorkRequest.Builder还有另外一个子类PeriodicWorkRequest.Builder用于构建周期性运行的后台任务请求,为了降低性能,PeriodicWorkRequest.Builder构造函数传入周期间隔不能低于15分钟

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()

最后一步将构建任务请求传入WorkManager

WorkManager.getInstance(this).enqueue(request)

2.WorkManager处理复杂的任务
1.

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
	.setInitialDelay(5,TimeUnit.MINUTES)
	.build()

让SimpleWorker这个后台任务在5分钟后运行
2.给后台任务请求添加标签

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
	.addTag("simple")
	.build()

最主要的一个功能我们可以通过标签来取消后台任务请求

WorkManager.getInstance(this).cancelWorkByTag("simple")

也可以通过id来取消后台任务请求

WorkManager.getInstance(this).cancelWorkById(request.id)

我们也可以一次性取消所有后台

WorkManager.getInstance(this).cancelAllWork()

3.我们可以利用Result.retry(),结合setBackoffCriteria()方法来重新执行任务

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
	.setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS)
	.build()

setBackoffCriteria()方法接收3个参数:第二个和第三个参数指定多久之后重新执行任务,时间最短不能少于10秒。第一个参数用于指定如果任务再次执行失败,下次重试的时间应该是什么样的形式延迟。可选值两种LINEAR和EXPONENTIAL,前者代表下次重试时间以线性方式延迟,后者代表下次重试时间以指数方式延迟。
我们可以使用如下代码对后台任务的运行结果进行监听。

WorkManager.getInstance(this)
	.getWorkInfoByIdLiveData(request.id)
	.observe(this){ workInfo ->
	if(workInfo.state ==WorkInfo.State.SUCCEEDED){
		log.d("MainActivity","do work succeeded")
	}else if(workInfo.state==WorkInfo.state.FAILED){
	log.d("MainActivity","do work failed")
	}
}

我们也可以哟个getWorkInfoByTagLiveData()方法来监听同一标签下后台任务请求运行结果
4.WorkManager特色功能—链式任务
这里定义了3个独立的后台任务:同步数据,压缩数据和上传数据

val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
						.beginWith(sync)
						.then(compress)
						.then(upload)
						.enqueue()

beginWith()方法开创一个链式任务,后面只需使用then()连接即可。必须前一个后台任务运行成功之后,下一个后台任务才会运行
WorkManager千万别依赖它实现核心功能,它在国产手机上会非常不稳定

Kotlin课堂:DSL构建语法结构

1.使用DSL可以实现类似的语法结构

class Dependency{
    val libraries = ArrayList<String>()

    fun implementtion(lib : String){
        libraries.add(lib)
    }
}

fun dependencies(block : Dependency.() ->Unit) : List<String>{
        val dependency = Dependency()
        dependency.block()
       return dependency.libraries
}
fun main(){
    val libraries = dependencies {
        implementtion("com.squareup.retrofit2:retrofit:2.6.1")
        implementtion("com.squareup.retrofit2:converter-gson:2.6.1")
    }
    for (lib in libraries){
        println(lib)
    }
}

在这里插入图片描述
2.


class Td{
    var content = ""
    fun html()="\n\t\t<td>$content</td>"
}

class Tr{
    private val children = ArrayList<Td>()

    fun td(block: Td.() -> String){
        val td = Td()
        td.content = td.block()
        children.add(td)
    }

    fun html() : String{
        val builder = StringBuilder()
        builder.append("\n\t<tr>")
        for (childTag in children){
            builder.append(childTag.html())
        }
        builder.append("\n\t</tr>")
        return builder.toString()
    }
}

class Table {
    private val children = ArrayList<Tr>()

    fun tr(block: Tr.() -> Unit){
        val tr =Tr()
        tr.block()
        children.add(tr)
    }

    fun html() : String{
        val builder = StringBuilder()
        builder.append("<table>")
        for (childTag in children){
            builder.append(childTag.html())
        }
        builder.append("\n</table>")
        return builder.toString()
    }
}

fun main(){
    val table =Table()
    table.tr {
        td { "apple" }
        td { "banana" }
        td { "pear" }
    }
    table.tr {
        td { "apple" }
        td { "banana" }
        td { "pear" }
    }
    print(table.html())
}

在这里插入图片描述
另外DSL中可以使用Kotlin的其他语法特性

fun main(){
	val html = table{
	repeat(2){
		tr{
			val fruits = listOf("apple","grape","orange")
			for(fruit in fruits){
				td{fruit}
			}
		}
	}
}
pritln(html)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一天发火两次

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值