ViewPager + Fragment的懒汉加载模式

ViewPager + Fragment的懒汉加载模式

首先,说下viewpager的使用场景下为什么需要懒汉式加载.

ViewPager本身是带有缓存机制的,对缓存页数的设置可以通过

public void setOffscreenPageLimit(int limit) {}实现,但是这个函数的实现中,有默认缓存页数DEFAULT_OFFSCREEN_PAGES 值为1,就是说即使你调用这个函数设置缓存页为0,实际是不会生效的,因为他会根据如果缓存页小于默认值,那么缓存页数就是默认值1.

它的缓存机制,大致原理是加载当前页时,会去缓存相邻页,这个页数是当前页两边的分别有几页,比如说设置为1,就是当前页左右两边都缓存一页. 这也是会出现懒加载机制的原因,

ViewPager的默认机制,总是会去缓存,会去预加载一页数据,这个做法原本是为了让滑动效果更流畅,在从第一页滑到第二页时,因为第二页的数据已经加载过了,所以可以立即显示出来,但是也正因为在加载第一页时,要去预加载多页,这可能拖累它所属activity的初始化速度,有可能会多次预加载,但是实际哪些页面没来得及显示,那么预加载多页也浪费了资源,流量。

所以,懒加载,就是在这个页面的UI为用户可见时才去加载数据,不在依赖预加载机制,提前加载多页数据。

 

然后,实现懒加载的流程:

1,懒加载的时机,界面可见的时候才去加载,这里就有界面可见,不可见的情况判断.

界面可见,又分第一次可见,非第一次可见.

怎么去判断fragment可见?是在其生命周期:onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach.

的那个阶段呢? 答案是跟这个生命周期没关系,并不是我们印象中onResume时UI可见,onPause失去焦点,onstop不可见.

在ViewPager搭配Fragment时,我们关注的生命周期:

onCreatedView + onActivityCreated + onResume + onPause + onDestroyView

看一个从Fragment1,滑到Fragment2的log:

2020-02-12 22:35:50.643 12 myviewpager.FragmentDemo: setUserVisibleHint()--,fragment 3,isVisibleToUser=false
2020-02-12 22:35:50.643 myviewpager.FragmentDemo: setUserVisibleHint()--,fragment 1,isVisibleToUser=false
2020-02-12 22:35:50.643 myviewpager.FragmentDemo: setUserVisibleHint()--,fragment 2,isVisibleToUser=true
2020-02-12 22:35:50.647 .myviewpager.FragmentDemo: onCreateView()--, fragment 3
2020-02-12 22:35:50.648  myviewpager.FragmentDemo: onResume()--, fragment 3

从Log看,public void setUserVisibleHint(boolean isVisibleToUser)这个函数跟生命周期是无关的,它是用来表面Fragment的UI是否为用户可见,类似的还有一个函数:

public void onHiddenChanged(boolean hidden)。

Log中表明,Fragment1,Fragment3都是 false,表明对用户不可见,只有Fragment2是true,表明当前Fragment2是可见的,同时去预加载Fragment3,。

这个函数的调用堆栈:

myviewpager.FragmentDemo.setUserVisibleHint(FragmentDemo.java:40)
androidx.fragment.app.FragmentPagerAdapter.setPrimaryItem(FragmentPagerAdapter.java:138)
androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1234)
androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1092)

其中setPrimaryItem,就是设置当前的Fragment的可见性

 public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

在Viewpager嵌套Fragment的结构中,ViewPager.populate()这个函数非常重要。populate() 就是处理预加载,缓存的,并且跟adapter紧密关联.

那setUserVisibleHint是在生命周期的那个过程中被执行的呢?

从Log看:

setUserVisibleHint → onCreatedView, →onResume

并且其中的参数true表示fragment可见,false不可见,所以setUserVisibleHint就是要找的处理懒加载的时机.

2,懒加载怎么实现?

首先,这里定义一个抽象类,

在界面可见时才去加载界面更新需要的数据,界面不可见时,比如那些预加载的页面,只是加载默认界面(空白页面,或者loading页面),

所以有一个抽象函数getDefaultLayoutRes,,去定义默认界面长什么样,

