ContentObserver注册和触发流程分析

android内容观察者ContentObserver类 结合android.developer网站看,常用方法在那个网站看 contentObserver——内容观察者,目的是观察特定Uri引起的数据库的变化,方便监听插入数据库中数据的变化 有的项目中数据库数据进行改变后,去到某一个页面需要刷新数据,如果有这个需求使用ContentObserver就比较有用了 如果每次在页面显示都刷新数据库的数据,对于会多次显示的页面效率就很低了 则如果我们希望监测一个数据源的变化情况,如果单独使用一个线程去不断的进行查询,那么效率就太低了,我们可以使用Cont 阅读详情

ContentObserver注册和触发流程

获取ContentResolver

mContext.getContentResolver()

Context是一个抽象类,他的实现类之一是ContextImpl,分析ContextImpl源码可以看到:

//android.app.ContextImpl
public ContentResolver getContentResolver() {
        return mContentResolver;
    }

mContentResolver声明如下:

private final ApplicationContentResolver mContentResolver;

注册阶段

mContext.getContentResolver().registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer)

通过context获取到ContentResolver后,我们知道他是一个ApplicationContentResolver的实例对象,分析ApplicationContentResolver源码,并没有找到registerContentObserver方法,进入他的父类ContentResolver。

registerContentObserver的实现如下:

//android.content.ContentResolver
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
            ContentObserver observer, @UserIdInt int userHandle) {
        try {
            getContentService().registerContentObserver(uri, notifyForDescendents,
                    observer.getContentObserver(), userHandle, mTargetSdkVersion);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

getContentService返回的是ContentService的Binder对象,分析ContentService源码:

//com.android.server.content.ContentService
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
            IContentObserver observer, int userHandle, int targetSdkVersion) {
    
        //权限检查等操作,忽略...

        synchronized (mRootNode) {
            mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
                    uid, pid, userHandle);
            if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
                    " with notifyForDescendants " + notifyForDescendants);
        }
    }

mRootNode是ObserverNode的实例,ObserverNode是ContentService的静态内部类。

ObserverNode内部有ArrayList<ObserverNode>,ArrayList<ObserverEntry>。对于Observer是用树来存储的,可以看作左节点存储的是Children,右节点存储Observer。

举个栗子:content://authority/path1/path2 , authority是path1父节点,path1是path2父节点,即path1左节点存储的path2节点,右节点存储的自己的observer。

//ContentService$ObserverNode
//就是在Observer树上插入节点。用递归实现的(╯°□°)╯︵ ┻━┻
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
                                       boolean notifyForDescendants, Object observersLock,
                                       int uid, int pid, int userHandle) {
            // If this is the leaf node add the observer
            if (index == countUriSegments(uri)) {
                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
                        uid, pid, userHandle, uri));
                return;
            }

            // Look to see if the proper child already exists
            String segment = getUriSegment(uri, index);
            if (segment == null) {
                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
            }
            int N = mChildren.size();
            for (int i = 0; i < N; i++) {
                ObserverNode node = mChildren.get(i);
                if (node.mName.equals(segment)) {
                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
                            observersLock, uid, pid, userHandle);
                    return;
                }
            }

            // No child found, create one
            ObserverNode node = new ObserverNode(segment);
            mChildren.add(node);
            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
                    observersLock, uid, pid, userHandle);
        }

( •̀ ω •́ )y注册过程终于说完了。

数据修改

当在自定义ContentProvider里修改数据库后,需要显示调用ContentResolver.notifyChange以达到通知Observer的目的,否则该URI上注册的Observer将不会收到数据改变的通知。

notifyChange阶段

ContentResolver.notifyChange方法最终会进入ContentService.notifyChange,由ContentService分发change事件。

