JAndFix: 基于Java实现的Android实时热修复方案

Android热修复实战之AndFix,androidstudiondk配置 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=“http://schemas.andro id.com/apk/res/android” android:layout_width=“match_parent” android:layout_height=“match_parent” android:orientation=“vertical”> <TextView android:id="@ 阅读详情

简述

  • JAndFix是一种基于Java实现的Android实时热修复方案,它并不需要重新启动就能生效。JAndFix是在AndFix的基础上改进实现,AndFix主要是通过jni实现对method(ArtMethod)结构题内容的替换。JAndFix是通过Unsafe对象直接操作Java虚拟机内存来实现替换。

原理

  • 为何JAndfix能够做到即时生效呢? 原因是这样的,在app运行到一半的时候,所有需要发生变更的Class已经被加载过了,在Android上是无法对一个Class进行卸载的。而腾讯系的方案,都是让Classloader去加载新的类。如果不重启,原来的类还在虚拟机中,就无法加载新类。因此,只有在下次重启的时候,在还没走到业务逻辑之前抢先加载补丁中的新类,这样后续访问这个类时,就会Resolve为新的类。从而达到热修复的目的。JAndfix采用的方法是,在已经加载了的类中直接拿到Method(ArtMethod)在JVM的地址,通过Unsafe直接修改Method(ArtMethod)地址的内容,是在原来类的基础上进行修改的。我们这就来看一下JAndfix的具体实现。
虚拟机调用方法的原理

为什么这样替换完就可以实现热修复呢?这需要从虚拟机调用方法的原理说起。在Android 6.0,art虚拟机中ArtMethod的结构是这个样子的:

@art/runtime/art_method.h

class ArtMethod FINAL {
 ... ...

 protected:
  // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
  // The class we are a part of.
  GcRoot<mirror::Class> declaring_class_;

  // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
  GcRoot<mirror::PointerArray> dex_cache_resolved_methods_;

  // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
  GcRoot<mirror::ObjectArray<mirror::Class>> dex_cache_resolved_types_;

  // Access flags; low 16 bits are defined by spec.
  uint32_t access_flags_;

  /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */

  // Offset to the CodeItem.
  uint32_t dex_code_item_offset_;

  // Index into method_ids of the dex file associated with this method.
  uint32_t dex_method_index_;

  /* End of dex file fields. */

  // Entry within a dispatch table for this method. For static/direct methods the index is into
  // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
  // ifTable.
  uint32_t method_index_;

  // Fake padding field gets inserted here.

  // Must be the last fields in the method.
  // PACKED(4) is necessary for the correctness of
  // RoundUp(OFFSETOF_MEMBER(ArtMethod, ptr_sized_fields_), pointer_size).
  struct PACKED(4) PtrSizedFields {
    // Method dispatch from the interpreter invokes this pointer which may cause a bridge into
    // compiled code.
    void* entry_point_from_interpreter_;

    // Pointer to JNI function registered to this method, or a function to resolve the JNI function.
    void* entry_point_from_jni_;

    // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
    // the interpreter.
    void* entry_point_from_quick_compiled_code_;
  } ptr_sized_fields_;

... ...
}

这其中最重要的字段就是entry_point_from_interprete_和entry_point_from_quick_compiled_code_了,从名字可以看出来,他们就是方法的执行入口。我们知道,Java代码在Android中会被编译为Dex Code。 art中可以采用解释模式或者AOT机器码模式执行。

解释模式,就是取出Dex Code,逐条解释执行就行了。如果方法的调用者是以解释模式运行的,在调用这个方法时,就会取得这个方法的entry_point_from_interpreter_,然后跳转过去执行。

而如果是AOT的方式,就会先预编译好Dex Code对应的机器码,然后运行期直接执行机器码就行了,不需要一条条地解释执行Dex Code。如果方法的调用者是以AOT机器码方式执行的,在调用这个方法时,就是跳转到entry_point_from_quick_compiled_code_执行。

AndFix的方法替换其本质是ArtMethod指针所指内容的替换。 Art Method--Art 6.0

变成了这样的整体替换 Art Method--Art 6.0

由Unsafe来实现相当于:

	//src means source ArtMethod Address,dest mean destinction ArtMethod Address
    private void replaceReal(long src, long dest) throws Exception {
        int methodSize = MethodSizeUtils.methodSize();
        int methodIndexOffset = MethodSizeUtils.methodIndexOffset();
        //methodIndex need not replace,becase the process of finding method in vtable need methodIndex
        int methodIndexOffsetIndex = methodIndexOffset / 4;
        //why 1? index 0 is declaring_class, declaring_class need not replace.
        for (int i = 1, size = methodSize / 4; i < size; i++) {
            if (i != methodIndexOffsetIndex) {
                int value = UnsafeProxy.getIntVolatile(dest + i * 4);
                UnsafeProxy.putIntVolatile(src + i * 4, value);
            }
        }
    }