默认界面怎么去设置,是由两一个抽象函数,setupDefaultView去完成,也就是onCreateView中那些findViewById获取控件,给控件设置内容的操作.

其次,不要按原来的机制,在onResume里面加载数据,而是依据懒加载机制,通过setUserVisibleHint里面的逻辑判断(具体的逻辑在代码中注释),通过可见,不可见标记来决定是否加载数据,这里也定义了需要子类去覆写的方法.

还有一种情况,Activity之间切换时,跟同一个Activity中的fragment之间切换是不同的,如何分发fragment的可见性.

在界面已经创建后,当其不可见后,也要通知Fragment界面不可见的相关操作(中断网路等).也就是建议子类去重写的两个函数:

onFragmentPause,onFragmentResume.

最后,考虑ViewPager的嵌套情况下(其中一个fragment又是一个Viewpager为框架的),上面的实现是否有坑.今日头条应该是这种两层嵌套的框架.

这种情况,在第一层viewpager切换时,那个有嵌套的fragment不可见时,也会加载其中(viewpager中的默认页面)的数据.这个bug如何修复?

修改策略,第一:在分发可见性状态时,是去判断父Fragment是否可见,只有父fragment可见时,才去执行数据加载,

第二个,要在父Fragment可见时,去为其中嵌套的fragment分发可见性状态.

这就是isParentInVisible, dispatchChildVisibleState做的事情.

有了上面的思路流程,来看代码就很容易理解了.

FragmentLazyLoading 是实现了懒加载的抽象类,FragmentLazyDemo 是示例。后面有输出的log,

public abstract class FragmentLazyLoading extends Fragment {
    private static final String TAG = FragmentLazyLoading.class.getName();
    //表示默认的界面,如loading,空白界面
    protected View mDefaultView = null;
    private boolean mIsViewCreated = false;
    //是否是第一次创建,并可见
    private boolean mIsFirstVisible = false;
    //当前的可见标记
    private boolean mCurrentVisibleState = false;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        if (mDefaultView == null) {
            mDefaultView = inflater.inflate(getDefaultLayoutRes(), container, false);
        }
        //设置默认的界面,具体什么样子,需要子类去实现
        setupDefaultView(mDefaultView);
        mIsViewCreated = true;
        Log.d(TAG,"onCreateView(),mDefaultView="+mDefaultView);
        //这里分发tab中默认页的可见性,也即是初始的那个Fragment,
        // 此时这个Fragment的getUserVisibleHint()为true,isHidden()为false,对于除默认页的其他页面,
        // 只做了        mIsViewCreated =true。
        if (!isHidden() && getUserVisibleHint()) {
            dispatchUserVisibleHint(true);
        }

