java crashhandler_Android中CrashHandler的实现

本文详细介绍了在Android中如何实现一个CrashHandler,用于捕获和处理未捕获的异常。CrashHandler收集设备信息、系统配置、内存情况等,并将异常信息保存到文件,然后上传到服务器。当发生异常时,应用会显示提示并退出。此外,它还提供了删除崩溃日志文件和上传崩溃信息的功能。
摘要由CSDN通过智能技术生成

public class CrashHandler implements UncaughtExceptionHandler {

private static final String TAG = CrashHandler.class.getSimpleName();

private Context mContext;

private String mPath;

private static final String sFileName = "crash";

private static final String sFileNameSuffix = ".trace";

private DateFormat mDateFormat;

private ExecutorService mExecutor = Executors.newSingleThreadExecutor();

//private ExecutorService mExecutor = Executors.newCachedThreadPool();

private static CrashHandler mInstance;

private UncaughtExceptionHandler mUncaughtExceptionHandler;

private ConcurrentHashMap mInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mPackageInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mDeviceInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mSysInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mSecureInfoMap = new ConcurrentHashMap<>();

private String mMemInfo;

private String mThrowableInfo;

/** 异常信息 */

private static final String sThrowableInfoString = "throwable_info";

/** 应用包信息 */

private static final String sPackageInfoMap = "package_info_map";

/** 设备数据信息 */

private static final String sBuildInfoMap = "build_info_map";

/** 系统常规配置信息 */

private static final String sSystemInfoMap = "system_info_map";

/** 手机安全配置信息 */

private static final String sSecureInfoMap = "secure_info_map";

/** 内存情况信息 */

private static final String sMemoryInfoString = "memory_info_map";

private CrashUploader mCrashUploader;

public static CrashHandler getInstance() {

if (mInstance == null) {

synchronized (CrashHandler.class) {

if (mInstance == null) {

mInstance = new CrashHandler();

}

}

}

return mInstance;

}

public void init(Context context, CrashUploader crashUploader) {

mContext = context.getApplicationContext();

mCrashUploader = crashUploader;

mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();

Thread.setDefaultUncaughtExceptionHandler(this);

mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());

File crashDir = mContext.getExternalFilesDir(sFileName);

if (crashDir != null){

if (!crashDir.exists()) {

crashDir.mkdirs();

}

mPath = crashDir.getAbsolutePath();

}

}

public void deleteCrashFile(){

clearInfo();

deleteDirectory(mPath);

}

/**

* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法

*

* @param t 出现未捕获异常的线程

* @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息

*/

@Override

public void uncaughtException(Thread t, Throwable e) {

if (!handleException(e) && mUncaughtExceptionHandler != null) {

//如果用户没有处理则让系统默认的异常处理器来处理

mUncaughtExceptionHandler.uncaughtException(t, e);

}else {

killProcess();

}

}

/**

* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.

*

* @param ex

* @return true:如果处理了该异常信息;否则返回false.

*/

private boolean handleException(Throwable ex) {

if (ex == null) {

return false;

}

mExecutor.execute(new Runnable() {

@Override

public void run() {

Looper.prepare();

Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show();

Looper.loop();

}

});

collectInfo(ex);

saveCrashInfoToFile();

uploadCrashMessage();

return true;

}

/**

* 退出应用

*/

private void killProcess() {

MLog.e(TAG, "killProcess");

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Intent startMain = new Intent(Intent.ACTION_MAIN);

startMain.addCategory(Intent.CATEGORY_HOME);

startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

mContext.startActivity(startMain);

//Process.killProcess(Process.myPid());

System.exit(1);

}

/**

* 收集设备参数信息

*

* @param ex

*/

private void collectInfo(Throwable ex){

clearInfo();

collectThrowableInfo(ex);

collectPackageInfo();

collectBuildInfo();

collectSystemInfo();

collectSecureInfo();

collectMemInfo();

mInfoMap.put(sThrowableInfoString, mThrowableInfo);

mInfoMap.put(sPackageInfoMap, mPackageInfoMap);

mInfoMap.put(sBuildInfoMap, mDeviceInfoMap);

mInfoMap.put(sSystemInfoMap, mSysInfoMap);

mInfoMap.put(sSecureInfoMap, mSecureInfoMap);

mInfoMap.put(sMemoryInfoString, mMemInfo);

}

