2-2.Jetpack 之 Room 简单编码模板(优化版)(Entity、DAO、Database、Repository)

一、Room

1、Room 概述
  • Room 是 Jetpack 中的一个重要成员,它是一个持久化库,它为管理数据库提供了简单强大的方法
2、Room 引入
  • 在模块级 build.gradle 中引入相关依赖
implementation "androidx.room:room-runtime:2.2.5"
annotationProcessor "androidx.room:room-compiler:2.2.5"

二、Room 简单案例

1、Application
  • MyApplication.java
package com.my.room.application;

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    public static final String TAG = MyApplication.class.getSimpleName();

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();

        context = this;
    }

    public static Context getContext() {
        return context;
    }
}
2、Entity
  • Staff.java
package com.my.room.entity;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "Staff")
public class Staff {

    @PrimaryKey(autoGenerate = true)
    public Long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "age")
    public Integer age;

    @ColumnInfo(name = "cardNum")
    public String cardNum;

    @Override
    public String toString() {
        return "Staff{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", cardNum='" + cardNum + '\'' +
                '}';
    }
}
3、DAO
  • StaffDAO.java
package com.my.room.dao;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import com.my.room.entity.Staff;

import java.util.List;

@Dao
public interface StaffDAO {

    @Insert
    void insert(Staff staff);

    @Delete
    void delete(Staff staff);

    @Update
    void update(Staff staff);

    @Query("SELECT * FROM Staff")
    List<Staff> queryAll();
}
4、Database
  • MyDatabase.java
package com.my.room.database;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

import com.my.room.application.MyApplication;
import com.my.room.dao.StaffDAO;
import com.my.room.entity.Staff;

