PacakgeManagerService学习笔记七-APK installation

本文详细探讨了Android系统的APK安装流程,从adb install命令的使用到PackageManagerService的内部实现,包括解析安装参数、安装路径的选择、APK的验证与加密等关键步骤。通过对installPackageWithVerificationAndEncryption方法的分析,揭示了Android系统如何确保APK的安全安装。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. adb install: adb intall有多个参数,只做简单学习,adb install xxx.apk, adb是一个命令,install是其参数。

/*--commandline.c--> adb_commandline*/

int adb_commandline(int argc, char **argv)
{
	/*-----------省略------------*/
    if(!strcmp(argv[0], "install")) {
        if (argc < 2) return usage();
        return install_app(ttype, serial, argc, argv);
    }
	/*-----------省略------------*/
}

int install_app(transport_type transport, char* serial, int argc, char** argv)
{
	/*
	将要安装的APK从host主机上,copy到手机中,需要先设置copy目录,
	如果要安装在内存中对应的目录为/data/local/tmp;
	如果要安装在sd卡中对应的目录为/sdcard/temp
	*/
    static const char *const DATA_DEST = "/data/local/tmp/%s";
    static const char *const SD_DEST = "/sdcard/tmp/%s";
    const char* where = DATA_DEST;
    char apk_dest[PATH_MAX];
    char verification_dest[PATH_MAX];
    char* apk_file;
    char* verification_file = NULL;
    int file_arg = -1;
    int err;
    int i;
    int verify_apk = 1;

    for (i = 1; i < argc; i++) {
        if (*argv[i] != '-') {
            file_arg = i;
            break;
        } else if (!strcmp(argv[i], "-i")) {
            // Skip the installer package name.
            i++;
        } else if (!strcmp(argv[i], "-s")) {
            where = SD_DEST;
        } else if (!strcmp(argv[i], "--algo")) {
            verify_apk = 0;
            i++;
        } else if (!strcmp(argv[i], "--iv")) {
            verify_apk = 0;
            i++;
        } else if (!strcmp(argv[i], "--key")) {
            verify_apk = 0;
            i++;
        }
    }

    if (file_arg < 0) {
        fprintf(stderr, "can't find filename in arguments\n");
        return 1;
    } else if (file_arg + 2 < argc) {
        fprintf(stderr, "too many files specified; only takes APK file and verifier file\n");
        return 1;
    }

    apk_file = argv[file_arg];

    if (file_arg != argc - 1) {
        verification_file = argv[file_arg + 1];
    }

    if (check_file(apk_file) || check_file(verification_file)) {
        return 1;
    }

    snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));
    if (verification_file != NULL) {
        snprintf(verification_dest, sizeof(verification_dest), where, get_basename(verification_file));

        if (!strcmp(apk_dest, verification_dest)) {
            fprintf(stderr, "APK and verification file can't have the same name\n");
            return 1;
        }
    }
	
	/*
	如果有verification.apk,先做检查
	*/
    err = do_sync_push(apk_file, apk_dest, verify_apk);
    if (err) {
        goto cleanup_apk;
    } else {
        argv[file_arg] = apk_dest; /* destination name, not source location */
    }

    if (verification_file != NULL) {
        err = do_sync_push(verification_file, verification_dest, 0 /* no verify APK */);
        if (err) {
            goto cleanup_apk;
        } else {
            argv[file_arg + 1] = verification_dest; /* destination name, not source location */
        }
    }
	/*
	执行pm脚本进行安装
	*/
    pm_command(transport, serial, argc, argv);

cleanup_apk:
    if (verification_file != NULL) {
        delete_file(transport, serial, verification_dest);
    }

    delete_file(transport, serial, apk_dest);

    return err;
}


以上代码中的3个关键点:

1> android4.0新增了APK安装过程中的Verfication的功能。该功能就是在安装的过程中将相关信息发送给指定的Verfication程序(另外一个apk),由它对要安装的APK进行检查
2> 调用pm_command进行安装

3> 安装完后,执行删除复制的临时文件 

2. pm 

static int pm_command(transport_type transport, char* serial,
                      int argc, char** argv)
{
    char buf[4096];

    snprintf(buf, sizeof(buf), "shell:pm");

    while(argc-- > 0) {
        char *quoted;

        quoted = dupAndQuote(*argv++);

        strncat(buf, " ", sizeof(buf)-1);
        strncat(buf, quoted, sizeof(buf)-1);
        free(quoted);
    }
	/*
	发送“shell:pm install 参数”到手持式终端
	*/
    send_shellcommand(transport, serial, buf);
    return 0;
}
pm脚本:

/*
pm脚本
*/
/*
#Script to start "pm" on the device, which has a very rudimentary(基本的)
#shell
#
base=/system
export CLASSPATH=$base/frameworks/pm.jar 
exec app_process $base/bin com.android.commands.pm.Pm “$@”
*/
该脚本通过app_process进程指向pm,jar中的main方法。
/*---------pm.java------------*/
 public static void main(String[] args) {
        new Pm().run(args);
    }

 public void run(String[] args) {
        boolean validCommand = false;
        if (args.length < 1) {
            showUsage();
            return;
        }
		/*
		获得binder客户端
		*/
        mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));
        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        if (mPm == null) {
            System.err.println(PM_NOT_RUNNING_ERR);
            return;
        }

        mArgs = args;
        String op = args[0];
        mNextArg = 1;
		.........

        if ("install".equals(op)) {
            runInstall();
            return;
        }

		.........
}

run方法中调用runInstall方法

1. 解析安装参数

2. 调用installPackageWithVerificationAndEncrytion完成后续的安装工作

private void runInstall() {
        int installFlags = PackageManager.INSTALL_ALL_USERS;
        String installerPackageName = null;

        String opt;

        String algo = null;
        byte[] iv = null;
        byte[] key = null;

        String macAlgo = null;
        byte[] macKey = null;
        byte[] tag = null;
        String originatingUriString = null;
        String referrer = null;

		/*
		省略部分
		该部分对安装参数进行解析
		*/
		
        /*省略*/
        final Uri apkURI;
        final Uri verificationURI;
        final Uri originatingURI;
        final Uri referrerURI;

        if (originatingUriString != null) {
            originatingURI = Uri.parse(originatingUriString);
        } else {
            originatingURI = null;
        }

        if (referrer != null) {
            referrerURI = Uri.parse(referrer);
        } else {
            referrerURI = null;
        }

        // Populate apkURI, must be present
        final String apkFilePath = nextArg();
        System.err.println("\tpkg: " + apkFilePath);
        if (apkFilePath != null) {
            apkURI = Uri.fromFile(new File(apkFilePath));
        } else {
            System.err.println("Error: no package specified");
            return;
        }

        // Populate verificationURI, optionally present
        final String verificationFilePath = nextArg();
        if (verificationFilePath != null) {
            System.err.println("\tver: " + verificationFilePath);
            verificationURI = Uri.fromFile(new File(verificationFilePath));
        } else {
            verificationURI = null;
        }

		/*
		inofify机制,接收安装结果
		*/
        PackageInstallObserver obs = new PackageInstallObserver();
        try {
            VerificationParams verificationParams = new VerificationParams(verificationURI,
                    originatingURI, referrerURI, VerificationParams.NO_UID, null);
			/*
			调用installPackageWithVerificationAndEncryption完成安装
			*/
            mPm.installPackageWithVerificationAndEncryption(apkURI, obs, installFlags,
                    installerPackageName, verificationParams, encryptionParams);

            synchronized (obs) {
                while (!obs.finished) {
                    try {
                        obs.wait();
                    } catch (InterruptedException e) {
                    }
                }
                if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
                    System.out.println("Success");
                } else {
                    System.err.println("Failure ["
                            + installFailureToString(obs.result)
                            + "]");
                }
            }
        } catch (RemoteException e) {
            System.err.println(e.toString());
            System.err.println(PM_NOT_RUNNING_ERR);
        }
    }

installPackageWithVerfication:

/*PackageManagerService.java*/
 public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
            int flags, String installerPackageName, Uri verificationURI,
            ManifestDigest manifestDigest) {
		/*
		检查客户端进程是否具有安装Package权限。
		*/
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);

        final int uid = Binder.getCallingUid();

        final int filteredFlags;

        if (uid == Process.SHELL_UID || uid == 0) {
            if (DEBUG_INSTALL) {
                Slog.v(TAG, "Install from ADB");
            }
            filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
        } else {
            filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
        }
		/*
		创建一个Message对象,code = INIT_COPY,将该消息发送给PackageManagerService中的mHandler
		创建一个InstallParams对象
		*/
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
                verificationURI, manifestDigest);
        mHandler.sendMessage(msg);
    }

mHandler中的handleMessage中调用doHandleMessage方法:

1. INIT_COPY

