再次理解Android账号管理体系

使用AccountManager和AbstractAccountAuthenticator建立账户系统 使用AccountManager和AbstractAccountAuthenticator建立账户系统使用AccountManager和AbstractAccountAuthenticator建立账户系统 为什么要使用AccountManager和AccountAuthenticatr AccountManager和Authenticator之间的关系 使用AccountManager管理账号 创建 阅读详情

目录

✅ 0. 需求

📂 1. 前言

🔱 2. 使用

2.1 账户体系前提

2.2 创建账户服务

2.3 操作账户-增删改查

💠 3. 源码流程


✅ 0. 需求

        试想,自己去实现一个账号管理体系,该如何做呢?

        ——————————

        最近有遇到这样一个需求:AR眼镜只支持同时与一个手机绑定,即:AR眼镜已与手机绑定过的话,不再支持与其他手机绑定;如要绑定其他手机,需要先解绑当前手机才能重新绑定。

        理解一下需求,即是:眼镜端需要账号管理体系,主要用来存储已绑定过手机的token。

        AR眼镜绑定手机时序图,如下所示:


📂 1. 前言

        我们知道,Android的账号管理体系是用来管理用户在Android设备上的身份验证和授权的系统,包括了对账号的创建、授权、修改和删除等操作的管理。

        那么,我们为什么要使用Android的账号管理体系?

        ——————————

        尽管,我们可以自己使用SP、MMKV、文件或数据库等方式来存储、更新、删除账户、密码或AuthToken;但其实涉及到跨进程通信,实现起来其实是稍显麻烦的;并且对于数据安全,信息加密这块的可靠性也有待商榷。

        另外,从本文第一张图可见,我们在架构设计中规划的账号体系服务,是寄生于Android启动时system_server开启的服务,然后通过binder方式,提供给其他进程使用。

        然而,在技术预研时发现,强大的Android早已想到了这点,在Android 2.0开始就加入了新包android.accounts,为我们准备好了这样一个服务ACCOUNT_SERVICE。

        而且,该包功能已十分强大,我们可以直接拿来使用,功能主要包括:

  1. 集中式的账户管理API,可以安全地存储和访问认证的令牌和密码;
  2. 可以在同一个设备中管理同一应用的多个不同账号,能够自动批量的同步服务器更新账户,甚至可以和不同服务器进行数据同步和安全认证;
  3. 把账户的验证过程、AuthToken的获取过程分离出来,降低程序的耦合性;
  4. 并且会在”设置”应用中添加一个账户入口;
  5. 方便应用间账号共享。

🔱 2. 使用

2.1 账户体系前提

        1)账号体系共享前提:使用相同的签名。

        2)权限申明:

<uses-permission android:name="android.permission.GET_ACCOUNTS" />

2.2 创建账户服务

        步骤一:定义一个action为android.accounts.AccountAuthenticator的Intent的Service,并在meta-data的resource属性指定该Account基本显示信息的xml文件authenticator,模版代码如下:

<service
    android:name=".portal.feature.account.service.AccountService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator" />
    </intent-filter>
    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator" />
</service>

        步骤二:在res下的xml文件夹中新建authenticator.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.agg.account"
    android:icon="@drawable/ic_test"
    android:label="AggAccount"
    android:smallIcon="@drawable/ic_test" />

        注:android:accountType表示的是Account类型,它必须是唯一的,一般是包名。

        步骤三:创建继承自AbstractAccountAuthenticator的Authenticator文件,如下:

class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {

    override fun editProperties(
        response: AccountAuthenticatorResponse?, accountType: String?
    ): Bundle? {
        return null
    }

