Android 8.1.0 源码探究之 - 为啥给设备设置锁屏密码以后,重启设备会看到屏幕会变得比关机之前亮(解锁后亮度恢复为重启之前的亮度)

本文详细解析了Android设备重启后解锁界面亮度增加的原因,通过跟踪从应用层到系统层的代码逻辑,揭示了屏幕亮度自动提升的技术细节。

        今天测试给提了一个issue:刷机 - 设置设备的锁屏密码(pattern)- 重启设备,设备重启完成之后,首先看到的当然是解锁界面,会看到解锁界面的亮度比重启之前在 Settings 中设置的亮度亮了不少(即使在系统的 Settings 中设置了系统亮度是 0 ,重启之后首先看到的解锁界面也是很亮,解锁之后,设备屏幕的亮度恢复为重启之前亮度)。测试认为这是有问题的,把这个问题扔了出来。

        首先,作为开发的直觉来说,我觉得这其实应该不是一个issue,而是Google有意为之,就像是平时我们给商户展示支付宝二维码的时候,会发现支付宝的二维码界面亮度亮了不少,等到扫码结束退到其他界面的时候,会看到亮度恢复为之前。但是空说没有什么证据,得看代码到底是怎么处理这个地方的逻辑的。

        首先找到这个重启之后的输入密码界面是啥,通过打印堆栈信息,看到这个界面是:

realActivity=com.android.settings/.CryptKeeper

        恩恩,这个界面是 Settings 中的某一个 Activity 界面,具体位置是:

folder\vendor\rockchip\hxxx\gxx\apps\Settings\src\com\android\settings\CryptKeeper.java

 首先看一下这个 Activity 在初始化的时候做了什么,可以看到在它的 onCreate()里面没有做关于界面展示的逻辑,他把逻辑放到了onStart() 方法中。如下:

    /**
     * Note, we defer the state check and screen setup to onStart() because this will be
     * re-run if the user clicks the power button (sleeping/waking the screen), and this is
     * especially important if we were to lose the wakelock for any reason.
     */
    @Override
    public void onStart() {
        super.onStart();
        //关键代码
        setupUi();
    }

具体为啥把UI相关的逻辑放到 onStart() 方法,Google 工程师做了解释。那在 setupUi(); 中做了什么操作?如下:

    /**
     * Initializes the UI based on the current state of encryption.
     * This is idempotent - calling repeatedly will simply re-initialize the UI.
     */
    private void setupUi() {
        if (mEncryptionGoneBad || isDebugView(FORCE_VIEW_ERROR)) {
            setContentView(R.layout.crypt_keeper_progress);
            showFactoryReset(mCorrupt);
            return;
        }

        final String progress = SystemProperties.get("vold.encrypt_progress");
        if (!"".equals(progress) || isDebugView(FORCE_VIEW_PROGRESS)) {
            setContentView(R.layout.crypt_keeper_progress);
            encryptionProgressInit();
        } else if (mValidationComplete || isDebugView(FORCE_VIEW_PASSWORD)) {
            new AsyncTask<Void, Void, Void>() {
                int passwordType = StorageManager.CRYPT_TYPE_PASSWORD;
                String owner_info;
                boolean pattern_visible;
                boolean password_visible;

                @Override
                public Void doInBackground(Void... v) {
                    try {
                        final IStorageManager service = getStorageManager();
                        passwordType = service.getPasswordType();
                        owner_info = service.getField(StorageManager.OWNER_INFO_KEY);
                        pattern_visible = !("0".equals(service.getField(StorageManager.PATTERN_VISIBLE_KEY)));
                        password_visible = !("0".equals(service.getField(StorageManager.PASSWORD_VISIBLE_KEY)));
                    } catch (Exception e) {
                        Log.e(TAG, "Error calling mount service " + e);
                    }

                    return null;
                }

                @Override
                public void onPostExecute(java.lang.Void v) {
                    Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
                                  password_visible ? 1 : 0);

                    //code...
                    
                    //关键代码

                    passwordEntryInit();

                    //code...

                }
            }.execute();
        } else if (!mValidationRequested) {
            // We're supposed to be encrypted, but no validation has been done.
            new ValidationTask().execute((Void[]) null);
            mValidationRequested = true;
        }
    }

        可以看到当系统检查过错误之后,会创建一个 AsyncTask,并在doInBackground()中获取到当前设备密码的类型,在onPostExecute()中调用 passwordEntryInit()方法进行密码实体化界面的初始化。看一下 passwordEntryInit()做了什么操作:

     private void passwordEntryInit() {
        // Password/pin case
        // code...

        // Pattern case
        mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
        if (mLockPatternView != null) {
            mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
        }

        // Disable the Emergency call button if the device has no voice telephone capability
        // code...

        // We want to keep the screen on while waiting for input. In minimal boot mode, the device
        // is completely non-functional, and we want the user to notice the device and enter a
        // password.
        if (mWakeLock == null) {
            Log.d(TAG, "Acquiring wakelock.");
            final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            if (pm != null) {
                mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                //关键代码!
                mWakeLock.acquire();
                // Keep awake for 10 minutes - if the user hasn't been alerted by then
                // best not to just drain their battery
                mReleaseWakeLockCountdown = 96; // 96 * 5 secs per click + 120 secs before we show this = 600
            }
        }

        // Make sure that the IME is shown when everything becomes ready.
        // code...

        // Notify the user in 120 seconds that we are waiting for him to enter the password.
        mHandler.removeMessages(MESSAGE_NOTIFY);
        mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY, 120 * 1000);

        // Dismiss secure & non-secure keyguards while this screen is showing.
        // code...
    }

        这个方法很长,但是看起来其实一目了然,系统做了判断,对用户可能设置密码的三种可能做了初始化。我们关心一个方法就行:mWakeLock.acquire(); 我们可以看到这里是调用了 PowerManager 的 acquire();方法,这个方法是为了获取唤醒锁定。另外还有注释如下:

We want to keep the screen on while waiting for input. In minimal boot mode, the device is completely non-functional, and we want the user to notice the device and enter a password.

 

       这里可以解释为,当设备重启完成之后,设备要保持 Screen on等待用户输入,并且保持屏幕是亮着的,用以提醒用户赶快输入密码解锁。

        继续跟代码,在目录:

folder\XX\frameworks\base\core\java\android\os\

下有一个 PowerManager.java 类,刚刚调用的 acquire()就是调用的这个类中的方法。在这个类中可以看到:

        /**
         * Acquires the wake lock.
         * <p>
         * Ensures that the device is on at the level requested when
         * the wake lock was created.
         * </p>
         */
        public void acquire() {
            synchronized (mToken) {
                //关键代码!
                acquireLocked();
            }
        }

       

        private void acquireLocked() {
	    mInternalCount++;
            mExternalCount++;
            if (!mRefCounted || mInternalCount == 1) {
                for(int i=0;i<packageList.size();i++){
                    String pckname = packageList.get(i);
                    //Slog.d("lvjinhua","--------------------pckname111="+pckname+",mPackageName="+mPackageName);
                    if(mPackageName.equals(pckname) || mTag.equals(pckname)){
                        return;
                    }
                }

                // Do this even if the wake lock is already thought to be held (mHeld == true)
                // because non-reference counted wake locks are not always properly released.
                // For example, the keyguard's wake lock might be forcibly released by the
                // power manager without the keyguard knowing.  A subsequent call to acquire
                // should immediately acquire the wake lock once again despite never having
                // been explicitly released by the keyguard.
                mHandler.removeCallbacks(mReleaser);
                Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
                try {
                    //关键代码!
                    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                            mHistoryTag);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mHeld = true;
            }
        }

        可以看到这里通过 IPowerManager 这个类型的代理对象  mService ,调用了 PowerManagerService 的acquireWakeLock();方法,很明显,这里是AIDL,最终执行的逻辑是Server 端的逻辑。

        跟代码,找到 PowerManagerService.java 路径如下:

folder\XX\frameworks\base\services\core\java\com\android\server\power\