//com.android.server.content.ContentService$ObserverNode
public void notifyChange(Uri[] uris, IContentObserver observer,
            boolean observerWantsSelfNotifications, int flags, int userId,
            int targetSdkVersion, String callingPackage) {
        	//...忽略亿点点
            //收集所有需要通知的observer
            synchronized (mRootNode) {
                final int segmentCount = ObserverNode.countUriSegments(uri);
                mRootNode.collectObserversLocked(uri, segmentCount, 0, observer,
                        observerWantsSelfNotifications, flags, resolvedUserId, collector);
            }
        }

        final long token = clearCallingIdentity();
        try {
            // 通知所有Observer
            collector.dispatch();
            //...忽略亿点点
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }
//com.android.server.content.ContentService$ObserverNode
//Observer树上查找所有需要通知的Observer,结果存储在collector里。又是递归(╯°□°)╯︵ ┻━┻
public void collectObserversLocked(Uri uri, int segmentCount, int index,
                IContentObserver observer, boolean observerWantsSelfNotifications, int flags,
                int targetUserHandle, ObserverCollector collector) {
            String segment = null;
            if (index >= segmentCount) {
                // This is the leaf node, notify all observers
                if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
                collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
                        flags, targetUserHandle, collector);
            } else if (index < segmentCount){
                segment = getUriSegment(uri, index);
                if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
                        + segment);
                // Notify any observers at this level who are interested in descendants
                collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
                        flags, targetUserHandle, collector);
            }

            int N = mChildren.size();
            for (int i = 0; i < N; i++) {
                ObserverNode node = mChildren.get(i);
                if (segment == null || node.mName.equals(segment)) {
                    // We found the child,
                    node.collectObserversLocked(uri, segmentCount, index + 1, observer,
                            observerWantsSelfNotifications, flags, targetUserHandle, collector);
                    if (segment != null) {
                        break;
                    }
                }
            }
        }

大概写了下ContentObserver注册和触发的时机,中间省略了很多底层的东西,我也不是很懂。

SettingsProvider分析

这里分析下SettingsProvider的源码,分析Settings.Global.putString写入自定义字段的过程。

直接上代码:

//android.provider.Settings.Global
public boolean putStringForUser(ContentResolver cr, String name, String value,
                String tag, boolean makeDefault, final int userHandle,
                boolean overrideableByRestore) {
            try {
                Bundle arg = new Bundle();
                arg.putString(Settings.NameValueTable.VALUE, value);
                arg.putInt(CALL_METHOD_USER_KEY, userHandle);
                if (tag != null) {
                    arg.putString(CALL_METHOD_TAG_KEY, tag);
                }
                if (makeDefault) {
                    arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                }
                if (overrideableByRestore) {
                    arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
                }
                IContentProvider cp = mProviderHolder.getProvider(cr);
                //mCallSetCommand = CALL_METHOD_PUT_GLOBAL
                cp.call(cr.getPackageName(), cr.getAttributionTag(),
                        mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
            } catch (RemoteException e) {
                Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                return false;
            }
            return true;
        }

中间关于binder通信的部分就省略了,我自己也不是很明白。(┬┬﹏┬┬)

//com.android.providers.settings.SettingsProvider
public Bundle call(String method, String name, Bundle args) {
    //取出CALL_METHOD_USER_KEY的值
    final int requestingUserId = getRequestingUserId(args);
	switch (method) {
            //...省略亿点点
        case Settings.CALL_METHOD_PUT_GLOBAL: {
            //取出VALUE的值
                    String value = getSettingValue(args);
            //取出CALL_METHOD_TAG_KEY的值
                    String tag = getSettingTag(args);
            //取出CALL_METHOD_MAKE_DEFAULT_KEY的值
                    final boolean makeDefault = getSettingMakeDefault(args);
            //取出CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY的值
                    final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                    insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
                            overrideableByRestore);
                    break;
                }
            //...省略亿点点
    }
}

跟进到insertGlobalSetting,再进一步到mutateGlobalSetting,在该方法内进入MUTATION_OPERATION_INSERT这个分支。

//...
case MUTATION_OPERATION_INSERT: {
                    return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                            UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                            getCallingPackage(), forceNotify,
                            CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
                }
//...
public boolean insertSettingLocked(int type, int userId, String name, String value,
                String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
                boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
			//...
            final int key = makeKey(type, userId);
            boolean success = false;
            SettingsState settingsState = peekSettingsStateLocked(key);
            if (settingsState != null) {
                //数据插入在这里。这里返回true会触发对应的Observer
                success = settingsState.insertSettingLocked(name, value,
                        tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
            }
			//...
            if (forceNotify || success) {
                //数据改变后调用notify方法通知observer
                notifyForSettingsChange(key, name);
            }
            return success;
        }
public boolean insertSettingLocked(String name, String value, String tag,
            boolean makeDefault, boolean forceNonSystemPackage, String packageName,
            boolean overrideableByRestore) {
        if (TextUtils.isEmpty(name)) {
            return false;
        }

        Setting oldState = mSettings.get(name);
        String oldValue = (oldState != null) ? oldState.value : null;
        String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
        Setting newState;

        if (oldState != null) {
            //值更新操作。天真的我以为Settings.Global.putString只会更新value。真的是naive ಠ_ಠ
            if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage,
                    overrideableByRestore)) {
                return false;
            }
            newState = oldState;
        } 
        //...
        return true;
    }
