ViewModel 源码分析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

ViewModel 是 Jetpack 组件之一,能力是维护数据保证在因配置更改导致的页面重建时 ViewModel 可以存活下来,另外使用 ViewModel 可以很方便的在一个 Activity 的多个 Fragment 中共享数据

创建对象

ViewModel 对象通过 ViewModelProvider 来创建,下面这样就可以拿到一个 TestViewModel 对象

class TestViewModel : ViewModel()

val viewModel = ViewModelProvider(this)[TestViewModel::class.java]

// androidx.lifecycle.ViewModelProvider
public open class ViewModelProvider(
    private val store: ViewModelStore,
    private val factory: Factory
) {

    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner))

    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
        owner.viewModelStore,
        factory
    )

}

ViewModelProvider 有三个构造方法但最终都会调用到 ViewModelStore 和 Factory 两个参数的构造方法 ViewModelStore 用来存储 ViewModel 对象 Factory 用来创建 ViewModel 对象,如果第二个参数 factory 不传则取默认的 Factory 默认的 Factory 只能创建没有构造参数的 ViewModel 如果有构造参数则需要自己实现 Factory 传入。这里调用的是一个 ViewModelStoreOwner 参数的构造方法 ViewModelStoreOwner 是一个接口只有一个方法 getViewModelStore 返回 ViewModelStore 对象 ComponentActivity 实现了这个接口所以这里可以传 this

	// androidx.activity.ComponentActivity
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
        	// 如果 mViewModelStore 为 null 则使用上一次重建保存的数据
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            // 如果还为 null 说明是正常的创建 Activity 的流程则创建 ViewModelStore 对象
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

再回来看一下 defaultFactory 的实现

// androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
public open class AndroidViewModelFactory(
        private val application: Application
    ) : NewInstanceFactory() {
        public companion object {
            internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
                if (owner is HasDefaultViewModelProviderFactory)
                	// 如果没有实现 HasDefaultViewModelProviderFactory 接口则使用 
                	// androidx.lifecycle.ViewModelProvider.NewInstanceFactory 的单例对象来反射创建对象
                    owner.defaultViewModelProviderFactory else instance

            internal const val DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"

            private var sInstance: AndroidViewModelFactory? = null

            public fun getInstance(application: Application): AndroidViewModelFactory {
            	// 单例
                if (sInstance == null) {
                    sInstance = AndroidViewModelFactory(application)
                }
                return sInstance!!
            }
        }
    }

defaultFactory 中会判断传入的 owner 对象是否实现了 HasDefaultViewModelProviderFactory 接口,HasDefaultViewModelProviderFactory 接口同样只有一个方法用于返回一个 ViewModelProvider.Factory 对象 ComponentActivity 实现了这个接口并且返回了一个 SavedStateViewModelFactory 对象,这样就两个参数都有了拿到了 ViewModelProvider 对象

    @NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
            		// 这里传入 Application 不为 null
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

创建 ViewModelProvider 对象之后调用 get 方法(这里用到了 Kotlin 的操作符重载)

    @MainThread
    public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
    	// 在方法中创建的本地类和匿名类获取对象会抛异常
        val canonicalName = modelClass.canonicalName
            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        // 生成一个默认 key   
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }

    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
    	// 根据 key 去 ViewModelStore 中取 ViewModel
        var viewModel = store[key]
        // 如果是 modelClass 类型的对象则直接返回
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel)
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        // 否则使用 factory 创建 ViewModel 对象
        // 从上面的分析指导这里的 factory 是 SavedStateViewModelFactory 实例
        viewModel = if (factory is KeyedFactory) {
        	// SavedStateViewModelFactory 实现了 KeyedFactory 接口所以会走这里
            factory.create(key, modelClass)
        } else {
            factory.create(modelClass)
        }
        // 存到 ViewModelStore 中下次直接返回
        // 如果直接调用此方法传入 key 不一样则还是创建新对象
        // 所以默认情况下(不传)在同一个 Activity 中的 Fragment 可以拿到同一个 ViewModel 对象共享数据
        store.put(key, viewModel)
        // 就拿到了 ViewModel 对象
        return viewModel
    }