so easy,JAndFix就这样完成了方法替换。值得一提的是,由于忽略了底层ArtMethod结构的差异,对于所有的Android版本都不再需要区分,而统一以Unsafe实现即可,代码量大大减少。即使以后的Android版本不断修改ArtMethod的成员,只要保证ArtMethod数组仍是以线性结构排列,就能直接适用于将来的Android 8.0、9.0等新版本,无需再针对新的系统版本进行适配了。事实也证明确实如此,当我们拿到Google刚发不久的Android O(8.0)开发者预览版的系统时。

对比方案

名字公司实现语言及时生效方法替换方法的增加减少Android版本机型性能损耗补丁大小回滚易用性
JAndFix阿里天猫JAVA支持支持不支持4.0+ALL小小支持好
AndFix阿里支付宝C支持支持不支持4.0+极少部分不支持小小支持好
Tinker腾讯JAVA不支持支持支持ALLALL小小不支持好
Robust美团JAVA支持不支持不支持ALLALL大小支持差(需要反射调用,需要打包插件支持)
Dexposed个人C支持支持不支持4.0+部分不支持小小支持差(需要反射调用)

如何使用

     try {
            Method method1 = Test1.class.getDeclaredMethod("string");
            Method method2 = Test2.class.getDeclaredMethod("string");
            MethodReplaceProxy.instance().replace(method1, method2);
        } catch (Exception e) {
            e.printStackTrace();
        }

Running DEMO

  • 把整个项目放入你的IDE即可(Android Studio)

注意

Proguard
-keep class com.tmall.wireless.jandfix.MethodSizeCase { *;}

解释实现

  • 我以Android Art 6.0的实现来解释为什么这样实现就可实现方法替换
package com.tmall.wireless.jandfix;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Created by jingchaoqinjc on 17/5/15.
 */

public class MethodReplace6_0 implements IMethodReplace {

    static Field artMethodField;