private boolean update(String value, boolean setDefault, String packageName, String tag,
                boolean forceNonSystemPackage, boolean overrideableByRestore,
                boolean resetToDefault) {
             //...
    		//这几个属性都没有改变的情况下才认为Setting的值没有改变
            // Is something gonna change?
            if (Objects.equals(value, this.value)
                    && Objects.equals(defaultValue, this.defaultValue)
                    && Objects.equals(packageName, this.packageName)
                    && Objects.equals(tag, this.tag)
                    && defaultFromSystem == this.defaultFromSystem
                    && isPreserved == this.isValuePreservedInRestore) {
                return false;
            }
            return true;
        }

不同进程即使对同一个Setting写入相同的值,也会改变Setting,触发Observer。

(❁´◡`❁)菜鸟一个,记录下学习过程。

如果文中有错误,欢迎大家指正。

数据共享与访问 了解ContentProvider、ContentResolver和ContentObserver之间的联系 掌握使用内容提供者对外暴露数据 掌握使用ContentResolver操作其他应用的数据 掌握使用ContentObserver监视并响应数据变化 阅读详情

相关推荐

带你学习Android中的ContentObserver

通过使用,开发者可以有效地监听系统设置的变化,例如屏幕亮度等。这种机制使得应用能够动态响应系统设置的更改,提升用户体验。在实现需要与系统设置交互的应用时,合理使用是非常重要的。

执剑人 1311

Android系统之registerContentObserver(一)

(1)API ContentObserver——内容观察者,目的是观察特定Uri引起的数据库的数据变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri数据发生变化时,便会触发它。 (A)ContentResolver类:注册/取消注册ContentObserver方法 /* 为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。 uri—— 需要观察的Uri(需要在UriMat

Flying snow 1万+

后台进程注册的ContentObserver接收到的回调晚10秒钟

Android后台进程中延迟回调机制是为了系统资源优化而设计。它确保后台应用不会频繁监听数据变化,从而降低系统负载。理解该机制有助于开发者优化应用在后台的数据同步策略,提升应用的系统兼容性和资源管理效率。

执剑人 1601

ContentObserver 内容观察者作用及特点

eg: 1、定义Uri public static Uri KEY_BROWSER_URI = Uri.parse("content://com.android.browser.provider.search_engine/search_engine"); 2、new 一个ContentObserver private ContentObserver mBrowse...

weixin_30611509的博客 499

Android截屏录屏监听实战:兼容性方案与安全边界解析

在移动应用开发中,监听用户屏幕操作行为是一个涉及安全与体验的核心技术点。其原理通常基于系统提供的媒体库监听、投影服务回调等机制,通过感知系统层事件来响应截屏、录屏等动作。这项技术的价值在于,它能够帮助应用在金融交易、版权内容保护等场景下,及时做出安全响应或提供智能交互,例如自动隐藏敏感信息或添加分享水印。从工程实践角度,开发者需要综合考量兼容性、实时性与系统权限限制,尤其在Android不同版本和厂商定制系统下,方案选型差异显著。本文聚焦Android平台,深入探讨了基于ContentObserver的截屏

weixin_33728708的博客 397

告别隐藏入口!Android 11开发者模式里,一键开启状态栏显示秒数(附源码追踪)

本文深入解析Android 11状态栏秒显功能的实现原理与多种开启方法,包括ADB命令、开发者选项和第三方工具方案。通过源码追踪,详细介绍了SystemUI中时钟秒显的核心机制与性能优化考量,帮助开发者和高阶用户轻松实现精确到秒的时间显示。

weixin_42548829的博客 464

ContentObserver分析说明

ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于    数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,   相应地ContentObserver也分为“表“ContentObserver、“行”ContentObserver,

hufei_2008的专栏 390

Android数据库监视全解析:四大方案深度对比与实践指南

本文系统介绍了Android数据库监视的四大方案:1. ContentObserver基于ContentProvider的标准化方案,适合跨进程通知但需手动处理线程;2. CursorLoader传统异步加载方案,现已被LiveData替代;3. SQLite触发器数据库原生方案,适合审计日志但无法直接通知UI;4. Room+LiveData/Flow现代架构方案,提供自动更新、生命周期感知和类型安全。

学无止境 1931

Android ContentObserver 学习

当我们查询数据中某个值是否变化时,通常很耗时费力,数据量大时可能还需要开辟线程来做这种事,急需一种及时反馈机制来处理这种问题,ContentObserver的提供的很 好的解决方案。         ContentObserver为内容观察着,目的就是捕捉内容的变化,当所观察的Uri发生变化时,就会被触发,当然有内容观察者就离不开内容提供者。 内容观察者实现 下面以Android源

Tony的专栏 2872

Android Content Observer实时短信监听示例源代码

在Android开发中,ContentObserver类作为一套专门用于观察数据变化的机制,扮演着至关重要的角色。它允许应用程序监听特定数据提供者的URI变化事件,即使数据源位于系统的其他部分或设备中。通过ContentObserver,开发者可以实现应用内的数据同步、变化通知等功能。ContentObserver的核心优势在于其异步处理方式,开发者无需持续轮询数据源以检测变化,这不仅减少了资源消耗,还提高了程序的响应效率。当数据发生变化时,系统会自动触发ContentObserver的。

weixin_35811662的博客 1176

在Android中实现ContentProvider数据变更的通知机制

观察者模式可以定义为一种行为设计模式,一个目标(Subject)维护一组观察者(Observer),当目标的状态发生变化时,会自动通知观察者。此模式包含以下几个主要组件:Subject(主题):定义注册、移除、通知观察者的接口。ConcreteSubject(具体主题):主题的实现,管理观察者列表,以及在状态变化时通知观察者。Observer(观察者):定义更新接口,当具体主题状态变化时得到通知。ConcreteObserver(具体观察者)

weixin_31938351的博客 897

Android 12系统源码_系统设置(三)Setting注册回调链路源码分析

本文分析了 Android 中监听系统设置变化的实现机制,重点拆解了 registerContentObserver 的完整回调链路。错误修正版指出初版的两个关键误解:通知并非通过 AMS 广播,也不走 IContentProvider Binder 到 Provider 进程。核心发现是 ContentService 作为运行在 system_server 的中央枢纽,通过 ObserverNode 树形结构管理观察者注册,并直接处理来自 SettingsProvider 的通知分发。注册过程涉及跨进程

AfinalStone的专栏 379

SAP ABAP实战:用BAPI_PO_CREATE1创建采购订单时,如何巧妙处理PBXX和PB00条件类型?

本文深入探讨了在SAP ABAP开发中使用BAPI_PO_CREATE1创建采购订单时,如何有效处理PBXX和PB00条件类型的难题。通过分析外协加工订单的特殊需求,提供了从问题定位到解决方案的全流程方法,包括数据流向追踪、字段控制策略和增强实现方案,帮助开发者解决条件类型自动生成的业务痛点。

weixin_30614109的博客 824

android ContentObserver内容观察者基本使用

package com.example.observertest; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; impor...

进击的Ace 64

遥感目标检测数据集汇总

文章目录1. 光学数据集1.1 DIOR1.2 LEVIR1.3 DOTA1.4 RSOD1.5 NWPU VHR-101.6 VEDAI1.7 COWC1.8 ITCVD1.9 DIUx xView 20181.10 HRSC20161.11 TAS1.12 SZTAKI‐INRIA1.13 UCAS AOD1.14 DLR 3K Vehicle2. SAR2.1 SSDD2.2 SSDD+2....

quanwei的博客 6万+

INCA刷写App文件必看:A2l文件配置与ProF文件安装详解(2023最新版)

本文深入解析了使用INCA进行ECU应用程序刷写时,A2l文件配置与ProF文件安装的关键细节与避坑指南。重点阐述了A2l文件必须与待刷写二进制文件严格匹配的核心逻辑,以及ProF文件与Bootloader版本强相关的安装策略,帮助开发者解决刷写过程中的常见失败问题,确保刷写流程的稳定与可靠。

milk8的博客 590

Android -- ContentObserver 内容观察者

1. 实现原理图 2. 示例代码 (暂时有个问题,短信观察者 收到一条短信时 onchange方法会执行两次, 解决方法为:每次监听到变化的时候就去取最新短信的id,跟上次取的比较,如果一样的就不做处理,如果不一样,就该干嘛干嘛) MainActivity.java public class MainActivity extends Activity { pub...

weixin_30657999的博客 139

PowerDesigner16.5下载和安装教程

一、软件下载 1.下载地址(不用复制,直接点击下载链接即可):链接:https://pan.baidu.com/s/1MSnWHWt3SO2Uz5dt9AANAg提取码:z9kk 二、软件安装 1.下载安装包(包含安装文件、汉化包、注册文件) 2.下载后文件内容如下 3....

cicong2374的博客 10万+

ContentService分析(以相机拍照,图库刷新为例)

ContentObserver机制

ltc_666的博客 361

告别‘小目标’丢失!手把手复现DNANet红外检测网络(PyTorch版)

本文详细介绍了如何使用PyTorch从零实现DNANet红外小目标检测网络,重点解析了DNIM模块和CSAM注意力机制的设计与实现。通过实战教程,帮助开发者解决红外小目标检测中的特征提取和训练调参难题,适用于军事侦察、安防监控等场景。

weixin_28701593的博客 251

Flotherm XT学习历程

大家好,通过半年的热仿真软件学习使用,最终发现最好用,最好上手的软件就是Flotherm XT,对新手非常友好,但由于软件近几年才发布,网上教程非常少,后面我将在这里持续分析软件使用心得,和大家一起学习。

xiaoredred的博客 6258

Android中内容观察者的使用---- ContentObserver类详解

                     转载请注明出处:http://blog.csdn.net/qinjuning     前言: 工作中,需要开启一个线程大量的查询某个数据库值发送了变化,导致的开销很大,后来在老大的指点下,利用了      ContentObserver完美的解决了该问题,感到很兴奋,做完之后自己也对ContentObserver做下总结。           Con...

weixin_34326558的博客 82

从零开始理解二极管限幅:5种典型电路设计及实际应用场景解析

本文系统解析了二极管限幅电路的五大经典拓扑,包括串联与并联型上下限幅及双向限幅。通过剖析其核心原理与二极管的单向导电性、正向压降特性,结合ADC输入保护、电源管理、音频处理等实际应用场景,提供了从器件选型到设计陷阱规避的完整实战指南,帮助工程师为信号构建可靠的“安全笼”。

2y3u4i5o6p的博客 1034

Android 7系统无障碍服务(三)无障碍服务的注册与绑定

上一篇我们分析了 AMS 的启动与初始化过程,初始化完成时还是空的,没有任何无障碍服务被绑定。本篇将完整梳理一个无障碍服务从"在 Manifest 中声明"到"被 AMS 绑定并能够接收回调"的全过程,包括 AccessibilityServiceInfo 的解析、服务启用的触发链路、以及的核心绑定逻辑。

AfinalStone的专栏 529

AgentScope实战训练营-Java开发HITL人机协作AI应用完整指南

本文手把手带你从零搭建一个基于 Spring Boot WebFlux + AgentScope + 通义千问的人机协作(HITL)AI 聊天应用。项目涵盖流式对话、工具调用、高危操作确认、对话中断、动态 MCP 工具接入、中英文双语界面等完整功能。文章从环境准备、pom.xml 配置、DTO 数据模型、Hook 机制、AgentService 核心服务、ChatController 接口到前端 HTML/CSS/JS 实现,逐一讲解每行代码的设计意图,并附带 REST API 接口说明、测试场景、常见问题

program哲学 713

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Android应用如何监听截屏、录屏与投屏操作:原理、实现与实战

在移动应用开发中,用户行为监听是保障应用安全与内容保护的关键技术。其核心原理在于通过系统提供的机制感知用户操作,例如利用ContentObserver监听媒体库文件变化来检测截屏,借助MediaProjectionManager回调追踪录屏和投屏会话。这项技术的价值在于让应用从被动响应升级为主动感知,能够在敏感操作发生时及时介入,有效防范内容泄露、作弊风险与数据盗用。典型的应用场景包括在线教育平台的版权内容保护、金融类App的交易安全防护以及在线考试系统的防作弊机制。本文将以Android平台为例,深入解析

weixin_33910759的博客 297

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

programW
博客等级 码龄12年 0粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值