void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
                    // If a bind was already initiated we dont really
                    // need to do anything. The pending install
                    // will be processed later on.
                    if (!mBound) {
                        // If this is the only one pending we might
                        // have to bind to the service again.
						/*
						启动DefaultContainerService服务,通过bindService来启动
						*/
                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            return;
                        } else {
                            // Once we bind to the service, the first
                            // pending request will be processed.
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);
                        // Already bound to the service. Just make
                        // sure we trigger off processing the first request.
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }
		/*---------省略---------*/
}

connectToService方法:

private boolean connectToService() {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                    " DefaultContainerService");
            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
			/*
			mDefContainerConn:
			*/
            if (mContext.bindService(service, mDefContainerConn,
                    Context.BIND_AUTO_CREATE)) {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                mBound = true;
                return true;
            }
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return false;
        }
mDefContainerConn: DefaultContainerConnection,如果链接成功内部发送MCS_BOUND

final private DefaultContainerConnection mDefContainerConn =
            new DefaultContainerConnection();
    class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            IMediaContainerService imcs =
                IMediaContainerService.Stub.asInterface(service);
			/*
			连接成功后,发送MCS_BOUND给mHandler
			*/
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
        }

        public void onServiceDisconnected(ComponentName name) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
        }
    };


2.MCS_BOUND

/*省略*/
	               case MCS_BOUND: {
                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
                    if (msg.obj != null) {
                        mContainerService = (IMediaContainerService) msg.obj;
                    }
                    if (mContainerService == null) {
						//服务没启动,不能安装
                        // Something seriously wrong. Bail out
                        Slog.e(TAG, "Cannot bind to media container service");
                        for (HandlerParams params : mPendingInstalls) {
                            mPendingInstalls.remove(0);
                            // Indicate service bind error
                            params.serviceError();
                        }
                        mPendingInstalls.clear();
                    } else if (mPendingInstalls.size() > 0) {
                        HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            if (params.startCopy()) {
                                // We are done...  look for more work or to
                                // go idle.
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                // Delete pending install
                                if (mPendingInstalls.size() > 0) {
									/*
									移除队头
									*/
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
										/*
										安装完成,与服务断开,通过发送MCS_UNBIND断开与Service
										*/
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        // Unbind after a little delay, to avoid
                                        // continual thrashing.
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    // There are more pending requests in queue.
                                    // Just post MCS_BOUND message to trigger processing
                                    // of next pending install.
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next woek");
                                    mHandler.sendEmptyMessage(MCS_BOUND);
                                }
                            }
                        }
                    } else {
                        // Should never happen ideally.
                        Slog.w(TAG, "Empty queue");
                    }
                    break;
                }
/*省略*/
1> 抽象类HandlerParams,及其子类:

InstallParams: 处理APK的安装;

MoveParams:处理APK用于处理某个已经安装的APK"搬家";

MeasureParams:用于查询某个已安装的APK占存储空间的大小;

2> 抽象类InstallArgs及其子类:

FileInstallArgs: 针对的是安装在内部存储的APK

SdInstallArgs: 针对的是安装在sd卡的APK