    override fun addAccount(
        response: AccountAuthenticatorResponse?,
        accountType: String?,
        authTokenType: String?,
        requiredFeatures: Array<out String>?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun getAuthToken(
        response: AccountAuthenticatorResponse?,
        account: Account,
        authTokenType: String?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun confirmCredentials(
        response: AccountAuthenticatorResponse?, account: Account?, options: Bundle?
    ): Bundle? {
        return null
    }

    override fun getAuthTokenLabel(authTokenType: String?): String {
        return ""
    }

    override fun updateCredentials(
        response: AccountAuthenticatorResponse?,
        account: Account?,
        authTokenType: String?,
        options: Bundle?
    ): Bundle? {
        return null
    }

    override fun hasFeatures(
        response: AccountAuthenticatorResponse?, account: Account?, features: Array<out String>?
    ): Bundle? {
        return null
    }

}

        步骤四:创建帐户Service,并在Service的onBind中调AbstractAccountAuthenticator的getIBinder()返回其用于远程调用的IBinder,如下所示:

class AccountService : Service() {

    private var authenticator: Authenticator? = null

    override fun onCreate() {
        super.onCreate()
        authenticator = Authenticator(this)
    }

    override fun onBind(intent: Intent?): IBinder? {
        return authenticator?.iBinder
    }

}

        注:运行起来程序后,在“设置”应用-“帐户”-“添加帐户”列表中就已可以发现自己的app了。

2.3 操作账户-增删改查

        1)获取所有账号

    /**
     * 获取所有账号
     */
    fun getAllAccount(context: Context) {
        val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
        Log.e(TAG, "getAllAccount: size = ${accountManager.accounts.size}")
        for (account in accountManager.accounts) {
            Log.e(TAG, "getAllAccount: $account")
        }
    }

         2)添加账号

    /**
     * 添加账号
     */
    fun addAccount(
        context: Context, account: Account, password: String, authToken: String
    ) {
        val bundle = Bundle()
        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)
        bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken)
        bundle.putString(AccountManager.KEY_PASSWORD, password)
        AccountManager.get(context).addAccountExplicitly(account, password, bundle)
    }

         3)更新某个账号

    /**
     * 更新某个账号
     */
    fun updateAccount(
        context: Context, account: Account, password: String = "", authToken: String = ""
    ) {
        var updatePassword = password
        var updateAuthToken = authToken
        AccountManager.get(context).apply {
            if (updatePassword.isEmpty()) {
                updatePassword = getUserData(account, AccountManager.KEY_PASSWORD)
            }
            if (updateAuthToken.isEmpty()) {
                updateAuthToken = getUserData(account, AccountManager.KEY_AUTHTOKEN)
            }
            removeAccountExplicitly(account)
        }
        addAccount(context, account, updatePassword, updateAuthToken)
    }

         4)删除某个账号

    /**
     * 删除某个账号
     */
    fun delAccount(context: Context, account: Account) {
        val isRemoveSuccess = AccountManager.get(context).removeAccountExplicitly(account)
        Log.e(TAG, "delAllAccount: isRemoveSuccess = $isRemoveSuccess, $account")
    }

          5)删除所有账号

    /**
     * 删除所有账号
     */
    fun delAllAccount(context: Context) {
        val accountManager = AccountManager.get(context)
        for (account in accountManager.accounts) {
            val isRemoveSuccess = accountManager.removeAccountExplicitly(account)
            Log.e(TAG, "delAllAccount: isRemoveSuccess = $isRemoveSuccess, $account")
        }
    }

💠 3. 源码流程

  • 从系统启动system_server进程,进而启动ACCOUNT_SERVICE服务开始;
  • AccountManager是一个面向应用程序开发的组件,它提供了一套对应于IAccountManager协议的应用程序接口;
  • 这组接口通过Binder机制与系统服务AccountManagerService进行通信,协作完成帐号相关的操作;
  • 同时AccountManager接收authenticators提供的回调,以便在帐号操作完成之后向调用此帐号服务的业务返回对应的接口,同时触发这个业务对结果的处理。