最终执行的 acquireWakeLock();方法是调用的 PowerManagerService.java 中的 acquireWakeLock();查看这个方法:

        @Override // Binder call
        public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
                WorkSource ws, String historyTag) {
            if (lock == null) {
                throw new IllegalArgumentException("lock must not be null");
            }
            if (packageName == null) {
                throw new IllegalArgumentException("packageName must not be null");
            }
            PowerManager.validateWakeLockParameters(flags, tag);
     
            try {
                //关键代码!
                acquireWakeLockInternal(lock, flags, tag, packageName, ws, historyTag, uid, pid);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        可以看到这个关键代码调用了封装之后的方法  acquireWakeLockInternal(),即调用了获取唤醒锁的内部封装的逻辑,代码比较多,这里只展示关键代码:

 

    private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
            WorkSource ws, String historyTag, int uid, int pid) {
        synchronized (mLock) {
            // code...

            WakeLock wakeLock;
            int index = findWakeLockIndexLocked(lock);
            boolean notifyAcquire;
            if (index >= 0) {
                // code...
            } else {
                // code...
            }

            applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
            mDirty |= DIRTY_WAKE_LOCKS;
            
            // 关键代码!
            updatePowerStateLocked();
            // code...
        }
    }

 

可以看到这里又通过调用方法 updatePowerStateLocked()调用了更新电源状态锁。这个方法做了什么呢,如下:

 

    /**
     * Updates the global power state based on dirty bits recorded in mDirty.
     *
     * This is the main function that performs power state transitions.
     * We centralize them here so that we can recompute the power state completely
     * each time something important changes, and ensure that we do it the same
     * way each time.  The point is to gather all of the transition logic here.
     */
    private void updatePowerStateLocked() {
        // code check system status
        // code...

        Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
        try {
            // Phase 0: Basic state updates.
            updateIsPoweredLocked(mDirty);
            updateStayOnLocked(mDirty);
            // 关键代码!!
            updateScreenBrightnessBoostLocked(mDirty);

            // Phase 1: Update wakefulness.
            // Loop because the wake lock and user activity computations are influenced
            // by changes in wakefulness.
            final long now = SystemClock.uptimeMillis();
            int dirtyPhase2 = 0;
            for (;;) {
                int dirtyPhase1 = mDirty;
                dirtyPhase2 |= dirtyPhase1;
                mDirty = 0;

                updateWakeLockSummaryLocked(dirtyPhase1);
                updateUserActivitySummaryLocked(now, dirtyPhase1);
                if (!updateWakefulnessLocked(dirtyPhase1)) {
                    break;
                }
            }

            // Phase 2: Update display power state.
            boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);

            // Phase 3: Update dream state (depends on display ready signal).
            updateDreamLocked(dirtyPhase2, displayBecameReady);

            // Phase 4: Send notifications, if needed.
            finishWakefulnessChangeIfNeededLocked();

            // Phase 5: Update suspend blocker.
            // Because we might release the last suspend blocker here, we need to make sure
            // we finished everything else first!
            updateSuspendBlockerLocked();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_POWER);
        }
    }

        Google 工程师对这个方法的注释很明白了:方法中,根据mDirty中记录的数据更新全局电源状态。并且这是执行电源状态转换的主要功能。 我们将它们集中在这里,以便我们可以在每次重要变化时完全重新计算功率状态,并确保每次都以相同的方式进行。 关键是要在这里收集所有过渡逻辑。在这个方法中,分了5个阶段来更新处理全局电源状态 ,具体每个阶段做了什么我们就不一一看了,这不是我们追踪代码的目的,感兴趣的话,大家可以自己跟一下各个方法做了什么。我们只要关注第 0 个阶段和第 2 个阶段就可以了。

        看第 0 个阶段中的标注出来的关键代码:updateScreenBrightnessBoostLocked(mDirty);  如下:

    private void updateScreenBrightnessBoostLocked(int dirty) {
        if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) {
            if (mScreenBrightnessBoostInProgress) {
                final long now = SystemClock.uptimeMillis();
                mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
                    final long boostTimeout = mLastScreenBrightnessBoostTime +
                            SCREEN_BRIGHTNESS_BOOST_TIMEOUT;
                    if (boostTimeout > now) {
                        Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtTime(msg, boostTimeout);
                        return;
                    }
                }
                mScreenBrightnessBoostInProgress = false;
                mNotifier.onScreenBrightnessBoostChanged();
                userActivityNoUpdateLocked(now,
                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
            }
        }
    }

        这个方法中做的事情很简单,就是监测并处理屏幕亮度增加超时情况,如果在默认的时间内,屏幕的亮度一直没有增加,会通过mHandler 发送一个“屏幕亮度增加超时” 的广播出去。

        看第 2 个阶段中的标注出来的关键代码:updateDisplayPowerStateLocked(dirtyPhase2);  如下:

    /**
     * Updates the display power state asynchronously.
     * When the update is finished, mDisplayReady will be set to true.  The display
     * controller posts a message to tell us when the actual display power state
     * has been updated so we come back here to double-check and finish up.
     *
     * This function recalculates the display power state each time.
     *
     * @return True if the display became ready.
     */
    private boolean updateDisplayPowerStateLocked(int dirty) {
        final boolean oldDisplayReady = mDisplayReady;
        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
                | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
                | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
                DIRTY_QUIESCENT)) != 0) {

            // code...


            // Update display power request.
            mDisplayPowerRequest.screenBrightness = screenBrightness;
            mDisplayPowerRequest.screenAutoBrightnessAdjustment =
                    screenAutoBrightnessAdjustment;
            mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser;
            mDisplayPowerRequest.useAutoBrightness = autoBrightness;
            mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
            mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();

            updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest);

            // code...

            // 关键代码!!!
            mDisplayReady = mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
                    mRequestWaitForNegativeProximity);
            mRequestWaitForNegativeProximity = false;


            // code...

        }
        return mDisplayReady && !oldDisplayReady;
    }

       可以看到在代码中,把一些参数(其中就包含是否需要自动增加屏幕亮度 :boostScreenBrightness 这个参数)封装到 mDisplayPowerRequest 对象中,并通过调用 mDisplayManagerInternal 对象的 requestPowerState()方法将这些参数设置到 mDisplayManagerInternal 对象中。跟一下 mDisplayManagerInternal 对象的 requestPowerState()方法做了什么。