下面来看一下 SavedStateViewModelFactory

public final class SavedStateViewModelFactory extends ViewModelProvider.KeyedFactory {

    private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
            SavedStateHandle.class};
    private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

    private final Application mApplication;
    private final ViewModelProvider.Factory mFactory;
    private final Bundle mDefaultArgs;
    private final Lifecycle mLifecycle;
    private final SavedStateRegistry mSavedStateRegistry;

    public SavedStateViewModelFactory(@Nullable  Application application,
            @NonNull SavedStateRegistryOwner owner) {
        this(application, owner, null);
    }

    public SavedStateViewModelFactory(@Nullable Application application,
            @NonNull SavedStateRegistryOwner owner,
            @Nullable Bundle defaultArgs) {
        // savedstate 相关    
        mSavedStateRegistry = owner.getSavedStateRegistry();
        mLifecycle = owner.getLifecycle();
        mDefaultArgs = defaultArgs;
        mApplication = application;
        // 如果 application 不为 null 创建一个 AndroidViewModelFactory 实例
        mFactory = application != null
                ? 
        //          ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                : ViewModelProvider.NewInstanceFactory.getInstance();
    }
   
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
    	// 判断是否继承自 AndroidViewModel
        boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
        Constructor<T> constructor;
        // 如果是 AndroidViewModel 子类并且构造时传入的 mApplication 不等于 null
        // AndroidViewModel 是以 Application 为作用域的 ViewModel 
        if (isAndroidViewModel && mApplication != null) {
        	// 找有没有第一个参数是 Application 类型,第二个参数 SavedStateHandle 类型的构造方法(savedstate 相关)
        	// 默认情况下是没有所以 constructor 等于 null
            constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
        } else {
        	// 如果不是 AndroidViewModel 子类是普通的 ViewModel
        	// 如果不使用 savedstate 也是没有的
            constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
        }
        if (constructor == null) {
        	// 使用 ViewModelProvider.AndroidViewModelFactory 创建
            return mFactory.create(modelClass);
        }

		// savedstate 相关
        SavedStateHandleController controller = SavedStateHandleController.create(
                mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
        try {
            T viewmodel;
            if (isAndroidViewModel && mApplication != null) {
                viewmodel = constructor.newInstance(mApplication, controller.getHandle());
            } else {
                viewmodel = constructor.newInstance(controller.getHandle());
            }
            viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
            return viewmodel;
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Failed to access " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("A " + modelClass + " cannot be instantiated.", e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("An exception happened in constructor of "
                    + modelClass, e.getCause());
        }
    }
    
    @SuppressWarnings("unchecked")
    private static <T> Constructor<T> findMatchingConstructor(Class<T> modelClass,
            Class<?>[] signature) {
        for (Constructor<?> constructor : modelClass.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            // 找签名一致的构造方法
            if (Arrays.equals(signature, parameterTypes)) {
                return (Constructor<T>) constructor;
            }
        }
        return null;
    }

}

按照开始的例子最后会使用全局单例的 ViewModelProvider.AndroidViewModelFactory 实例来创建对象,刚说了 ViewModelProvider.AndroidViewModelFactory 用来创建以 Application 为作用域的 ViewModel 这里并不是为什么还是会使用它呢,看一下它的代码

	// androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
    public open class AndroidViewModelFactory(
        private val application: Application
    ) : NewInstanceFactory() {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
                try {
                    // 如果是 AndroidViewModel 子类则调用其一个 Application 参数的构造方法
                    modelClass.getConstructor(Application::class.java).newInstance(application)
                } catch (e: NoSuchMethodException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: IllegalAccessException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InstantiationException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                } catch (e: InvocationTargetException) {
                    throw RuntimeException("Cannot create an instance of $modelClass", e)
                }
                // 否则调用父类的 create 方法去创建
            } else super.create(modelClass)
        }
}