final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");

                if (++mRetries > MAX_RETRIES) {
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();
            return res;
        }

 public void handleStartCopy() throws RemoteException {
            int ret = PackageManager.INSTALL_SUCCEEDED;
			/*
			根据adb install 的参数,判断安装位置
			*/
            final boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
            PackageInfoLite pkgLite = null;

            if (onInt && onSd) {
                // Check if both bits are set.
				/*
				不能同时安装在内部存储和SD卡
				*/
                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else if (fwdLocked && onSd) {
                // Check for forward locked apps
                Slog.w(TAG, "Cannot install fwd locked apps on sdcard");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                final long lowThreshold;
				/*
				获取DeviceStorageMonitorService的binder客户端
				*/
                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
                        .getService(DeviceStorageMonitorService.SERVICE);
                if (dsm == null) {
                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
                    lowThreshold = 0L;
                } else {
					/*查询最小余量*/
                    lowThreshold = dsm.getMemoryLowThreshold();
                }

                // Remote call to find out default install location
                try {
					/*给DefaultContainerService URI读权限*/
                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
					/*获取轻量级的PackageInfoLite对象*/
                    pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags,
                            lowThreshold);
                } finally {
					/*收回权限*/
                    mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
				/*推荐安装路径*/
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_APK;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_URI;
                } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
                    ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
                } else {
                    // Override with defaults if needed.
					/*apk检查*/
                    loc = installLocationPolicy(pkgLite, flags);
                    if (!onSd && !onInt) {
                        // Override install location with flags
                        if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                            // Set the flag to install on external media.
                            flags |= PackageManager.INSTALL_EXTERNAL;
                            flags &= ~PackageManager.INSTALL_INTERNAL;
                        } else {
                            // Make sure the flag for installing on external
                            // media is unset
                            flags |= PackageManager.INSTALL_INTERNAL;
                            flags &= ~PackageManager.INSTALL_EXTERNAL;
                        }
                    }
                }
            }
			/*创建一个安装参数对象,内部安装该对象实际为FileInstallArgs*/
            final InstallArgs args = createInstallArgs(this);
            mArgs = args;

            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                /*
                 * Determine if we have any installed package verifiers. If we
                 * do, then we'll defer to them to verify the packages.
                 */
                final int requiredUid = mRequiredVerifierPackage == null ? -1
                        : getPackageUid(mRequiredVerifierPackage);
				/*verification处理*/
                if (requiredUid != -1 && isVerificationEnabled()) {
                    final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
                    verification.setDataAndType(packageURI, PACKAGE_MIME_TYPE);
                    verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
                            PackageManager.GET_DISABLED_COMPONENTS);

                    if (DEBUG_VERIFY) {
                        Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
                                + verification.toString() + " with " + pkgLite.verifiers.length
                                + " optional verifiers");
                    }

                    final int verificationId = mPendingVerificationToken++;

                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);

                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
                            installerPackageName);

                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, flags);

                    if (verificationURI != null) {
                        verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI,
                                verificationURI);
                    }

                    final PackageVerificationState verificationState = new PackageVerificationState(
                            requiredUid, args);

                    mPendingVerification.append(verificationId, verificationState);

                    final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
                            receivers, verificationState);

                    /*
                     * If any sufficient verifiers were listed in the package
                     * manifest, attempt to ask them.
                     */
                    if (sufficientVerifiers != null) {
                        final int N = sufficientVerifiers.size();
                        if (N == 0) {
                            Slog.i(TAG, "Additional verifiers required, but none installed.");
                            ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
                        } else {
                            for (int i = 0; i < N; i++) {
                                final ComponentName verifierComponent = sufficientVerifiers.get(i);

                                final Intent sufficientIntent = new Intent(verification);
                                sufficientIntent.setComponent(verifierComponent);

                                mContext.sendBroadcast(sufficientIntent);
                            }
                        }
                    }

                    final ComponentName requiredVerifierComponent = matchComponentForVerifier(
                            mRequiredVerifierPackage, receivers);
                    if (ret == PackageManager.INSTALL_SUCCEEDED
                            && mRequiredVerifierPackage != null) {
                        /*
                         * Send the intent to the required verification agent,
                         * but only start the verification timeout after the
                         * target BroadcastReceivers have run.
                         */
                        verification.setComponent(requiredVerifierComponent);
                        mContext.sendOrderedBroadcast(verification,
                                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
                                new BroadcastReceiver() {
                                    @Override
                                    public void onReceive(Context context, Intent intent) {
                                        final Message msg = mHandler
                                                .obtainMessage(CHECK_PENDING_VERIFICATION);
                                        msg.arg1 = verificationId;
                                        mHandler.sendMessageDelayed(msg, getVerificationTimeout());
                                    }
                                }, null, 0, null, null);

                        /*
                         * We don't want the copy to proceed until verification
                         * succeeds, so null out this field.
                         */
                        mArgs = null;
                    }
                } else {
                    /*
                     * No package verification is enabled, so immediately start
                     * the remote call to initiate copy using temporary file.
                     */
					/*调用FileInstallArgs的copyApk方法*/
                    ret = args.copyApk(mContainerService, true);
                }
            }

            mRet = ret;
        }

以上代码知识点:

1> 调用DefaultContainerService的getMinimalPackageInfo函数,得到一个PackageInfoLite对象,该对象是一个轻量级的用于描述APK的机构,这个不同于PackageLite,这两个中的成员非常相似,只是PackageInfoLite实现了Parcelable接口,因此,该类可通过binder在进程间通信,该函数中可以获得一个合理的recommandedInstallLocation的值,该值表示该APK推荐的安装路径;

2> 调用installLocationPolicy检查推荐安装路径。(系统Package不能安装在SD卡上)

3> createInstallArgs根据安装位置创建不同的InstallArgs,如果是内部安装,为FileInstallArgs对象,否则为SdInstallArgs对象

4> 正式安装前需要对APK进行必要的检查

5> 调用InstallArgs的copyAPK方法

<---待续--->




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值