        return mDefaultView;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.d(TAG,"setUserVisibleHint(),isVisibleToUser="+isVisibleToUser);
        //对于默认页的可见分发已经在onCreateView中做了,这个针对的是非默认页,
        // 另外对一个界面做可见或者不可见的判断都有一个前提,就是这个页面已经创建了,
        // 也即是经历过onCreateView,至少默认页面已经创建过
        if (mIsViewCreated) {
            //从不可见到可见,
            if (isVisibleToUser && !mCurrentVisibleState) {
                dispatchUserVisibleHint(true);
            } else if (!isVisibleToUser && mCurrentVisibleState) {
                //从可见到不可见
                dispatchUserVisibleHint(false);
            }
        }
    }

    //分发可见性信息
    private void dispatchUserVisibleHint(boolean visible) {
        Log.d(TAG,"dispatchUserVisibleHint(),visible="+visible);
        //针对嵌套viewpager的处理,父Fragment不可见不做处理
        if (visible && isParentInVisible()) {
            return;
        }

        if (mCurrentVisibleState == visible) {
            //理论上,这里应该不会跑到
            return;
        }
        //修改当前的可见标记
        mCurrentVisibleState = visible;
        if (visible) {
            if (mIsFirstVisible) {
                //Fragment第一次可见,会有很多事情做,单独拿出来
                mIsFirstVisible = false;
                onFragmentInit();
            }

            onFragmentResume();
            //在父Fragment可见时,分发内嵌的Fragment的可见性状态
            dispatchChildVisibleHint(true);
        } else {
            onFragmentPause();
            //在父Fragment可见时,分发内嵌的Fragment的可见性状态
            dispatchChildVisibleHint(true);
        }
    }

    private void dispatchChildVisibleHint(boolean visible) {
        FragmentManager fragmentManager = getChildFragmentManager();
        List<Fragment> list = fragmentManager.getFragments();
        if (null != list) {
            for (Fragment fragment : list) {
                if ((fragment instanceof FragmentLazyLoading) &&
                    !fragment.isHidden() &&
                    fragment.getUserVisibleHint()) {
                    ((FragmentLazyLoading) fragment).dispatchUserVisibleHint(visible);
                }
            }
        }
    }

    private boolean isParentInVisible() {
        Fragment parentFragment = getParentFragment();
        if (parentFragment instanceof FragmentLazyLoading) {
            FragmentLazyLoading fragmentLazyLoading = (FragmentLazyLoading)parentFragment;
            //因为要判断不可见,这里取反
            return !fragmentLazyLoading.isSupportVisible();
        }
        return false;
    }

    private boolean isSupportVisible() {
        return mCurrentVisibleState;
    }

    //为什么这里也需要重新分发呢?当用FragmentTransaction切换fragment,推荐的做法是hide,add,show,
    // 在hide,show的时候,不会去执行fragment的生命周期,但是会执行
    //onHiddenChanged,所以在这里也要做可见性分发
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.d(TAG,"onHiddenChanged(),hidden="+hidden);
        if (hidden) {
            dispatchUserVisibleHint(false);
        } else {
            dispatchUserVisibleHint(true);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        //Log.d(TAG,"onResume()");
        //第一次创建fragment总会执行onResume,但是此时,可能并不是可见的,如从fragment1到fragment2,
        // 会去调用fragment3的onResume,这时fragment3并不可见,所以不需要处理分发。
        if (!mIsFirstVisible) {
            //当activity之间切换时,切换回来时,activity中的fragment都会执行其onResume,
            // 但是这种情况下,不会执行setUserVisibleHint,因为它是有activity创建时,
            // 通过绑定adapter来调用的,这一点从setUserVisibleHint的调用流程可以得出。
            // 此时我们只需要对当前可见的fragment做分发。
            if (!isHidden() && !mCurrentVisibleState && getUserVisibleHint()) {
                dispatchUserVisibleHint(true);
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        //Log.d(TAG,"onResume()");
        //从可见到不可见就去销毁它
        if (mCurrentVisibleState && !getUserVisibleHint()) {
            dispatchUserVisibleHint(false);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mIsViewCreated = false;
        mIsFirstVisible = false;
    }

    //建议子类,应该去覆写这个方法,通知可见状态
    public void onFragmentPause() {
    }
    //建议子类,应该去覆写这个方法,通知不可见状态
    public void onFragmentResume() {
    }
    //第一次创建fragment,
    protected abstract void onFragmentInit();
    //设置默认界面显示内容
    protected abstract void setupDefaultView(View mDefaultView);
    //默认界面的布局
    protected abstract int getDefaultLayoutRes();
}

--

public class FragmentLazyDemo extends FragmentLazyLoading {
    private static final String TAG = FragmentLazyDemo.class.getName();
    private static final String INTENT_INDEX = "intent.index";
    private static final int LOAD_DATA_DONE = 0;
    private ImageView mImageView;
    private TextView mTextView;
    private int mNavigationIndex;
    private CountDownTimer mCountDownTimer;

    public static FragmentLazyDemo newInstance(int navigationIndex) {
        Bundle bundle = new Bundle();
        bundle.putInt(INTENT_INDEX , navigationIndex);
        FragmentLazyDemo fragmentDemo = new FragmentLazyDemo();
        fragmentDemo.setArguments(bundle);
        return fragmentDemo;
    }

    @Override
    protected int getDefaultLayoutRes() {
        return R.layout.fragment_layout;
    }

    @Override
    protected void setupDefaultView(View view) {
        mImageView = view.findViewById(R.id.iv_content);
        mTextView = view.findViewById(R.id.tv_loading);
        mNavigationIndex = getArguments().getInt(INTENT_INDEX);

        Log.d(TAG, "setupDefaultView(),fragment "+mNavigationIndex + "设置默认界面 " );
    }

    @Override
    protected void onFragmentInit() {
        Log.d(TAG, "onFragmentInit(),fragment "+ mNavigationIndex + "第一次创建" );

    }

    @Override
    public void onFragmentResume() {
        super.onFragmentResume();
        loadData();
        Log.d(TAG,  " onFragmentResume(),fragment "+ mNavigationIndex + "更新界面" );
    }

    @Override
    public void onFragmentPause() {
        super.onFragmentPause();
        mHandler.removeMessages(10);
        mCountDownTimer.cancel();
        Log.d(TAG, "onFragmentPause(), fragment " +mNavigationIndex+ "暂停操作" );
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mNavigationIndex = getArguments().getInt(INTENT_INDEX);
        Log.d(TAG,"setUserVisibleHint()--,fragment " + mNavigationIndex +
                ",isVisibleToUser="+isVisibleToUser);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mNavigationIndex = getArguments().getInt(INTENT_INDEX);
        //Log.d(TAG,"onAttach()--,fragment " + mNavigationIndex);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //Log.d(TAG,"onCreate()--,fragment " + mNavigationIndex);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        //Log.d(TAG, "onCreateView()--, fragment " + mNavigationIndex);
        return super.onCreateView(inflater,container,savedInstanceState);
    }

    private void loadData() {
        //模拟耗时
        mCountDownTimer = new CountDownTimer(1000, 100) {
            @Override
            public void onTick(long millisUntilFinished) {

            }

            @Override
            public void onFinish() {
                mHandler.sendEmptyMessage(LOAD_DATA_DONE);
            }
        };
        mCountDownTimer.start();
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume()--, fragment " + mNavigationIndex);
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause()--, fragment " + mNavigationIndex);
    }

    @Override
    public void onStop() {
        super.onStop();
        //Log.d(TAG, "onStop()--, fragment " + mNavigationIndex);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (null != mCountDownTimer) {
            mCountDownTimer.cancel();
        }
        Log.d(TAG, "onDestroyView()--, fragment " + mNavigationIndex);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //Log.d(TAG, "onDestroy()--, fragment " + mNavigationIndex);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        //Log.d(TAG, "onDetach()--, fragment " + mNavigationIndex);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == LOAD_DATA_DONE) {
                mTextView.setVisibility(View.GONE);
                int resId = R.drawable.f1;
                switch (mNavigationIndex) {
                    case 1:
                        resId = R.drawable.f1;
                        break;
                    case 2:
                        resId = R.drawable.f2;
                        break;
                    case 3:
                        resId = R.drawable.f3;
                        break;
                    case 4:
                        resId = R.drawable.f4;
                        break;
                    case 5:
                        resId = R.drawable.f5;
                        break;
                }

                mImageView.setImageResource(resId);
                mImageView.setScaleType(ImageView.ScaleType.FIT_XY);
                mImageView.setVisibility(View.VISIBLE);
                //Log.d(TAG,"handleMessage()--,fragment " + mNavigationIndex);
                try {
                    Thread.sleep(200);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    };
}

输出log:

2020-02-13 11:07:24.248 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 1,isVisibleToUser=false
2020-02-13 11:07:24.248 21198-21198.myviewpager.lazyLoad.FragmentLazyLoading: setUserVisibleHint(),isVisibleToUser=false
2020-02-13 11:07:24.248 21198-21198.myviewpager.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 2,isVisibleToUser=false
2020-02-13 11:07:24.248 21198-21198.myviewpager.lazyLoad.FragmentLazyLoading: setUserVisibleHint(),isVisibleToUser=true
2020-02-13 11:07:24.248 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 1,isVisibleToUser=true
2020-02-13 11:07:24.256 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: setupDefaultView(),fragment 1设置默认界面 
2020-02-13 11:07:24.256 21198-21198myviewpager.lazyLoad.FragmentLazyLoading: onCreateView(),mDefaultView=android.widget.FrameLayout{c2bd86d V.E...... ......I. 0,0-0,0}
2020-02-13 11:07:24.256 21198-21198myviewpager.lazyLoad.FragmentLazyLoading: dispatchUserVisibleHint(),visible=true
2020-02-13 11:07:24.257 21198-21198.myviewpager.lazyLoad.FragmentLazyDemo:  onFragmentResume(),fragment 1更新界面
2020-02-13 11:07:24.259 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: onResume()--, fragment 1
2020-02-13 11:07:24.260 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: setupDefaultView(),fragment 2设置默认界面 
2020-02-13 11:07:24.261 21198-21198myviewpager.lazyLoad.FragmentLazyLoading: onCreateView(),mDefaultView=android.widget.FrameLayout{1036d33 V.E...... ......I. 0,0-0,0}
2020-02-13 11:07:24.261 21198-21198myviewpager.lazyLoad.FragmentLazyDemo: onResume()--, fragment 2

从Log,界面可见时才去执行更新界面内容的操作,
如果界面不可见,仅加载默认界面(空白页,或者loading页面)

2020-02-13 14:39:57.624 23633-23633// D/com.test.myviewpager.lazyLoad.FragmentLazyLoading: setUserVisibleHint(),isVisibleToUser=false
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 3,isVisibleToUser=false
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyLoading: setUserVisibleHint(),isVisibleToUser=false
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyLoading: dispatchUserVisibleHint(),visible=false
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyDemo: onFragmentPause(), fragment 1暂停操作
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 1,isVisibleToUser=false
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyLoading: setUserVisibleHint(),isVisibleToUser=true
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyLoading: dispatchUserVisibleHint(),visible=true
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyDemo:  onFragmentResume(),fragment 2更新界面
2020-02-13 14:39:57.624 23633-23633// D//.lazyLoad.FragmentLazyDemo: setUserVisibleHint()--,fragment 2,isVisibleToUser=true
2020-02-13 14:39:57.627 23633-23633// D//.lazyLoad.FragmentLazyDemo: setupDefaultView(),fragment 3设置默认界面 
2020-02-13 14:39:57.627 23633-23633// D//.lazyLoad.FragmentLazyLoading: onCreateView(),mDefaultView=android.widget.FrameLayout{964a825 V.E...... ......I. 0,0-0,0}
2020-02-13 14:39:57.628 23633-23633// D//.lazyLoad.FragmentLazyDemo: onResume()--, fragment 3

从Fragment1滑到Fragment2,可以看到Fragment 1 执行暂停,Fragment 2 更新界面, Fragment 3 仅设置默认界面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ViewPagerFragment 是 Android 开发中常用的组件,ViewPager 可以实现左右滑动切换不同的页面,而 Fragment 可以实现模块化开发,将界面和逻辑分离,便于维护和扩展。 将两者结合起来可以实现多个 FragmentViewPager 中的左右滑动切换。具体实现步骤如下: 1. 创建多个 Fragment,每个 Fragment 对应一个页面。 2. 创建一个继承自 FragmentPagerAdapter 或 FragmentStatePagerAdapter 的适配器类,重写 getItem() 方法,返回对应位置的 Fragment。 3. 在布局文件中添 ViewPager 组件,并设置适配器。 4. (可选)设置 ViewPager 的页面切换动画效果。 示例代码: 1. 创建 Fragment: ``` public class MyFragment extends Fragment { // ... } ``` 2. 创建适配器: ``` public class MyPagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragmentList; public MyPagerAdapter(FragmentManager fm, List<Fragment> fragmentList) { super(fm); this.fragmentList = fragmentList; } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return fragmentList.size(); } } ``` 3. 在布局文件中添 ViewPager 组件: ``` <androidx.viewpager.widget.ViewPager android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 4. 设置适配器: ``` List<Fragment> fragmentList = new ArrayList<>(); fragmentList.add(new MyFragment()); fragmentList.add(new MyFragment()); fragmentList.add(new MyFragment()); ViewPager viewPager = findViewById(R.id.view_pager); viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), fragmentList)); ``` 5. (可选)设置页面切换动画效果: ``` viewPager.setPageTransformer(true, new DepthPageTransformer()); ``` 以上是 ViewPagerFragment 的基本用法,你还可以根据实际需求对它们进行更多的扩展和定制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值