Android 8.0 - AccountManager之行为变更 这周有同事反馈android 8.0上获取不到帐号,第一反应就是去看源码了,没想起来先看下官方文档哈哈。。。由于自己的理解误区,还是走了点弯路,但幸运的是最终搞清了获取帐号的整个流程 接下来说的是获取帐号的权限变更: //获取帐号列表/或对应帐号类型的某个帐号 AccountManager#getAccounts()/getAccountsByType(String accountType... 阅读详情

相关推荐

Android AccountManager服务架构与安全机制解析

Android系统服务中的AccountManager是账户管理的核心组件,采用Binder IPC机制实现跨进程通信。其架构设计包含客户端API层、服务代理层和服务实现层,通过SQLite数据库持久化存储账户数据。在安全机制方面,AccountManager实施三级权限控制模型,并采用AndroidKeyStore进行敏感数据加密。该服务广泛应用于OAuth认证、数据同步等场景,支持通过AbstractAccountAuthenticator实现自定义认证流程。随着Android版本演进,AccountM

weixin_29311017的博客 232

Android AccountManager 账户同步管理简单介绍

Android 界面–》设置Settings–》账号–》添加账号,发现里面没有可以同步的应用列表,咋回事?百度了一波,刚开始看有点晕,后面多看一点基本理解了。先揭晓答案,是下面这里代码,返回的authTypes数组长度为0;为啥返回0 ,或者如何让他不返回0 ,后面介绍。可以看下面两个文章是介绍AccountManager相关知识的:AccountManager帐号管理器,集中管理apps注册的不同类型的帐号。...

wenzhi的博客 3862

安卓《我的世界》基岩版启动器LeviLauncher:多版本管理与模块加载指南

在安卓应用生态中,动态加载与沙盒隔离技术是实现应用多版本共存与功能扩展的核心原理。通过动态解析APK文件、创建独立运行环境,开发者能够在不影响系统安装状态的前提下,实现应用的免安装启动与模块化加载。这一技术不仅提升了应用管理的灵活性,也为游戏性能优化、功能测试等场景提供了工程实践方案。在《我的世界》基岩版中,玩家常面临版本管理僵化与扩展能力受限的痛点。LeviLauncher作为一款开源启动器,巧妙运用了动态加载与沙盒隔离技术,支持多版本隔离运行、原生SO模块加载与多账号管理,为安卓平台的正版玩家提供了更高

weixin_33978044的博客 451

android AccountManager是什么

需要注意的是,直接存储和检索密码可能不是最好的做法,通常更推荐的做法是存储授权令牌或者使用更安全的授权和身份验证流程,例如OAuth 2.0。这可能需要一些时间来理解和实现,但它提供了一个非常强大和安全的方式来管理用户账户信息。它会将账户信息存储在一个安全的、受系统保护的区域中,以降低信息泄露的风险。是 Android 系统提供的一个集中式账户管理服务,它允许应用程序存储和检索账户信息和凭据。你可以为你的应用程序定义自己的账户类型,这样就可以在系统的账户设置中看到你的应用程序的账户。

m0_57236802的博客 997

【产品经理修炼之道】-账号体系的变迁及设计

回顾互联网历史,会发现账号体系距离2000年已经大不相同,迭代的过程既有软件的发展,也有硬件的升级。本文作者对账号体系的变迁和设计思路进行了分析,一起来看一下吧。

xiaoli8748的专栏 1262

Android开发之AccountManager账号管理实战详解