private void clearInfo(){

mMemInfo = "";

mPackageInfoMap.clear();

mDeviceInfoMap.clear();

mSysInfoMap.clear();

mSecureInfoMap.clear();

mInfoMap.clear();

mThrowableInfo = "";

}

/**

* 保存日志文件

*/

private void saveCrashInfoToFile(){

StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap);

mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap));

mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap));

mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap));

mStringBuffer.append(mMemInfo);

mStringBuffer.append(mThrowableInfo);

String mTime = mDateFormat.format(new Date());

if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

try {

String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix;

FileOutputStream mFileOutputStream = new FileOutputStream(filePath);

mFileOutputStream.write(mStringBuffer.toString().getBytes());

mFileOutputStream.close();

MLog.d(TAG, filePath);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}catch (Exception e) {

e.printStackTrace();

}

}

}

/**

* 上传崩溃信息

*/

private void uploadCrashMessage(){

new Thread(new Runnable() {

@Override

public void run() {

//http 上传服务端

//deleteCrashFile();

//成功

Looper.prepare();

mCrashUploader.uploadSuccess();

Looper.loop();

//失败

Looper.prepare();

mCrashUploader.uploadError("");

Looper.loop();

}

}).start();

}

/**

* 获取捕获异常的信息

*

* @param ex

*/

private void collectThrowableInfo(Throwable ex) {

Writer mWriter = new StringWriter();

PrintWriter mPrintWriter = new PrintWriter(mWriter);

ex.printStackTrace(mPrintWriter);

ex.printStackTrace();

Throwable throwable = ex.getCause();

while (throwable != null) {

throwable.printStackTrace(mPrintWriter);

mPrintWriter.append("\r\n");

throwable = throwable.getCause();

}

mPrintWriter.close();

mThrowableInfo = mWriter.toString();

}

/**

* 获取APP进程内存信息

*/

private void collectMemInfo() {

BufferedReader bufferedReader = null;

StringBuffer stringBuffer = new StringBuffer();

ArrayList commandLine = new ArrayList<>();

commandLine.add("dumpsys");

commandLine.add("meminfo");

commandLine.add(Integer.toString(Process.myPid()));

try {

java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));

bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024);

