Activity启动流程

从 startActivity 入手

    // ContextImpl
    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        // 定位到这一行
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
    
复制代码
  1. final @NonNull ActivityThread mMainThread;
  2. getInstrumentation() -> public Instrumentation getInstrumentation {return mInstrumentation}
  3. 通过Instrumentation的实例去具体的执行Activity的启动
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        // 将传入的参数强转为: ApplicationThread  
        // 一个App进程存在一个MainThread, 对应一个ApplicationThread用于与AMS跨进程交互
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            // 重点的一行代码
            // 通过获取AMS的代理实例, 通过这个实例执行startActivity
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
复制代码
  1. ActivityManagerNative:
    • public abstract class ActivityManagerNative extends Binder implements IActivityManager
    • [ActivityManagerNative其实就是系统手写的: 类似于自动生成时AIDL接口中的Stub和Proxy的类, 用于处理AMS的跨进程通信]
    • PS: 系统源码里的XXXNative类就类似于我们创建AIDL接口时Build生成的与接口同名的包含Stub和Proxy的辅助类
  2. getDefault:
    • static public IActivityManager getDefault() { return gDefault.get(); }
    • 通过调用了gDefault的get()方法我们看看gDefault究竟是什么
  3. execStartActivity方法将传入的参数转交给AMS, 通过startActivity(...)去执行启动Activity的操作
// 可以看到gDefault.get()获取到的就是IActivityManager的单例
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            // 通过ServiceManager.getService方法获取到了全局IActivityManager的Binder对象
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            // 将远程的Binder通过asInterface转为接口供我们客户端跨进程去调用
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
    
/**
 * Singleton helper class for lazily initialization.
 *
 * Modeled after frameworks/base/include/utils/Singleton.h
 *
 * @hide
 */
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

// 看看ServiceManager是怎么拿到AMS实例的
public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    // getService()是通过getIServiceManager().getService(name)拿到了Activity的实例
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(getIServiceManager().getService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
    
    // 同一个套路, Binder.allowBlocking(BinderInternal.getContextObject())拿到了IServiceManager的实现
    // 只不过根进去发现是Native层的代码, 作罢
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

复制代码
  1. 通过gDefault.get()拿到远程的AMS的Binder对象
  2. 再通过ActivityManagerNative实现好的asInterface()方法 将这个Binder转为IActivityManager接口对象, 也就是AMS在客户端的代理对象
  3. 通过AMS对象去调用startActivity(...);方法
    • public int startActivity( IBinder whoThread, String callingPackage, Intent intent, String resolvedType, Bundle bOptions)
    • 原来我们调用startActivity(intent)方法, 到最后会衍生出这么多的参数
  4. 虽然没有看到 ServiceManager 是怎么拿到 ActivityManagerSerive 实例的,
    • 但我们知道 IActivityManager 的实现类就是 ActivityManagerSerive, 我们直接进去看看即可
    • public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback
从 ActivityManagerNative.getDefault().startActivity() 开始, 我们 Activity 的启动流程从App进程切换到了 ActivityManagerService 所在的进程
@Override
public int startActivity(IBinder whoThread, String callingPackage,
        Intent intent, String resolvedType, Bundle bOptions) {
    checkCaller();
    int callingUser = UserHandle.getCallingUserId();
    TaskRecord tr;
    IApplicationThread appThread;
    synchronized (ActivityManagerService.this) {
        tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
        if (tr == null) {
            throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
        }
        appThread = IApplicationThread.Stub.asInterface(whoThread);
        if (appThread == null) {
            throw new IllegalArgumentException("Bad app thread " + appThread);
        }
    }
    // 又交给了ActivityStarter的startActivityMayWait去处理
    return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
            resolvedType, null, null, null, null, 0, 0, null, null,
            null, bOptions, false, callingUser, null, tr, "AppTaskImpl");
            
}

    // 进入ActivityStarter这个类看看
    final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, WaitResult outResult,
            Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask, String reason) {
            ......// 省略了无数行代码
            // 又交给了startActivityLocked()去处理
            int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                    aInfo, rInfo, voiceSession, voiceInteractor,
                    resultTo, resultWho, requestCode, callingPid,
                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                    options, ignoreTargetSecurity, componentSpecified, outRecord, container,
                    inTask, reason);
            return res;
        }
    }

    int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask, String reason) {
        // 这里又回调了一个方法数超多的startActivity
        mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
                aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
                callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
                container, inTask);
        return mLastStartActivityResult;
    }

    /** 
     * DO NOT call this method directly. Use {@link #startActivityLocked} instead.
     * 这个startActivity主要处理了很多事情, 主要是检测Activity的信息, 判断是否有错误
     */
    private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask) {
        // 错误信息检测
        if (err == ActivityManager.START_SUCCESS) {
            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
                    + "} from uid " + callingUid);
        }
        ......
        // 权限检测
        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
                resultRecord, resultStack, options);
        ......
        // If permissions need a review before any of the app components can run, we
        // launch the review activity and pass a pending intent to start the activity
        // we are to launching now after the review is completed.
        if (mService.mPermissionReviewRequired && aInfo != null) {
            if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                    aInfo.packageName, userId)) {
                ......
            }
        }
        // activity信息的记录者
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, container, options, sourceRecord);
        if (outActivity != null) {
            outActivity[0] = r;
        }
        // 可以看到经过一系列的检测, 又回调了一个方法数比较少的startActivity
        return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
                options, inTask, outActivity);
    }
    /** 
     * 这个startActivity主要回调了startActivityUnchecked方法
     */
    private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {
        result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity);
        return result;
    }
    
    /** 
     * Note: This method should only be called from {@link startActivity}.
     * 上面已经检测过了, 所以这里方法命名为Unchecked
     * 这个方法极为重要
     */
    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {
        // 设置初始化的状态, 通过ActivityRecord r中记录的信息,去初始化状态, 其中配置了我们的LaunchMode/Flags
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession, voiceInteractor);
        .....
        // 这里根据启动模式判断是回调newIntent还是继续往下去执行
        final ActivityStack topStack = mSupervisor.mFocusedStack;
        final ActivityRecord topFocused = topStack.topActivity();
        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
        final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.realActivity.equals(mStartActivity.realActivity)
                && top.userId == mStartActivity.userId
                && top.app != null && top.app.thread != null
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || mLaunchSingleTop || mLaunchSingleTask);
         // 回调newIntent
        top.deliverNewIntentLocked(
                    mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
         ......
         // 1.调用了ActivityStack里的startActivityLocked, 将当前Activity切换到栈顶, 通过windowManager处理过场动画等等
         mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
                mOptions);
        // 2. 恢复栈顶Activity的焦点
        mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, mOptions);
    
        return START_SUCCESS;
    }