原来是做了判断如果不是 AndroidViewModel 的子类直接调用了父类的 create 方法

	// androidx.lifecycle.ViewModelProvider.NewInstanceFactory
    public open class NewInstanceFactory : Factory {
        @Suppress("DocumentExceptions")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return try {
                modelClass.newInstance()
            } catch (e: InstantiationException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
                throw RuntimeException("Cannot create an instance of $modelClass", e)
            }
        }
    }

到这里终于可以得出结论 ViewModel 对象最终是通过 NewInstanceFactory 对象反射创建的

保存实例

在上面的分析中已经知道在创建 mViewModelStore 之前会先通过 getLastNonConfigurationInstance 方法去取,如果是系统配置更改引起的页面重建就可以拿到之前的 ViewModelStore 下面就从这个方法开始分析

	// 一个数据结构
    static final class NonConfigurationInstances {
    	// activity 是一个 Object 对象
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }	

	// android.app.Activity#getLastNonConfigurationInstance
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

如果 mLastNonConfigurationInstances 不为空则返回mLastNonConfigurationInstances.activity
而 activity 会在 retainNonConfigurationInstances 中被赋值

	// android.app.Activity#retainNonConfigurationInstances
    NonConfigurationInstances retainNonConfigurationInstances() {
    	// 这里的 activity 是 androidx.activity.ComponentActivity.NonConfigurationInstances 对象
    	// 它持有了 ViewModelStore 
    	// ViewModelStore 中保存着 ViewModel 
        Object activity = onRetainNonConfigurationInstance();
		// 代码省略 ...

        NonConfigurationInstances nci = new NonConfigurationInstances();
        // 将 androidx.activity.ComponentActivity.NonConfigurationInstances 对象
        // 赋值给 android.app.Activity.NonConfigurationInstances 对象的 activity
        nci.activity = activity;
		// 代码省略 ...
		// 返回 android.app.Activity.NonConfigurationInstances 对象
        return nci;
    }

	// android.app.Activity#onRetainNonConfigurationInstance
    public Object onRetainNonConfigurationInstance() {
        return null;
    }
	
	// androidx.activity.ComponentActivity#onRetainNonConfigurationInstance
	public final Object onRetainNonConfigurationInstance() {
        // 可以使用这个方法保存对象跟 ViewModel 一样不被销毁
        Object custom = onRetainCustomNonConfigurationInstance();
		// 当前 ViewModelStore 对象
        ViewModelStore viewModelStore = mViewModelStore;
        if (viewModelStore == null) {
            // 如果页面重建后并没有调用 androidx.activity.ComponentActivity#getViewModelStore 方法
            // 要尝试获取上一次重建页面保存的 ViewModelStore 对象
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            // 非重建页面        
            if (nc != null) {
                viewModelStore = nc.viewModelStore;
            }
        }
		
        if (viewModelStore == null && custom == null) {
            return null;
        }

		// 这里的数据结构是 androidx.activity.ComponentActivity.NonConfigurationInstances
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        // 保存 viewModelStore 对象
        nci.viewModelStore = viewModelStore;
        return nci;
    }

	// androidx.activity.ComponentActivity.NonConfigurationInstances
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