while (true) {

String line = bufferedReader.readLine();

if (line == null) {

break;

}

stringBuffer.append(line);

stringBuffer.append("\n");

}

} catch (IOException e) {

e.printStackTrace();

} finally {

if (bufferedReader != null) {

try {

bufferedReader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

mMemInfo = stringBuffer.toString();

}

/**

* 获取系统安全设置信息

*/

private void collectSecureInfo() {

Field[] fields = Settings.Secure.class.getFields();

for (Field field : fields) {

if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) {

try {

String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));

if (value != null) {

mSecureInfoMap.put(field.getName(), value);

}

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

/**

* 获取系统常规设定属性

*/

private void collectSystemInfo() {

Field[] fields = Settings.System.class.getFields();

for (Field field : fields) {

if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) {

try {

String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));

if (value != null) {

mSysInfoMap.put(field.getName(), value);

}

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

/**

* 从系统属性中提取设备硬件和版本信息

*

* 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因

*

*/

private void collectBuildInfo() {

Field[] mFields = Build.class.getDeclaredFields();

for (Field field : mFields) {

try {

field.setAccessible(true);

mDeviceInfoMap.put(field.getName(), field.get("").toString());

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

/**

* 获取应用包参数信息

*/

private void collectPackageInfo() {

try {

PackageManager mPackageManager = mContext.getPackageManager();

PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);

if (mPackageInfo != null) {

mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName);

mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName);

mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + "");

}

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

}

/**

* 将HashMap遍历转换成StringBuffer

*/

@NonNull

public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap infoMap) {

StringBuffer mStringBuffer = new StringBuffer();

for (Map.Entry entry : infoMap.entrySet()) {

String key = entry.getKey();

String value = entry.getValue();

mStringBuffer.append(key + "=" + value + "\r\n");

}

return mStringBuffer;

}

private String addSlash(String pPath) {

if (!TextUtils.isEmpty(pPath)) {

if (!pPath.endsWith(File.separator)) {

pPath = pPath + File.separator;

}

}else {

pPath = File.separator;

}

return pPath;

}

private boolean deleteDirectory(String pDir){

File dirFile = new File(addSlash(pDir));

if (!dirFile.exists() || !dirFile.isDirectory()) {

return false;

}

boolean delFlag = true;

File[] files = dirFile.listFiles();

for (File file : files) {

if (file.isFile()) {

delFlag = deleteFile(file.getAbsolutePath());

if (!delFlag) {

break;

}

} else {

delFlag = deleteDirectory(file.getAbsolutePath());

if (!delFlag) {

break;

}

}

}

if (!delFlag) {

return false;

}

if (dirFile.delete()) {

return true;

} else {

return false;

}

}

private boolean deleteFile(String pFileName){

try {

File file = new File(pFileName);

if (file.isFile() && file.exists()) {

file.delete();

return true;

} else {

return false;

}

} catch (Exception e) {

return false;

}

}

/**

* 崩溃信息上传接口回调

*/

public interface CrashUploader {

void uploadSuccess();

void uploadError(String error);

}

}

public class CrashHandler implements UncaughtExceptionHandler {

private static final String TAG = CrashHandler.class.getSimpleName();

private Context mContext;

private String mPath;

private static final String sFileName = "crash";

private static final String sFileNameSuffix = ".trace";

private DateFormat mDateFormat;

private ExecutorService mExecutor = Executors.newSingleThreadExecutor();

//private ExecutorService mExecutor = Executors.newCachedThreadPool();

private static CrashHandler mInstance;

private UncaughtExceptionHandler mUncaughtExceptionHandler;

private ConcurrentHashMap mInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mPackageInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mDeviceInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mSysInfoMap = new ConcurrentHashMap<>();

private ConcurrentHashMap mSecureInfoMap = new ConcurrentHashMap<>();

private String mMemInfo;

private String mThrowableInfo;

/** 异常信息 */

private static final String sThrowableInfoString = "throwable_info";

/** 应用包信息 */

private static final String sPackageInfoMap = "package_info_map";

/** 设备数据信息 */

private static final String sBuildInfoMap = "build_info_map";

/** 系统常规配置信息 */

private static final String sSystemInfoMap = "system_info_map";

/** 手机安全配置信息 */

private static final String sSecureInfoMap = "secure_info_map";

/** 内存情况信息 */

private static final String sMemoryInfoString = "memory_info_map";

private CrashUploader mCrashUploader;

public static CrashHandler getInstance() {

if (mInstance == null) {

synchronized (CrashHandler.class) {

if (mInstance == null) {

mInstance = new CrashHandler();

}

}

}

return mInstance;

}

public void init(Context context, CrashUploader crashUploader) {

mContext = context.getApplicationContext();

mCrashUploader = crashUploader;

mUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();

Thread.setDefaultUncaughtExceptionHandler(this);

mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());

File crashDir = mContext.getExternalFilesDir(sFileName);

if (crashDir != null){

if (!crashDir.exists()) {

crashDir.mkdirs();

}

mPath = crashDir.getAbsolutePath();

}

}

public void deleteCrashFile(){

clearInfo();

deleteDirectory(mPath);

}

/**

* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用uncaughtException方法

*

* @param t 出现未捕获异常的线程

* @param e 未捕获的异常,有了这个ex,我们就可以得到异常信息

*/

@Override

public void uncaughtException(Thread t, Throwable e) {

if (!handleException(e) && mUncaughtExceptionHandler != null) {

//如果用户没有处理则让系统默认的异常处理器来处理

mUncaughtExceptionHandler.uncaughtException(t, e);

}else {

killProcess();

}

}

/**

* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.

*

* @param ex

* @return true:如果处理了该异常信息;否则返回false.

*/

private boolean handleException(Throwable ex) {

if (ex == null) {

return false;

}

mExecutor.execute(new Runnable() {

@Override

public void run() {

Looper.prepare();

Toast.makeText(mContext, "Sorry, The program is abnormal and will exit soon.", Toast.LENGTH_SHORT).show();

Looper.loop();

}

});

collectInfo(ex);

saveCrashInfoToFile();

uploadCrashMessage();

return true;

}

/**

* 退出应用

*/

private void killProcess() {

MLog.e(TAG, "killProcess");

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

Intent startMain = new Intent(Intent.ACTION_MAIN);

startMain.addCategory(Intent.CATEGORY_HOME);

startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

mContext.startActivity(startMain);

//Process.killProcess(Process.myPid());

System.exit(1);

}

/**

* 收集设备参数信息

*

* @param ex

*/

private void collectInfo(Throwable ex){

clearInfo();

collectThrowableInfo(ex);

collectPackageInfo();

collectBuildInfo();

collectSystemInfo();

collectSecureInfo();

collectMemInfo();

mInfoMap.put(sThrowableInfoString, mThrowableInfo);

mInfoMap.put(sPackageInfoMap, mPackageInfoMap);

mInfoMap.put(sBuildInfoMap, mDeviceInfoMap);

mInfoMap.put(sSystemInfoMap, mSysInfoMap);

mInfoMap.put(sSecureInfoMap, mSecureInfoMap);

mInfoMap.put(sMemoryInfoString, mMemInfo);

}

private void clearInfo(){

mMemInfo = "";

mPackageInfoMap.clear();

mDeviceInfoMap.clear();

mSysInfoMap.clear();

mSecureInfoMap.clear();

mInfoMap.clear();

mThrowableInfo = "";

}

/**

* 保存日志文件

*/

private void saveCrashInfoToFile(){

StringBuffer mStringBuffer = infoMapToStrBuffer(mPackageInfoMap);

mStringBuffer.append(infoMapToStrBuffer(mDeviceInfoMap));

mStringBuffer.append(infoMapToStrBuffer(mSysInfoMap));

mStringBuffer.append(infoMapToStrBuffer(mSecureInfoMap));

mStringBuffer.append(mMemInfo);

mStringBuffer.append(mThrowableInfo);

String mTime = mDateFormat.format(new Date());

if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

try {

String filePath = addSlash(mPath) + mTime + sFileName + sFileNameSuffix;

FileOutputStream mFileOutputStream = new FileOutputStream(filePath);

mFileOutputStream.write(mStringBuffer.toString().getBytes());

mFileOutputStream.close();

MLog.d(TAG, filePath);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}catch (Exception e) {

e.printStackTrace();

}

}

}

/**

* 上传崩溃信息

*/

private void uploadCrashMessage(){

new Thread(new Runnable() {

@Override

public void run() {

//http 上传服务端

//deleteCrashFile();

//成功

Looper.prepare();

mCrashUploader.uploadSuccess();

Looper.loop();

//失败

Looper.prepare();

mCrashUploader.uploadError("");

Looper.loop();

}

}).start();

}

/**

* 获取捕获异常的信息

*

* @param ex

*/

private void collectThrowableInfo(Throwable ex) {

Writer mWriter = new StringWriter();

PrintWriter mPrintWriter = new PrintWriter(mWriter);

ex.printStackTrace(mPrintWriter);

ex.printStackTrace();

Throwable throwable = ex.getCause();

while (throwable != null) {

throwable.printStackTrace(mPrintWriter);

mPrintWriter.append("\r\n");

throwable = throwable.getCause();

}

mPrintWriter.close();

mThrowableInfo = mWriter.toString();

}

/**

* 获取APP进程内存信息

*/

private void collectMemInfo() {

BufferedReader bufferedReader = null;

StringBuffer stringBuffer = new StringBuffer();

ArrayList commandLine = new ArrayList<>();

commandLine.add("dumpsys");

commandLine.add("meminfo");

commandLine.add(Integer.toString(Process.myPid()));

try {

java.lang.Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()]));

bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 4 * 1024);

while (true) {

String line = bufferedReader.readLine();

if (line == null) {

break;

}

stringBuffer.append(line);

stringBuffer.append("\n");

}

} catch (IOException e) {

e.printStackTrace();

} finally {

if (bufferedReader != null) {

try {

bufferedReader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

mMemInfo = stringBuffer.toString();

}

/**

* 获取系统安全设置信息

*/

private void collectSecureInfo() {

Field[] fields = Settings.Secure.class.getFields();

for (Field field : fields) {

if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class && field.getName().startsWith("WIFI_AP")) {

try {

String value = Settings.Secure.getString(mContext.getContentResolver(), (String) field.get(null));

if (value != null) {

mSecureInfoMap.put(field.getName(), value);

}

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

/**

* 获取系统常规设定属性

*/

private void collectSystemInfo() {

Field[] fields = Settings.System.class.getFields();

for (Field field : fields) {

if (!field.isAnnotationPresent(Deprecated.class) && field.getType() == String.class) {

try {

String value = Settings.System.getString(mContext.getContentResolver(), (String) field.get(null));

if (value != null) {

mSysInfoMap.put(field.getName(), value);

}

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

/**

* 从系统属性中提取设备硬件和版本信息

*

* 迭代Build的字段key-value 此处的信息主要是为了在服务器端手机各种版本手机报错的原因

*

*/

private void collectBuildInfo() {

Field[] mFields = Build.class.getDeclaredFields();

for (Field field : mFields) {

try {

field.setAccessible(true);

mDeviceInfoMap.put(field.getName(), field.get("").toString());

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

/**

* 获取应用包参数信息

*/

private void collectPackageInfo() {

try {

PackageManager mPackageManager = mContext.getPackageManager();

PackageInfo mPackageInfo = mPackageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);

if (mPackageInfo != null) {

mPackageInfoMap.put("PackageName", TextUtils.isEmpty(mPackageInfo.packageName) ? "unknown" : mPackageInfo.packageName);

mPackageInfoMap.put("VersionName", TextUtils.isEmpty(mPackageInfo.versionName) ? "unknown" : mPackageInfo.versionName);

mPackageInfoMap.put("VersionCode", mPackageInfo.versionCode + "");

}

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

}

/**

* 将HashMap遍历转换成StringBuffer

*/

@NonNull

public static StringBuffer infoMapToStrBuffer(ConcurrentHashMap infoMap) {

StringBuffer mStringBuffer = new StringBuffer();

for (Map.Entry entry : infoMap.entrySet()) {

String key = entry.getKey();

String value = entry.getValue();

mStringBuffer.append(key + "=" + value + "\r\n");

}

return mStringBuffer;

}

private String addSlash(String pPath) {

if (!TextUtils.isEmpty(pPath)) {

if (!pPath.endsWith(File.separator)) {

pPath = pPath + File.separator;

}

}else {

pPath = File.separator;

}

return pPath;

}

private boolean deleteDirectory(String pDir){

File dirFile = new File(addSlash(pDir));

if (!dirFile.exists() || !dirFile.isDirectory()) {

return false;

}

boolean delFlag = true;

File[] files = dirFile.listFiles();

for (File file : files) {

if (file.isFile()) {

delFlag = deleteFile(file.getAbsolutePath());

if (!delFlag) {

break;

}

} else {

delFlag = deleteDirectory(file.getAbsolutePath());

if (!delFlag) {

break;

}

}

}

if (!delFlag) {

return false;

}

if (dirFile.delete()) {

return true;

} else {

return false;

}

}

private boolean deleteFile(String pFileName){

try {

File file = new File(pFileName);

if (file.isFile() && file.exists()) {

file.delete();

return true;

} else {

return false;

}

} catch (Exception e) {

return false;

}

}

/**

* 崩溃信息上传接口回调

*/

public interface CrashUploader {

void uploadSuccess();

void uploadError(String error);

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值