复制代码
  1. 调用了ActivityStack里的startActivityLocked
  2. 调用了ActivityStackSupervisor的
    • resumeFocusedStackTopActivityLocked ->
    • (ActivityStack)targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
    final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
        TaskRecord rTask = r.getTask();
        final int taskId = rTask.taskId;
        // 将我们要启动的Activity添加到最上面
        if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
            insertTaskAtTop(rTask, r);
        }
        // 这里处理了Activity启动的切换动画
        if (!isHomeOrRecentsStack() || numActivities() > 0) {
            if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                    "Prepare open transition: starting " + r);
            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
                mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
                mNoAnimActivities.add(r);
            } else {
                int transit = TRANSIT_ACTIVITY_OPEN;
                mWindowManager.prepareAppTransition(transit, keepCurTransition);
                mNoAnimActivities.remove(r);
            }
        } 
    }
    
    /**
     * Ensure that the top activity in the stack is resumed.
     * NOTE: It is not safe to call this method directly as it can cause an activity in a
     *       non-focused stack to be resumed.
     *       Use {@link ActivityStackSupervisor#resumeFocusedStackTopActivityLocked} to resume the
     *       right activity for the current system state.
     */
    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        // 调用了resumeTopActivityInnerLocked, 高能代码!!!!!
        result = resumeTopActivityInnerLocked(prev, options);
        return result;
    }
复制代码