父类 Activity 的 retainNonConfigurationInstances 方法中会调用 onRetainNonConfigurationInstance 方法并且 onRetainNonConfigurationInstance 方法在 Activity 中返回了 null 但是 ComponentActivity 重写了它,在这里判断如果 ViewModelStore 对象不会空则创建一个 androidx.activity.ComponentActivity.NonConfigurationInstances 对象持有 viewModelStore 返回然后赋值给了 android.app.Activity.NonConfigurationInstances 的 activity 而 retainNonConfigurationInstances 方法会在页面销毁的时候调用,下面看一下页面重建的逻辑

	// android.app.ActivityThread#handleRelaunchActivity
    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        // 省略代码 ..
        ActivityClientRecord r = mActivities.get(tmp.token);

        // 省略代码 ..

        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
                
        // 省略代码 ..
    }

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {

        // 省略代码 ..    
		// 第四个参数是 true
		// 销毁时保存 ViewModelStore 到 ActivityClientRecord
        handleDestroyActivity(r, false, configChanges, true, reason);

        // 省略代码 ..
        
		// 重建时从 ActivityClientRecord 中将 ViewModelStore 恢复
        handleLaunchActivity(r, pendingActions, customIntent);
    }

    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        // getNonConfigInstance 为 true    
        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
        // 省略代码 ..
    }

    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        // 省略代码 ..
        if (getNonConfigInstance) {
            try {
            	// getNonConfigInstance 为 true
            	// 将上面分析的 retainNonConfigurationInstances 方法的返回值 
            	// android.app.Activity.NonConfigurationInstances 赋值给 lastNonConfigurationInstances
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException("Unable to retain activity "
                            + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                }
            }
        }
        // 省略代码 ..
    }

    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        // 省略代码 ..

        final Activity a = performLaunchActivity(r, customIntent);

        // 省略代码 ..

        return a;
    }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 省略代码 .. 
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            // 反射创建 Activity 对象
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
                    appContext.getAttributionSource());
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
        try {
            // 获取 Application 对象
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            // 省略代码 ..
            if (activity != null) {
                // 省略代码 ..
                // 传入 ViewModelStore 的持有者 r.lastNonConfigurationInstances
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken, r.shareableActivityToken);

                // 省略代码 ..
            }
            // 省略代码 ..
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            // 省略代码 ..
        }
        return activity;
    }

	// android.app.Activity#attach
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        // 省略代码 .. 
        // 将 ViewModelStore 持有者 lastNonConfigurationInstances 赋值给 mLastNonConfigurationInstances
        // 在通过 getViewModelStore 从 mLastNonConfigurationInstances 取
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        // 省略代码 .. 
    }

从上面的代码可以看出在页面销毁时会将 ViewModelStore 保存在 ActivityThread 在页面重建时会再通过 attach 将保存 ViewModelStore 的 lastNonConfigurationInstances 赋值给 mLastNonConfigurationInstances 在通过 getViewModelStore 获取 ViewModelStore 就会尝试从 mLastNonConfigurationInstances 中取

	// androidx.activity.ComponentActivity#ensureViewModelStore
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
	
	// android.app.Activity#getLastNonConfigurationInstance
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
        		// 已经在 attach 中赋值返回 activity
        		// 而 activity 就是通过 androidx.activity.ComponentActivity#onRetainNonConfigurationInstance 
        		// 返回的 androidx.activity.ComponentActivity.NonConfigurationInstances
        		// 它真正持有 ViewModelStore 
                ? mLastNonConfigurationInstances.activity : null;
    }

这样就可以拿到页面销毁前的 ViewModelStore 了并且它持有 ViewModel

销毁对象

既然 ViewModel 可以在页面重建时保持那么在什么时候销毁呢

	// androidx.activity.ComponentActivity#ComponentActivity()
    public ComponentActivity() {
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // 收到 ON_DESTROY 事件时判断如果不是配置更改引起的说明是正常退出页面
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }

	// androidx.lifecycle.ViewModelStore#clear
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
        	// 遍历保存的 ViewModel 调用 clear 方法
            vm.clear();
        }
        mMap.clear();
    }
	
	// androidx.lifecycle.ViewModel#clear
	final void clear() {
		// 省略代码 ..
        onCleared();
    }

在 ComponentActivity 中监听自己的生命周期在收到 ON_DESTROY 事件后判断如果是正常退出页面则清空 ViewModelStore 并回调 ViewModel 的 onCleared 方法可以在这个方法里做一些善后工作


总结

ViewModel 只能处理因配置更改导致的页面重建,如果因为资源限制页面被销毁重建时 ViewModel 也会被销毁,如果要处理这种场景可以使用 android.app.Activity#onSaveInstanceState(android.os.Bundle) 或者 savedstate(对前者的封装)

参考与感谢

Android官方架构组件ViewModel:从前世今生到追本溯源
Jetpack ViewModel 抽丝剥茧
ViewModel 这些知识点你都知道吗?
从源码看 Jetpack(6)- ViewModel 源码详解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值