(该小节内容将在完整文档中展开,包含 AccountManagerDemo 项目的模块结构、源码分析与设计原则总结。本章通过和的使用方式、调用流程、安全机制,构建了一个完整的账户退出与令牌注销流程。同时结合监听机制与账户更新,实现了对用户状态的实时同步与本地数据的清理。下一章将结合完整项目示例,进一步说明 AccountManager 的实际应用与设计优化。本文还有配套的精品资源,点击获取简介:在Android开发中,

weixin_42351520的博客 1067

社交平台账号管理能力评估:AI合成数据驱动的治理水位测试

社交平台账号管理能力是内容安全与风险治理的核心基础,其本质涵盖感知灵敏度、决策精准度、执行一致性及反馈有效性四大维度。该能力依赖于多模态融合、上下文理解与实时计算等关键技术支撑,直接决定平台对水军、黑灰产、伪科普等新型风险的拦截效能。近年来,行业普遍面临‘宣称能力’与‘真实水位’脱节的困境,传统渗透测试与真实数据受限于隐私合规与样本稀疏性,难以支撑系统性评估。AI合成账号由此成为关键突破口——通过时序建模、语义约束与对抗鲁棒性设计,生成行为真实、规则边缘、可归因的测试样本,实现对平台风控闭环的第三方压力验证

483

掌握安卓微信多开技术与应用

为了实现微信多开功能,首先需要了解Android平台的多用户环境。Android系统支持多用户模式,这使得用户可以在一个设备上拥有多个独立的用户空间。每个用户空间都有自己的应用数据和账户信息,这样就可以在同一个设备上登录多个微信账号而互不干扰。消息推送策略是影响用户体验的重要因素之一。多开工具允许用户根据自身需求设置不同的推送规则。例如,用户可以选择仅在特定时间接收消息推送,或者对消息来源进行筛选,只允许来自重要联系人的消息被及时推送。用户自定义推送策略通常包括以下几个方面:时间设置。

weixin_35414484的博客 1947

网易云信 android,Android 网易云信集成(一)

网易云信流程与账号管理关于网易云信的注册还有应用管理就不说了,先说下网易云信的通信流程,只有清楚流程,你才能知道自己要做什么。先是你的服务器端拿着accid还有其他参数发给网易云信,生成一个token,移动端用户登录后,后端返回给用户token,移动端拿着accid跟token进行登录网易云信,与网易云信做后面其他一系列操作。 通过上面的流程你会不会感觉,你作为移动端想自己登录网易云只能先让后天生...

weixin_28862113的博客 666

Tesseract OCR 字库训练教程:提高识别率进阶版

通过 Tesseract 字库训练,你可以提高 OCR 对特定文本或字体的识别能力。训练过程包括准备训练数据、生成标签文件、运行训练工具,以及将训练结果集成到实际项目中。虽然训练过程需要一些时间和精力,但对于需要高精度文本识别的场景来说,字库训练是非常有效的提升识别率的方法。

小蜗牛的珍贵百宝箱 1868

CiteSpace基础操作(CNKI数据的导入分析)(一)

1 Cite Space简单介绍 2 文献导入 2.1 打开知网,选择高级检索

m0_50990607的博客 5万+

VirtualApp安卓沙盒技术:如何快速实现应用多开与安全隔离的终极指南

你是否曾想过在一部手机上同时登录多个微信账号?或者希望将工作和个人应用完全隔离?VirtualApp正是解决这些问题的安卓沙盒技术神器。作为一款轻量级的"Android虚拟机",VirtualApp让你能够在同一设备上运行多个应用实例,实现应用多开、移动办公安全、数据隔离等多种应用场景。 ## 🎯 项目价值定位:为什么VirtualApp如此重要? VirtualApp不是普通的应用多开工具

gitblog_00306的博客 312

大模型——AI对话四象限

{ "摘要": "文章揭示了90%的人不会有效与AI沟通的问题,指出简单粗暴的提问只能得到标准答案。作者提出'AI对话四象限'模型:1)双方都懂时提效;2)AI懂但需学会分层提问;3)共同探索未知领域;4)向AI传授独家知识。强调沟通技巧的重要性,认为会提问的人不会被AI替代。最后呼吁读者尝试四象限思维,与AI碰撞火花。" }

312

iOS原生自动化治理:游戏账号管理与行为过滤实战

iOS自动化并非仅指快捷指令或脚本,而是基于系统公开API构建的可落地管控能力。其核心在于理解沙盒边界、合理调用Game Center、Core Motion、ProcessInfo等原生框架,在不越狱、不依赖企业证书前提下实现账号生命周期编排与实时行为干预。技术价值体现在对教育终端、企业BYOD及家庭数字监护场景的合规适配——既能精准提取游戏登录态特征(如Keychain凭证、加密缓存文件),又能通过传感器数据建模动态调控进程资源。本文聚焦iOS原生自动化能力与游戏数据特征提取两大关键热词,详解如何在真实

weixin_33924770的博客 295

【WebRTC】【Unity】Unity Web RTC1-Unity中简单实现远程画面

平台:基本常用平台包括安卓,苹果(max和IOS),Windows64,Linux都行。10. 注意,如果从移动设备浏览器连接服务,信令服务器的SSL证书一定要先设好。想要在Unity中实现实时远程桌面,找到了Render Streaming这个手段,本篇介绍相应的使用方法和实例。8. 打开电脑浏览器,地址栏输入http://127.0.0.1,点击Receiver Sample。新建Unity项目,模板选Third Person,Project Name随意。启动App,控制台出现如下信息表示成功。

每日出拳老爷子的博客 2518

槽型光电开关传感器原理图设计,已量产(光电传感器)

在嵌入式设备的位置限位、物料计数等场景中,槽型光电开关是性价比极高的传感器方案 —— 它通过遮挡发射器与接收器之间的光路实现电平切换,电路简单且抗干扰性优于漫反射式光耦。今天分享一个实际项目中落地的槽型光电开关检测电路,从器件选型到逻辑设计都做了简化适配,适合小批量原型或低成本产品的需求。

不脱发的程序猿 719

OpenClip 扩展进阶技巧:正则门控、原生内容检测与结果投递契约完整实战指南

OpenClip 是一款开源的 macOS 文本动作弹出工具,让你在任意应用中选中文本后运行自定义脚本、AI 动作与可扩展工作流。本文带你掌握 OpenClip 扩展的三大进阶机制:**正则门控**、**原生内容检测**与**结果投递契约**,让你的扩展动作只在合适的时机出现,并把结果投递到最恰当的地方。 [![OpenClip 扩展商店界面](https://raw.gitcode.com/g

gitblog_00477的博客 466

AutoSAR从0开始实战 --- 目录

​​​​​​本专栏是全网最真实的AutoSAR实战专栏,博主将从一个0基础小白的身份来讲解AutoSAR工具的配置,以及相应代码的讲解。

wj博客 1729

酷鸟云是什么?一文看懂云手机与安卓虚拟化的落地应用

云计算正在重塑端侧设备的使用方式,虚拟化技术让操作系统不再局限于本地硬件。安卓虚拟化作为其中的关键分支,将完整的Android系统运行于云端服务器,通过视频流与指令回传实现远程操控。这种架构不仅降低了对终端性能的依赖,还为应用多开、7×24小时在线托管、跨设备无缝接入等场景提供了新的解决思路。理解其背后的容器虚拟化、屏幕串流与动态调度机制,有助于评估云手机服务的实际价值。酷鸟云正是这一技术路线下的典型产品,它通过浏览器即可接入云端安卓实例,让用户以任意低配设备获得流畅的交互体验。本文从技术原理到实测场景,系

weixin_31240035的博客 95

Pytorch入门实战(2)-使用BP神经网络实现MNIST手写数字识别

上一篇:Pytorch入门实战(1) - 实现线性回归 涉及知识点 Pytorch nn.Module的基本使用 Pytorch nn.Linear的基本用法 PytorchVision Transforms的基本使用 Pytorch中DataLoader的基本用法 Pytorch详解NLLLoss和CrossEntropyLoss 如何确定神经网络的层数和隐藏层神经元数量 本文内容 本文将会使用BP神经网络(就是最普通的神经网络)实现一个MNIST手写数据集的实现。话不多说,直接开始。 首先先导入需要的包

iioSnail的博客 6617

YOLOv11涨点改进 | Conv篇/小目标改进 | YOLOv11引入SPDConv卷积特征无损下采样模块,增加YOLOv11小目标P2检测层(助力小目标任务高效涨点)

本文提出了一种创新的SPDConv模块来优化YOLOv11模型,通过空间到深度(SPD)层和非步幅卷积层实现特征无损下采样。该方法有效解决了传统CNN中因跨步卷积和池化层导致的细粒度信息丢失问题,特别提升了小目标检测性能。实验表明,SPDConv在目标检测和图像分类任务中显著优于现有SOTA模型。文章详细介绍了模块原理、核心代码实现,并提供了YOLOv11集成SPDConv的具体方法,包括配置文件修改和P2层增强等优化技巧。该技术为计算机视觉任务提供了一种高效的特征保留方案。

Ai缝合怪、助力小伙伴高效发论文 586

易语言大漠多线程中控系统(PC端+安卓模拟器双平台支持)|一键填入注册码即用

开源易语言大漠多线程中控框架,兼容Windows PC与安卓模拟器双环境,无需复杂配置,输入大漠注册码即可快速启用。

haoziyuan168的博客 158

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

安卓 fragment内显示网站_如何开发一款以太坊安卓钱包系列3 - 资产信息展示

这是如何开发以太坊(安卓)钱包系列第3篇, 钱包账号资产信息展示,展示信息主要包括账号地址、eth余额及该账号所拥有的Token及余额。预备知识 MVVM本文会涉及和UI界面的交互,提前理解下界面和数据如何交互是非常有必要的,如果你已经很熟悉MVVM,可跳过这一小节。最早写Android的时候,数据和界面经常耦合在一起,一个Activity文件总是特别大,每当产品界面改版就非常痛苦,吐槽下,很多产...

weixin_39739395的博客 91

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

android中文api:Manifest.permission group

Manifest.permission group: 英文原文:http://developer.android.com/reference/android/Manifest.permission_group.html 版本:Android 4.0 r1 译者署名:Zhao Xing 译者链接:http://blog.youkuaiyun.com/hn307165411 Manife...

hyshucom 245

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

android权限大全

目录 1.存储 2.网络 3.蓝牙 4.位置    5.短信 6.电话 7.联系人 8.时间 9.日历 10.闹铃 11.壁纸 12.浏览器 13.广播 14.截图 15.动画&显示 16.同步 17.音频&视频 18.声音 19.设置 20.快捷方式 21.状态栏   22.系统安全 23.闪关灯 24.相机 25.振动设备 26.红外 27.NFC 28.电源 29.应用(app) 30.活动 31.系统服...

oDouZi的专栏 2047

苹果还是安卓?跨平台开发与技术选型实战指南

移动端开发中,iOS与Android的系统差异、开发语言与分发机制常让团队陷入选型困境。理解双端底层设计哲学——封闭生态与开放生态的权衡,是做出理性决策的前提。跨平台开发方案如Flutter、React Native应运而生,通过一套代码覆盖两端,平衡开发效率与用户体验。技术选型需结合实际团队规模、业务复杂度及性能要求,在原生与跨平台间找到最优解。本文从双端差异梳理到Flutter实战案例,为“苹果还是安卓”的争议提供了工程化落地路径。

weixin_33894640的博客 350
上一篇: 优雅实现垂直SeekBar:不继承Seekbar、不自定义View
下一篇: 当ContentProvider跨进程遇上Room数据库
Swuagg
Swuagg 新星创作者: 移动开发技术领域 新星创作者: 移动开发技术领域 领域专家: 操作系统技术领域 领域专家: 操作系统技术领域
博客等级 码龄10年 2355粉丝 153原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值