这个对象是 DisplayManagerInternal.java 的实例对象,查看代码发现这是一个抽象类,具体逻辑还是得看具体实现类(猜测实现类是 DMS,一会儿再跟),还是先看一下这个抽象类中抽象方法是怎么定义这个方法的吧:

    /**
     * Called by the power manager to request a new power state.
     * <p>
     * The display power controller makes a copy of the provided object and then
     * begins adjusting the power state to match what was requested.
     * </p>
     *
     * @param request The requested power state.
     * @param waitForNegativeProximity If true, issues a request to wait for
     * negative proximity before turning the screen back on, assuming the screen
     * was turned off by the proximity sensor.
     * @return True if display is ready, false if there are important changes that must
     * be made asynchronously (such as turning the screen on), in which case the caller
     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
     * then try the request again later until the state converges.
     */
    public abstract boolean requestPowerState(DisplayPowerRequest request,
            boolean waitForNegativeProximity);

        可以看到 DMS 对这个方法是这么描述的:这个方法被 PowerManagerService 调用,并根据传递进来的封装之后的 request 对象更新状态。(不太想继续往下跟了。。)(刚刚项目上有事情需要做,先暂停一下,等下午有空了再继续跟)

        抽空跟一下。。

        上面说到这个地方是在 DMS,也就是 DisplayManagerService.java 中调用的 requestPowerState()方法。那看一下这个DisplayManagerService.java 类,目录:

folder\XX\frameworks\base\services\core\java\com\android\server\display\DisplayManagerService.java

        看到在这个类中, requestPowerState()方法:

        @Override
        public boolean requestPowerState(DisplayPowerRequest request,
                boolean waitForNegativeProximity) {
            return mDisplayPowerController.requestPowerState(request,
                    waitForNegativeProximity);
        }

        其实是调用了 DisplayPowerController 代理对象 mDisplayPowerController 的 requestPowerState()方法。同级目录下找到 DisplayPowerController.java 这个类,查看这个 requestPowerState()方法:

    /**
     * Requests a new power state.
     * The controller makes a copy of the provided object and then
     * begins adjusting the power state to match what was requested.
     *
     * @param request The requested power state.
     * @param waitForNegativeProximity If true, issues a request to wait for
     * negative proximity before turning the screen back on, assuming the screen
     * was turned off by the proximity sensor.
     * @return True if display is ready, false if there are important changes that must
     * be made asynchronously (such as turning the screen on), in which case the caller
     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
     * then try the request again later until the state converges.
     */
    public boolean requestPowerState(DisplayPowerRequest request,
            boolean waitForNegativeProximity) {
        if (DEBUG) {
            Slog.d(TAG, "requestPowerState: "
                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
        }

        synchronized (mLock) {
            boolean changed = false;

            if (waitForNegativeProximity
                    && !mPendingWaitForNegativeProximityLocked) {
                mPendingWaitForNegativeProximityLocked = true;
                changed = true;
            }

            if (mPendingRequestLocked == null) {
                mPendingRequestLocked = new DisplayPowerRequest(request);
                changed = true;
            } else if (!mPendingRequestLocked.equals(request)) {
                mPendingRequestLocked.copyFrom(request);
                changed = true;
            }

            if (changed) {
                mDisplayReadyLocked = false;
            }

            if (changed && !mPendingRequestChangedLocked) {
                mPendingRequestChangedLocked = true;
                sendUpdatePowerStateLocked();
            }

            return mDisplayReadyLocked;
        }
    }

        可以看到这个方法的描述:

        请求新的 Power 状态。 控制器复制所提供的 request 对象,然后开始调整设备的 Power 状态以匹配请求的内容。

        以上就是给设备设置了锁屏密码并开机之后,设备首次展示给用户解锁界面时,设备屏幕显示以及 Power 状态配置的大概流程。

 

   基于以上流程跟踪,当调用 updateDisplayPowerStateLocked()时,PMS 会封装一个 DisplayPowerRequest  对象传递给 PMS,最终这个 DisplayPowerRequest  对象会被传递给 DMS,DMS 收到这个更新请求的时候,会按照  Request  对象中携带的数据进行状态以及 Power 的更新。

   需要注意一点,DisplayPowerRequest  对象有一个boolean类型德尔属性:boostScreenBrightness,这个属性就是导致屏幕亮度变亮的关键。

       

   
    // code...

    mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();

    // code...


    private boolean shouldBoostScreenBrightness() {
        return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress;
    }

        PMS 是怎么给boolean类型的  mIsVrModeEnabled 和 mScreenBrightnessBoostInProgress 进行赋值的呢?

对于变量 mIsVrModeEnabled  来说:True if we are currently in VR Mode.(所以这里一般为 false

对于变量 mScreenBrightnessBoostInProgress  来说,当 设置屏幕亮度超时的时候,会置为 false,当调用内部逻辑提升屏幕亮度的时候,会置为 true。(变量变化如下两个方法所示

    private void updateScreenBrightnessBoostLocked(int dirty) {
        if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) {
            if (mScreenBrightnessBoostInProgress) {
                final long now = SystemClock.uptimeMillis();
                mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                if (mLastScreenBrightnessBoostTime > mLastSleepTime) {
                    final long boostTimeout = mLastScreenBrightnessBoostTime +
                            SCREEN_BRIGHTNESS_BOOST_TIMEOUT;
                    if (boostTimeout > now) {
                        Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT);
                        msg.setAsynchronous(true);
                        mHandler.sendMessageAtTime(msg, boostTimeout);
                        return;
                    }
                }
                //关键代码!!
                mScreenBrightnessBoostInProgress = false;
                mNotifier.onScreenBrightnessBoostChanged();
                userActivityNoUpdateLocked(now,
                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
            }
        }
    }
   private void boostScreenBrightnessInternal(long eventTime, int uid) {
        synchronized (mLock) {
            if (!mSystemReady || mWakefulness == WAKEFULNESS_ASLEEP
                    || eventTime < mLastScreenBrightnessBoostTime) {
                return;
            }

            Slog.i(TAG, "Brightness boost activated (uid " + uid +")...");
            mLastScreenBrightnessBoostTime = eventTime;
            if (!mScreenBrightnessBoostInProgress) {
                // 关键代码!!
                mScreenBrightnessBoostInProgress = true;
                mNotifier.onScreenBrightnessBoostChanged();
            }
            mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST;

            userActivityNoUpdateLocked(eventTime,
                    PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
            updatePowerStateLocked();
        }
    }

        并且通过跟踪代码看到,首先会调用 updateScreenBrightnessBoostLocked()方法,再调用 boostScreenBrightnessInternal()方法。所以只要不出意外情况,mScreenBrightnessBoostInProgress   最终会被置为 true。

        所以,DisplayPowerRequest  对象中的 boolean 属性 boostScreenBrightness 不出意外情况,会是 true,这样当PMS 拿到 这个属性并传递给 DMS 的时候,DMS 就会去增加屏幕的亮度了。

        所以基于代码跟踪,测试人员提出的这个不应该属于是issue,不需要修改。

 

 

 

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值