    static {
        try {
            Class absMethodClass = Class.forName("java.lang.reflect.AbstractMethod");
            artMethodField = absMethodClass.getDeclaredField("artMethod");
            artMethodField.setAccessible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void replace(Method src, Method dest) {
        try {
            long artMethodSrc = (long) artMethodField.get(src);
            long artMethodDest = (long) artMethodField.get(dest);
            replaceReal(artMethodSrc, artMethodDest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void replaceReal(long src, long dest) throws Exception {
    	// ArtMethod struct size
        int methodSize = MethodSizeUtils.methodSize();
        int methodIndexOffset = MethodSizeUtils.methodIndexOffset();
        //methodIndex need not replace,becase the process of finding method in vtable
        int methodIndexOffsetIndex = methodIndexOffset / 4;
        //why 1? index 0 is declaring_class, declaring_class need not replace.
        for (int i = 1, size = methodSize / 4; i < size; i++) {
            if (i != methodIndexOffsetIndex) {
                int value = UnsafeProxy.getIntVolatile(dest + i * 4);
                UnsafeProxy.putIntVolatile(src + i * 4, value);
            }
        }
    }
}

1.declaring_class不能替换,为什么不能替换,是因为JVM去调用方式时很多地方都要对declaring_class进行检查。替换declaring_class会导致未知的错误。 2.methodIndex 不能替换,因为public proected等简介寻址的访问权限,本质在寻找方法的时候会查找virtual_methods_,而virtual_methods_是个ArtMethod数组对象,需要通过methodIndex来查找,如果你的methodIndex不对会导致方法寻址出错。 3.为什么AbstractMethod类中对应的artMethod属性的值可以作为c层ArtMethod的地址直接使用?看源码:

@@art/mirror/abstract_method.cc
ArtMethod* AbstractMethod::GetArtMethod() {
  return reinterpret_cast<ArtMethod*>(GetField64(ArtMethodOffset()));
}

@@art/mirror/abstract_method.h
static MemberOffset ArtMethodOffset() {
    return MemberOffset(OFFSETOF_MEMBER(AbstractMethod, art_method_));
  }

从源码可以看出C层在获取ArtMethod的地址,实际上就是把AbstractMethod的artMethod强制转换成了ArtMethod*指针,及我们在Java拿到的artMethod就是c层ArtMethod的实际地址。是不是很简单。

参考

原文地址: https://github.com/cmzy/JAndFix

hotfix-andfix:andfix是阿里巴巴推出的开源的android热修复的一个框架,主要是本地层方法替换。apkpatch将两个apk做一次对比,然后识别不同的部分。可以看到生成的apatch了文件,后缀改成zip再解压开,里面有一个dex文件。通过jadx查看一下原始文件,里面就是被修复的代码所在的类文件,这些更改过的类都加上了一个_CF的后缀,并且移动的方法都被加上一个一个叫@MethodReplace的注释,通过clazz和method指定了需要替换的方法。然后客户端sdk得到补丁文件后就 #AndFix-Demo有关使用AndFix 0.5.0 AndFix的集成使用的演示。 AndFix,全称是Android hot-fix。是阿里开源的一个Android热补丁框架,允许APP在不重新发布版本的情况下修复线上的错误。支持Android 2.3到6.0。 一,andfix使用 首先添加依赖编译'com.alipay.euler:andfix:0.3.1@aar' 然后在Application.onCreate()中添加以下代码:patchManager = new PatchManager(上下文); //初始化补丁包管理器patchManager.init(appversion); //版本字符串appversion = getPackageManager()。getPackageInfo(getPackageName(),0).versionName; patchMa 立即下载

相关推荐

QT下利用FFTW实现功率谱密度分析(周期图法)

写在前面 出于项目需求,需要在QT的msvc 2015编译环境下实现功率谱密度估计。 所以这个需求很直接的关键词就是 1. C++ 2. QT 3. 傅里叶变换 4. 功率谱密度估计 但是在网上搜索了很久,没有一个非常直接的答案。所以我觉得很有必要写一篇文章来说明,在QT msvc2015 32bit环境下如何实现功率谱密度估计。 本文并没有经过严格编辑,但代码是已经可以跑通的,如...

李时珍食疗志的博客 5222

Android进阶宝典 --- AndFix热修复原理解析

手写AndFix热修复框架

qq_33235287的博客 777

避开模板匹配的坑:OpenCVSharp中边缘梯度算法的优化实践

本文深入探讨了OpenCVSharp中边缘梯度算法在模板匹配中的优化实践,解决了传统方法在尺度变化、角度旋转和光照差异下的局限性。通过Sobel算子提取边缘梯度特征,结合轮廓检测和多尺度匹配策略,显著提升了匹配的鲁棒性和准确性。文章还分享了并行计算、内存访问优化等实战技巧,适用于工业检测和自动化识别场景。

http9protocoller的博客 196

热修复学习四:AndFix详解一,一文搞懂

然后输入命令行:就可以顺利打包生成一个apk3、patch生成阶段。

weixin_57992300的博客 1338

ANSYS WORKBENCH——参数化建模以及参数优化(结果导出为Excel)

目录 1、打开软件workbench 2、找到static structure,双击打开 3、选择材料 4、参数化建模 ​ 注:印记面的创建 5、模型属性设置 注:设置优化目标 6、回主界面,选择direct optimization模块 1、打开软件workbench 2、找到static structure,双击打开 3、选择材料 4、参数化建模 至此参数化建模就已经完成啦 注:印记面的创建 印记面一般是一个虚拟的区域,参与实际的网格划分,但是只是由于载.

python_AI_fans的博客 1万+

如何计算 R 中的基尼系数(附示例)

以意大利统计学家Corrado Gini命名,基尼系数是衡量人口收入分配的一种方法。基尼系数的值在 0 到 1 之间,值越高代表收入不平等越大,其中:0代表完全的收入平等(每个人的收入都一样) 1代表完全的收入不平等(一个人拥有所有收入) 您可以在此处找到按国家/地区列出的基尼系数列表。以下示例显示了使用DescTools包中的Gini()函数计算 R 中的基尼系数的两种方法。示例 1:使用个人收入计算基尼系数假设我们有以下 10 个人的年收入列表:收入:5 万美元、5 万美元、7 万美元、7 万美元、7

Mrrunsen的博客 2989

热修复学习四:AndFix详解一

一、AndFix的基本介绍=============官方介绍:alibaba/AndFix以后学习任何新技术,优先去官网查看文档,然后再考虑去看博客文章。二、AndFix执行流程及核心原理=================见官方介绍三、使用AndFix完成线上bug修复===================1、集成阶段a、gradle中添加AndFix依赖compile ‘com.alipay.euler:andfix:0.5.0@aar’b、在代码中完成AndFix的初始化封装AndFixPatchMan

2401_84149496的博客 904

SAP S/4HANA 数据 API 化与 MCP 集成全流程实战指南

通过 RAP 发布 OData、利用 Communication Arrangement 打通外网、再在 MCP-server 里封装函数签名,你就能把 S/4HANA 的核心数据安全、标准化地暴露给任意支持 MCP 的大模型。这条链路的每一个环节都由 SAP 与 MCP 社区提供了现成框架,既兼顾了企业合规,也让 LLM 落地更为轻量。期待你在项目里把这些拼图真正串联起来,让业务数据与生成式 AI 碰撞出高价值的火花。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 974

浅谈ConcurrentHashMap(1)

Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!上面分享的字节跳动公司2020年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。就先写到这,码字不易,写的很片面不好之处敬请指出,如果觉得有参考价值的朋友也可以关注一下我。

2401_84149423的博客 1070

【Java基础知识 1】Java入门级概述

优快云最强Java专栏,包含全部Java基础知识点、Java8新特性、Java集合、Java多线程、Java代码实例,理论结合实战,实现Java的轻松学习。

学Java,找哪吒 16万+

AI智能体丨使用Coze一键生成爆款书单视频,3个月内获赞131.1w!(附保姆级教程)

这篇教程详细介绍了如何使用扣子(Coze)平台的AI智能工作流批量制作爆款书单视频。通过自动化流程,只需1-2分钟即可生成45-50秒的书单视频,效率提升近百倍。工作流包含文案生成、音频合成、图片素材获取、视频剪辑等全流程,支持随机生成8本书单、自动提取金句、智能配音等功能。教程提供了完整的操作步骤,包括剪映草稿配置、特效添加等细节,并附体验链接和示例效果。最终生成的视频包含书单展示、金句字幕、背景音乐等元素,适合书单号运营者快速批量产出内容。

李同学Lino的博客 2353

Android热修复学习之旅——Andfix框架完全解析

通过方法名和本地已有的该方法的参数信息获取有bug的方法,然后将有bug的方法和修复的方法一起传入进行修复 注意:上面的操作,传入的是修复新的方法信息以及需要修复的旧方法名称,不过这里得先获取到旧方法类型,可以看到修复的新旧方法的签名必须一致,所谓签名就是方法的名称,参数个数,参数类型都必须一致,不然这里就报错的。接下来就是交给native方法了,由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式。

老皮的博客 1921

手把手教你学simulink(12.10)--设计和仿真一个用于控制飞机纵向运动(高度和速度)的多变量控制系统

飞机的纵向运动控制是航空工程中的一个重要问题,涉及飞机的高度和速度的精确控制。多变量控制系统可以同时控制多个飞行参数,确保飞机在最佳条件下运行。Simulink提供了一个强大的平台,用于建模、仿真和分析动态系统,非常适合设计和测试多变量控制系统。通过本项目的实施,我们成功设计和仿真了一个用于控制飞机纵向运动(高度和速度)的多变量控制系统。该项目展示了从系统建模、控制器设计、仿真验证到结果分析的完整流程,为多变量控制系统的设计提供了有价值的参考。

MHD0815的博客 597

JAndFix:Android实时热修复的革命性方案

JAndFix:Android实时热修复的革命性方案 项目介绍 在移动应用开发中,热修复技术已经成为不可或缺的一部分。它允许开发者在不发布新版本的情况下,快速修复线上问题,提升用户体验。然而,传统的热修复方案往往需要重启应用才能生效,这在某些场景下显得不够灵活。为了解决这一问题,阿里巴巴天猫团队推出了JAndFix,一种基于Java实现的Android实时热修复方案。JAndFix不仅继承了And...

gitblog_00305的博客 387

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

JAndFix 开源项目教程

JAndFix 开源项目教程 项目介绍 JAndFix 是一个基于 Java 实现的 Android 实时热修复方案。它允许开发者在不重新发布应用的情况下,修复应用中的 bug。JAndFix 的核心原理是通过 Java 方法的替换来实现热修复,这使得它能够在应用运行时动态地替换有问题的代码。JAndFix 是在 AndFix 的基础上改进实现的,通过 Unsafe 对象直接操作 Java 虚拟机...

gitblog_00763的博客 639

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

JAndFix

Welcome to the JAndFix wiki! `` JAndFix 简述 JAndFix是一种基于Java实现的Android实时热修复方案,它并不需要重新启动就能生效。JAndFix是在AndFix的基础上改进实现,AndFix主要是通过jni实现对method(ArtMeth...

chousunning1343的博客 221

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

JAndFix开源项目安装与使用指南

JAndFix开源项目安装与使用指南 1. 项目目录结构及介绍 JAndFix是一个基于Java的Android实时热修复工具,它的仓库目录结构简洁明了,以下为主要的文件和目录说明: app:这是示例应用的模块,包含了演示JAndFix如何使用的代码和资源。 build.gradle: Gradle构建脚本,定义了模块的依赖和编译配置。 gradle.properties: 包含了一些...

gitblog_00971的博客 640

JAndFix 开源项目使用教程

JAndFix 开源项目使用教程 1. 项目介绍 JAndFix 是一个基于 Java 实现的 Android 实时热修复工具。它能够在不重启应用的情况下,即时修复应用中的 Bug。JAndFix 是在 AndFix 的基础上进行改进的,通过使用 Unsafe 对象直接操作 Java 虚拟机内存,实现对方法(ArtMethod)内容的替换。 主要特点: 即时生效:修复后的代码无需重启应用即可生效...

gitblog_00013的博客 326

苹果核 - JAndFix——Java版本的AndFix方案

兄弟团队出品,转载自:http://www.jianshu.com/p/aaa6ce26f083 Welcome to the JAndFix wiki! JAndFix 简述 JAndFix是一种基于Java实现的Android实时热修复方案,它并不需要重新启动就能生效。JAndFix是在AndFix的基础上改进实现,AndFix主要是通过jni实现对method(A

醉离歌醉的专栏 344

Android Unsafe 分析

前两天在看Android平台上的FutureTask类的时候无意看到了Unsafe,当时这个类是报红色的属于sun.* API肿的类,并且不是J2SE中真正的一部分,因为你很有可能找不到任何的官方信息,但是你可以在Android源代码中可以找到该代码的实现以及 Native的实现,这个有利于我们的学习和使用。该类在Android4.4系统的Art虚拟机和Davilk虚拟中的代码中,其路径分别为: 1

寻梦者 4086

Android 热修补方案(AndFix)

AndFix介绍AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix就是 “Android Hot-Fix”的缩写。AndFix支持Android 2.3到6.0版本,并且支持arm 与 X86系统架构的设备。完美支持Dalvik与ART的Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。A

cocoooooa的专栏 5326

Android热修复实战之AndFix

写在前面 上一篇《Android热修复技术简介》中对Android的热修复技术的概念和常用的技术方案做了一个简单的介绍,那么今天就来实战一下热修复技术,我们使用的是AndFix,为什么是它?因为无论是从使用上还是原理上AndFix都是相对简单的,毕竟这是实战的第一篇,还是要有个由易到难的过程的,好了,话不多说,开始吧! 一、AndFix基本介绍 1.1、AndFix简介 AndFix项目地址:https://github.com/alibaba/AndFix,大家访问这个地址去看它的详细介绍,我这里

江湖人陆仁甲 828

fix协议封装 java_FIX协议-接入总结

FIX协议-接入总结它是由国际FIX协会组织提供的一个开放式协议,目的是推动国际贸易电子化的进程,在各类参与者之间,包括投资经理、经纪人,买方、卖方建立起实时的电子化通讯协议。Fix协议的目标是把各类证券金融业务需求流程格式化,使之成为一个个可用计算机语言描述的功能流程,并在每个业务功能接口上统一交换格式,方便各个功能模块的连接。目前市场 FIX 协议版本基本都是 FIX.4.4;FIX协议框架的...

weixin_39920397的博客 1708

java fix_Java中的低延迟FIX引擎

java fix 总览 Chronicle FIX是我们的Low Latency FIX引擎和Java数据库。 是什么使它与众不同? 是为Java中的超低GC *设计的。 支持字符串和日期时间的方式可以最大程度地减少垃圾和开销。 可自定义为仅包含您期望的字段。 使用通常在二进制解析器和生成器中使用的优化,例如一次读取/写入4或8个字节,以提高效率。 建立在低延迟持久性...

最佳 Java 编程 1008

Android热修复框架——AndFix

一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架。在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本。今天看了热修复技术,感觉挺好玩的,就实现了使用的全过程。下面记录使用开源框架阿里巴巴的AndFix过程。 这里说的不是热修复怎么实现修bug的原理,这里说的是怎么使用AndFix。如果你想了解更多的andFix实现原理,你

Vson的博客 721
上一篇: An introduction to Generative Adversarial Networks (with code in TensorFlow)
下一篇: 十图详解TensorFlow数据读取机制(附代码)
Omni-Space
博客等级 码龄11年 2158粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值