@Database(entities = {Staff.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {

    public static final String TAG = MyDatabase.class.getSimpleName();

    public static final String DATABASE_NAME = "test.db";

    private static MyDatabase myDatabase;

    public static MyDatabase getInstance() {
        if (myDatabase == null) {
            myDatabase = Room.databaseBuilder(MyApplication.getContext(), MyDatabase.class, DATABASE_NAME)
                    .build();
        }
        return myDatabase;
    }

    public abstract StaffDAO getStaffDAO();
}
5、Repository Observer
  1. InsertObserver.java
package com.my.room.repository.observer;

public interface InsertObserver {
    void onSuccess();
    void onError(String msg);
}
  1. DeleteObserver.java
package com.my.room.repository.observer;

public interface DeleteObserver {
    void onSuccess();
    void onError(String msg);
}
  1. UpdateObserver.java
package com.my.room.repository.observer;

public interface UpdateObserver {
    void onSuccess();
    void onError(String msg);
}
  1. QueryAllObserver.java
package com.my.room.repository.observer;

import java.util.List;

public interface QueryAllObserver<T> {
    void onResult(List<T> ts);
}
6、Repository
  1. RepositoryItem.java
package com.my.room.repository;

public class RepositoryItem<T> {
    public static final int CODE_SUCCESS = 1;
    public static final int CODE_ERROR = 2;

    private int code;
    private String msg;
    private T data;

    public RepositoryItem(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "RepositoryItem{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
  1. StaffRepository.java
package com.my.room.repository;

import android.os.AsyncTask;

import com.my.room.dao.StaffDAO;
import com.my.room.database.MyDatabase;
import com.my.room.entity.Staff;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.QueryAllObserver;
import com.my.room.repository.observer.UpdateObserver;

import java.util.List;

public class StaffRepository {
    private MyDatabase myDatabase;
    private StaffDAO staffDAO;

    public StaffRepository() {
        myDatabase = MyDatabase.getInstance();
        staffDAO = myDatabase.getStaffDAO();
    }

    // ====================================================================================================

    public void insert(Staff staff, InsertObserver insertObserver) {
        new InsertAsyncTask(staff, insertObserver).execute();
    }

    public void delete(Staff staff, DeleteObserver deleteObserver) {
        new DeleteAsyncTask(staff, deleteObserver).execute();
    }

    public void update(Staff staff, UpdateObserver updateObserver) {
        new UpdateAsyncTask(staff, updateObserver).execute();
    }

    public void queryAll(QueryAllObserver<Staff> queryAllObserver) {
        new QueryAllAsyncTask(queryAllObserver).execute();
    }

    // ====================================================================================================

    private class InsertAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private InsertObserver insertObserver;

        public InsertAsyncTask(Staff staff, InsertObserver insertObserver) {
            this.staff = staff;
            this.insertObserver = insertObserver;
        }

        @Override
        protected RepositoryItem doInBackground(Void... voids) {
            try {
                staffDAO.insert(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                insertObserver.onSuccess();
            } else {
                insertObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class DeleteAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private DeleteObserver deleteObserver;

        public DeleteAsyncTask(Staff staff, DeleteObserver deleteObserver) {
            this.staff = staff;
            this.deleteObserver = deleteObserver;
        }

        @Override
        protected RepositoryItem<Object> doInBackground(Void... voids) {
            try {
                staffDAO.delete(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                deleteObserver.onSuccess();
            } else {
                deleteObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class UpdateAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private UpdateObserver updateObserver;

        public UpdateAsyncTask(Staff staff, UpdateObserver updateObserver) {
            this.staff = staff;
            this.updateObserver = updateObserver;
        }

        @Override
        protected RepositoryItem<Object> doInBackground(Void... voids) {
            try {
                staffDAO.update(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                updateObserver.onSuccess();
            } else {
                updateObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class QueryAllAsyncTask extends AsyncTask<Void, Void, RepositoryItem<List<Staff>>> {

        private QueryAllObserver<Staff> queryAllObserver;


        public QueryAllAsyncTask(QueryAllObserver<Staff> queryAllObserver) {
            this.queryAllObserver = queryAllObserver;
        }

        @Override
        protected RepositoryItem<List<Staff>> doInBackground(Void... voids) {
            try {
                List<Staff> staffs = staffDAO.queryAll();
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, staffs);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<List<Staff>> repositoryItem) {
            super.onPostExecute(repositoryItem);

            queryAllObserver.onResult(repositoryItem.getData());
        }
    }
}
7、Activity Layout
  • activity_staff.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".StaffActivity">

    <Button
        android:id="@+id/btn_insert"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_insert" />

    <Button
        android:id="@+id/btn_update"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_delete" />

    <Button
        android:id="@+id/btn_query_all"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_update" />
</androidx.constraintlayout.widget.ConstraintLayout>
8、Activity Code
  • StaffActivity.java
package com.my.room;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.Button;

import com.my.room.entity.Staff;
import com.my.room.repository.StaffRepository;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.UpdateObserver;

public class StaffActivity extends AppCompatActivity {

    public static final String TAG = StaffActivity.class.getSimpleName();

    private StaffRepository staffRepository;

    private Button btnInsert;
    private Button btnDelete;
    private Button btnUpdate;
    private Button btnQueryAll;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_staff);

        staffRepository = new StaffRepository();

        btnInsert = findViewById(R.id.btn_insert);
        btnDelete = findViewById(R.id.btn_delete);
        btnUpdate = findViewById(R.id.btn_update);
        btnQueryAll = findViewById(R.id.btn_query_all);

        btnInsert.setOnClickListener(v -> {
            Staff staff = new Staff();
            staff.name = "jack";
            staff.age = 20;
            staff.cardNum = "staff-001";

            staffRepository.insert(staff, new InsertObserver() {
                @Override
                public void onSuccess() {
                    Log.i(TAG, "------------------------------ insert success");
                }

                @Override
                public void onError(String msg) {
                    Log.i(TAG, "------------------------------ insert error - " + msg);
                }
            });
        });

        btnDelete.setOnClickListener(v -> {
            Staff staff = new Staff();
            staff.id = 1L;

            staffRepository.delete(staff, new DeleteObserver() {
                @Override
                public void onSuccess() {
                    Log.i(TAG, "------------------------------ delete success");
                }

                @Override
                public void onError(String msg) {
                    Log.i(TAG, "------------------------------ delete error - " + msg);
                }
            });
        });

        btnUpdate.setOnClickListener(v -> {
            Staff staff = new Staff();
            staff.id = 1L;
            staff.name = "jack";
            staff.age = 30;
            staff.cardNum = "staff-002";

            staffRepository.update(staff, new UpdateObserver() {
                @Override
                public void onSuccess() {
                    Log.i(TAG, "------------------------------ update success");
                }

                @Override
                public void onError(String msg) {
                    Log.i(TAG, "------------------------------ update error - " + msg);
                }
            });
        });

        btnQueryAll.setOnClickListener(v -> {
            staffRepository.queryAll(staffs -> {
                if (staffs == null) {
                    Log.i(TAG, "------------------------------ queryAll - staffs is null");
                    return;
                }
                if (staffs.size() == 0) {
                    Log.i(TAG, "------------------------------ queryAll - staffs is empty");
                    return;
                }

                for (Staff staff : staffs)
                    Log.i(TAG, "------------------------------ queryAll - " + staff);
            });
        });
    }
}
Test
  • 增 -> 查 -> 改 -> 查 -> 删 -> 查,输出结果
I/StaffActivity: ------------------------------ insert success
I/StaffActivity: ------------------------------ queryAll - Staff{id=1, name='jack', age=20, cardNum='staff-001'}
I/StaffActivity: ------------------------------ update success
I/StaffActivity: ------------------------------ queryAll - Staff{id=1, name='jack', age=30, cardNum='staff-002'}
I/StaffActivity: ------------------------------ delete success
I/StaffActivity: ------------------------------ queryAll - staffs is empty

三、Room 简单案例解析

1、Entity 解析
@Entity(tableName = "Staff")
public class Staff {

    @PrimaryKey(autoGenerate = true)
    public Long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "age")
    public Integer age;

    @ColumnInfo(name = "cardNum")
    public String cardNum;

    @Override
    public String toString() {
        return "Staff{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", cardNum='" + cardNum + '\'' +
                '}';
    }
}
  • @Entity(tableName = "Staff"):指定 Staff 类为 Room 数据库中 Staff 表对应的实体类
  1. @PrimaryKey(autoGenerate = true):指定 id 字段为主键,并且它的值是自动生成的

  2. @ColumnInfo(name = "name"):指定 name 字段映射到表中的 name 列,@ColumnInfo(name = "age")@ColumnInfo(name = "cardNum") 同理

  • Staff 表会在 APP 首次安装时由 Room 根据 Staff 类创建,SQL 语句如下
CREATE TABLE IF NOT EXISTS Staff (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    age INTEGER,
    cardNum TEXT
);
2、DAO 解析
@Dao
public interface StaffDAO {

    @Insert
    void insert(Staff staff);

    @Delete
    void delete(Staff staff);

    @Update
    void update(Staff staff);

    @Query("SELECT * FROM Staff")
    List<Staff> queryAll();
}
  • @Dao:指定 StaffDAO 接口与 Room 数据库进行交互,以操作 Staff 类,该接口使用 Room 相关注解来定义操作方法
  1. @Insert:插入数据,这里传递一个 Staff 对象作为参数,Room 将这个对象插入到数据库中

  2. @Delete:删除数据,这里传递一个 Staff 对象作为参数,Room 基于该对象的 id 字段来删除相应的行

  3. @Update:修改数据,这里传递一个 Staff 对象作为参数,Room 基于该对象的 id 字段来修改相应的行

  4. @Query("SELECT * FROM Staff"):查询数据,查询表中的所有行

3、Database 解析
@Database(entities = {Staff.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {

    public static final String TAG = MyDatabase.class.getSimpleName();

    public static final String DATABASE_NAME = "test.db";

    private static MyDatabase myDatabase;

    public static MyDatabase getInstance() {
        if (myDatabase == null) {
            myDatabase = Room.databaseBuilder(MyApplication.getContext(), MyDatabase.class, DATABASE_NAME)
                    .build();
        }
        return myDatabase;
    }

    public abstract StaffDAO getStaffDAO();
}
(1)注解解析
  • @Database:指定 MyDatabase 抽象类为 Room 数据库
  1. entities = {Staff.class}:指定这个数据库包含 Staff 类

  2. version = 1:指定数据库的版本号,当实体或数据库结构发生变化时,需要增加这个版本号

  3. exportSchema = false:指定不导出数据库的 schema

(2)思路解析
  1. 单例模式确保整个应用程序中只有一个 MyDatabase 实例对象

  2. 通过 Room.databaseBuilder 方法构建数据库,该方法需要三个参数,分别为上下文、数据库类、数据库名称

  3. 定义获取 StaffDao 实例对象的抽象方法,Room 将自动生成这个方法的实现

4、Repository 解析
package com.my.room.repository;

import android.os.AsyncTask;

import com.my.room.dao.StaffDAO;
import com.my.room.database.MyDatabase;
import com.my.room.entity.Staff;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.QueryAllObserver;
import com.my.room.repository.observer.UpdateObserver;

import java.util.List;

public class StaffRepository {
    private MyDatabase myDatabase;
    private StaffDAO staffDAO;

    public StaffRepository() {
        myDatabase = MyDatabase.getInstance();
        staffDAO = myDatabase.getStaffDAO();
    }

    // ====================================================================================================

    public void insert(Staff staff, InsertObserver insertObserver) {
        new InsertAsyncTask(staff, insertObserver).execute();
    }

    public void delete(Staff staff, DeleteObserver deleteObserver) {
        new DeleteAsyncTask(staff, deleteObserver).execute();
    }

    public void update(Staff staff, UpdateObserver updateObserver) {
        new UpdateAsyncTask(staff, updateObserver).execute();
    }

    public void queryAll(QueryAllObserver<Staff> queryAllObserver) {
        new QueryAllAsyncTask(queryAllObserver).execute();
    }

    // ====================================================================================================

    private class InsertAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private InsertObserver insertObserver;

        public InsertAsyncTask(Staff staff, InsertObserver insertObserver) {
            this.staff = staff;
            this.insertObserver = insertObserver;
        }

        @Override
        protected RepositoryItem doInBackground(Void... voids) {
            try {
                staffDAO.insert(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                insertObserver.onSuccess();
            } else {
                insertObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class DeleteAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private DeleteObserver deleteObserver;

        public DeleteAsyncTask(Staff staff, DeleteObserver deleteObserver) {
            this.staff = staff;
            this.deleteObserver = deleteObserver;
        }

        @Override
        protected RepositoryItem<Object> doInBackground(Void... voids) {
            try {
                staffDAO.delete(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                deleteObserver.onSuccess();
            } else {
                deleteObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class UpdateAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {

        private Staff staff;
        private UpdateObserver updateObserver;

        public UpdateAsyncTask(Staff staff, UpdateObserver updateObserver) {
            this.staff = staff;
            this.updateObserver = updateObserver;
        }

        @Override
        protected RepositoryItem<Object> doInBackground(Void... voids) {
            try {
                staffDAO.update(staff);
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
            super.onPostExecute(repositoryItem);

            if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
                updateObserver.onSuccess();
            } else {
                updateObserver.onError(repositoryItem.getMsg());
            }
        }
    }

    private class QueryAllAsyncTask extends AsyncTask<Void, Void, RepositoryItem<List<Staff>>> {

        private QueryAllObserver<Staff> queryAllObserver;


        public QueryAllAsyncTask(QueryAllObserver<Staff> queryAllObserver) {
            this.queryAllObserver = queryAllObserver;
        }

        @Override
        protected RepositoryItem<List<Staff>> doInBackground(Void... voids) {
            try {
                List<Staff> staffs = staffDAO.queryAll();
                return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, staffs);
            } catch (Exception e) {
                e.printStackTrace();
                return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
            }
        }

        @Override
        protected void onPostExecute(RepositoryItem<List<Staff>> repositoryItem) {
            super.onPostExecute(repositoryItem);

            queryAllObserver.onResult(repositoryItem.getData());
        }
    }
}
  • StaffRepository 类是对 StaffDAO 的封装,封装的主要目的是异步任务处理
(1)异步任务处理
  • 对于每种 StaffDAO 实例对象的操作都定义了一个继承 AsyncTask 的内部类,它用于安排操作在后台线程中执行,然后使用观察者模式来通知调用者
  1. InsertAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 InsertObserver 通知调用者

  2. DeleteAsyncTask:负责在后台线程中执行删除操作,完成后(无论成功还是失败)通过 DeleteObserver 通知调用者

  3. UpdateAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 UpdateObserver 通知调用者

  4. QueryAllAsyncTask:负责在后台线程中执行查询操作,完成后(无论成功还是失败)通过 QueryAllObserver 通知调用者

(2)补充
  1. 线程调度:数据库操作不能在 UI 线程执行,操作在后台线程中(doInBackground 方法)执行完成后,然后回到 UI 线程中(onPostExecute 方法)执行后续操作

  2. 异常处理:在异步任务种,捕获可能抛出的异常,例如,执行插入操作时主键冲突会抛出异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值