上面的准备工作处理完成了, resumeTopActivityInnerLocked 中的代码就正式开始回调 Activity 的生命周期了

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        // 1 将之前的 Activity Pause
        if (mResumedActivity != null) {
            pausing |= startPausingLocked(userLeaving, false, next, false);
        }
        // pasuing 成功就直接 returnif (pausing && !resumeWhilePausing) {
            return true;
        } 
        if (next.app != null && next.app.thread != null) {
            // 2. 当前要启动的 Activity 存在, 则直接调用它的onResume
            next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
                mService.isNextTransitionForward(), resumeAnimOptions);
        } else {
            // 3. 当前要启动 Activity 不存在则调用这个方法去创建 Activity 调用 onCreate
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }
        return ...
    }
    final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
         // 1.1 处理上一个 Activity 的 onPause
        prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
                        userLeaving, prev.configChangeFlags, pauseImmediately);
        // 1.2 完成上一个 Activity 的 onPasue 时调用该方法处理 onPause 之后的事务
        completePauseLocked(false, resuming);
    }
    
    private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
        // 1.3 
        mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);
    }
    
    boolean resumeFocusedStackTopActivityLocked(
            ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
        // 1.4 
        return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
    }
    
    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        // 1.5绕了一圈又回到了 resumeTopActivityInnerLocked , 不过这次不会执行onPause()了
        result = resumeTopActivityInnerLocked(prev, options);
        return result;
    }

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        // 1 将之前的 Activity Pause
        if (mResumedActivity != null) {
            pausing |= startPausingLocked(userLeaving, false, next, false);
        }
        // pasuing成功就直接returnif (pausing && !resumeWhilePausing) {
            return true;
        } 
        if (next.app != null && next.app.thread != null) {
            // 2. 当前要启动的Activity存在, 则直接调用它的onResume
            next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
                mService.isNextTransitionForward(), resumeAnimOptions);
        } else {
            // 3. 当前要启动 Activity 不存在则调用这个方法去创建 Activity 调用 onCreate
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }
        return ...
    }
    
     void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        // 3.1 调用了realStartActivityLocked
        realStartActivityLocked(r, app, andResume, checkConfig);
      
    }
    
     final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
         // 3.2 scheduleLaunchActivity 这个方法执行了 Activity 生命周期的回调, 从 onCreate 到 onStart
         app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info,
                    // TODO: Have this take the merged configuration instead of separate global and
                    // override configs.
                    mergedConfiguration.getGlobalConfiguration(),
                    mergedConfiguration.getOverrideConfiguration(), r.compat,
                    r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                    r.persistentState, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profilerInfo);
    }
复制代码

总结一波: resumeTopActivityInnerLocked的作用

  1. 第一次执行resumeTopActivityInnerLocked
    • 拿到当前最上层的Activity, 调用它的onPasue方法 pausing |= startPausingLocked(userLeaving, false, next, false);
    • startPausingLocked()这个方法中有一行 prev.app.thread.schedulePauseActivity();
    • 看到app.thread它为ApplicationThread(为MainThread的内部类): IApplicationThread的实现者, 很显然有一个ApplicationThreadNative用与处理跨进程
    • 回调到我们App的进程中的schedulePauseActivity方法去回调当前显示Activitiy的onPause方法
    • 调用completePauseLocked()方法取处理onPasue之后的事务, 发现它最终又调到了resumeTopActivityInnerLocked()方法中
  2. 第二次执行resumeTopActivityInnerLocked
    • 这次pevActivity已经Pasue了, 所以代码会往下走
    • 判断当前要启动的Activity是否已经被创建过了
    • 若被创建了则调用next.app.thread.scheduleResumeActivity去回调onResume方法
    • 若未被创建则调用startSpecificActivityLocked方法, 最终会回调 app.thread.scheduleLaunchActivity 去启动一个新的Activity

从 app.thread.scheduleLaunchActivity 开始 Activity 的启动就从 AMS 所在的进程回溯到我们 App 所在的进程中了

在 App 进程具体的执行 Activity 的启动

  1. 看看 scheduleLaunchActivity 做了什么
    /**
     * ActivityThread.ApplicationThread.scheduleLaunchActivity
     */
    public final void scheduleLaunchActivity(...) {
        // 发送了一个 Handler 切换到主线程去执行 Activity 的启动
        ActivityClientRecord r = new ActivityClientRecord();
        // 最终会调用 performLaunchActivity
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

    /**
     * ActivityThread.performLaunchActivity
     */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        // 2.1. 获取 ClassLoader 加载类, 再通过反射构建 Activity 实例
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ...
        }
        // 2.2 获取 Application 实例, 绑定相关参数
        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            activity.attach(..., app, ...);
        }
        // 2.3 回调 Activity 的 onCreate 方法
         if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
        // 2.4 回调 onStart 
        if (!r.activity.mFinished) {
            activity.performStart();
            r.stopped = false;
        }
        ...
        return activity;
    }
复制代码

我们可以看到当 AMS 远程调用我们 ApplicationThread 的 scheduleLaunchActivity 时

  1. 通过 Handler 切换到主线程回调 performLaunchActivity 方法
  2. 反射构造Activity实例
    • 通过 LoadedApk.makeApplication 构造 Application 实例, 一个进程只有一个 Application 实例
    • 给 Activity 对象 attach 相关参数
    • 回调 onCreate 方法
    • 回调 onStart 方法

至此 Activity 的实例就已经创建出来了, 并且回调了我们最熟悉的 onCreate()、onStart()

onResume() 方法的执行过程与onCreate和onStart套路一致, 这里就不分析了

启动流程图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值