get${XXX}FragmentManager()的区别

本文详细解析了FragmentManager及其相关方法在Android中的作用与实现原理,包括不同FragmentManager的用途与区别。

${XXX}


为何会有这么多get${XXX}FragmentManager,它们存在目的是什么呢

FragmentActivity

  • getSupportFragmentManager
  • getFragmentManager

Fragment

  • getFragmentManager
  • getChildFragmentManager

FragmentActivity.getSupportFragmentManager()


代码取自 v4兼容包

    /**
     * Return the FragmentManager for interacting with fragments associated
     * with this activity.
     */
    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }

跟进mFragments看到

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

继续跟进FragmentControllerFragmentController的方法列表如下:

/** Attaches the host to the FragmentManager for this controller. The host must be attached before the FragmentManager can be used to manage Fragments.*/
attachHost 
createController
/**dispatch fragment lifecycle */
onCreateView
dispatchActivityCreated
dispatchConfigurationChanged
dispatchContextItemSelected
dispatchCreate
dispatchCreateOptionsMenu
dispatchDestroy
dispatchDestroyView
dispatchLowMemory
dispatchOptionsItemSelected
dispatchOptionsMenuClosed
dispatchPause
dispatchPrepareOptionsMenu
dispatchReallyStop
dispatchResume
dispatchStart
dispatchStop
/**dispatch loader*/
doLoaderDestroy
doLoaderRetain
doLoaderStart
doLoaderStop
dumpLoaders
reportLoaderStart
restoreLoaderNonConfig
retainLoaderNonConfig
/**Execute any pending actions for the Fragments managed by the controller's FragmentManager.
Call when queued actions can be performed [eg when the Fragment moves into a start or resume state].*/
execPendingActions
getActiveFragments
getActiveFragmentsCount
/**Returns a {@link FragmentManager} for this controller.*/
getSupportFragmentManager
/**Returns a {@link LoaderManager}.*/
getSupportLoaderManager
/**Bundle state manager*/
noteStateNotSaved
restoreAllState
retainNonConfig
saveAllState
/**
Provides integration points with a FragmentManager for a fragment host.
It is the responsibility of the host to take care of the Fragment's lifecycle. The methods provided by FragmentController are for that purpose.
*/
class FragmentController{
    private final FragmentHostCallback<?> mHost;
    // ignore ...
}

查看源代码之后,FragmentController做了如下几件事情:

  1. 提供单例方法createController用于创建实例;
  2. 提供attachHost(Fragment parent),通过FragmentControllerHostFragmentFragmentContainer附加到FragmentManager
  3. 提供分发Fragment 的生命周期的方法;
  4. 提供存储/恢复SaveState的方法,用于处理Fragment的状态恢复;
  5. 提供分发LoaderMananger的回调方法;

接着跟进FragmentHostCallback,查看继承关系如下:

/**
Integration points with the Fragment host.Fragments may be hosted by any object; such as an Activity.

In order to host fragments, implement FragmentHostCallback, overriding the methods applicable to the host.*/
--Object (java.lang)
----FragmentContainer (android.support.v4.app)
------FragmentHostCallback (android.support.v4.app)
--------HostCallbacks in FragmentActivity (android.support.v4.app)

FragmentHostCallback存在的意义,上面的注释中已经说明的很明白了,那么继续跟进它在FragmentActivity中的 usage。

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

OK,FragmentController 已经清晰的展示它的职责。


那么继续回到代码:

public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
}

跟进FragmentHostCallback

final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

FragmentManagerImpl getFragmentManagerImpl() {
        return mFragmentManager;
    }

继续跟进FragmentManagerImpl

Object (java.lang)
--FragmentManager (android.support.v4.app)
----FragmentManagerImpl (android.support.v4.app)

愈来愈接近FragmentManager( 对 FragmentManager 不了解?点我!)。我们了解到FragmentManager只是一个抽象类,实际的实现类只有FragmentManagerImpl一个而已。

先不慌忙,先啃掉FragmentManagerImpl再回头看看为什么。

// Read The Fuck Code!(RTFC)
// 已忽略掉无关的代码
final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {

    /** 一个pending 列表,借助 handler 将 Runnable 转发到execPendingActions()中。在execPendingActions()经由中转 Runnable 执行任务。*/
    ArrayList<Runnable> mPendingActions;

    /**{@link #moveToState()}*/
    int mCurState = Fragment.INITIALIZING;
    /**{@link FragmentController#attachController()}*/
    FragmentHostCallback mHost;
    FragmentController mController;
    FragmentContainer mContainer;
    Fragment mParent;

    Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions();
        }
    }

    @Override
    public boolean executePendingTransactions() {
        return execPendingActions();
    }

    @Override
    public FragmentTransaction beginTransaction() {
        // BackStackRecord 简要说就是记录“你从哪里来,到哪里去。”
        // 如:你从 A 处来,要去往 B处。
        // 记录的是:这个事务(从 A 来,要去 B。),而不是目的{A,B}。
        // 具体,可以跟进{@link BackStackRecord}
        return new BackStackRecord(this);
    }
   @Override
    public void popBackStack() {
        enqueueAction(new Runnable() {
            @Override public void run() {
                popBackStackState(mHost.getHandler(), null, -1, 0);
            }
        }, false);
    }

@Override
    public boolean popBackStackImmediate() {
        checkStateLoss();
        executePendingTransactions();
        return popBackStackState(mHost.getHandler(), null, -1, 0);
    }

    @Override
    public int getBackStackEntryCount() {
        return mBackStack != null ? mBackStack.size() : 0;
    }

     /**
     *增加了一个action,等待队列的操作。
     * @param action 待添加的行为
     * @param allowStateLoss 是否允许丢失状态
     * @throws IllegalStateException if the activity has been destroyed
     */
    public void enqueueAction(Runnable action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<Runnable>();
            }
            mPendingActions.add(action);
            if (mPendingActions.size() == 1) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
            }
        }
    }

public boolean execPendingActions() {
      //...
      while (true) {
            int numActions;

            synchronized (this) {
                if (mPendingActions == null || mPendingActions.size() == 0) {
                    break;
                }

                numActions = mPendingActions.size();
                if (mTmpActions == null || mTmpActions.length < numActions) {
                    mTmpActions = new Runnable[numActions];
                }
                mPendingActions.toArray(mTmpActions);
                mPendingActions.clear();
                mHost.getHandler().removeCallbacks(mExecCommit);
            }

            mExecutingActions = true;
            for (int i=0; i<numActions; i++) {
                mTmpActions[i].run();
                mTmpActions[i] = null;
            }
            mExecutingActions = false;
            didSomething = true;
        }
            //...
}

/** 对 Fragment 的状态分发的管理的实现,调用可参考{@link FragmentController#dispatch${XXX}};这个方法所做的事情是,根据状态等一系列参数,让fragment 做出对应的实现。*/
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
            // 有兴趣的可行阅读代码
    }
}

先总结下FragmentManagerImpl做了哪些事情?

1.BackStackRecord管理,派生于(FragmentTransaction),然最终是对 BackStackRecord.Op的管理。
2.Fragment状态的管理。


Fragment.getFragmentManager()

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {
    // The fragment manager we are associated with.  Set as soon as the
    // fragment is used in a transaction; cleared after it has been removed
    // from all transactions.
    FragmentManagerImpl mFragmentManager;
    // Private fragment manager for child fragments inside of this one.
    FragmentManagerImpl mChildFragmentManager;

    final public FragmentManager getFragmentManager() {
        return mFragmentManager;
    }
    /**
     * Return a private FragmentManager for placing and managing Fragments
     * inside of this Fragment.
     */
    final public FragmentManager getChildFragmentManager() {
        if (mChildFragmentManager == null) {
            instantiateChildFragmentManager();
            if (mState >= RESUMED) {
                mChildFragmentManager.dispatchResume();
            } else if (mState >= STARTED) {
                mChildFragmentManager.dispatchStart();
            } else if (mState >= ACTIVITY_CREATED) {
                mChildFragmentManager.dispatchActivityCreated();
            } else if (mState >= CREATED) {
                mChildFragmentManager.dispatchCreate();
            }
        }
        return mChildFragmentManager;
    }
}

最终都是引用FragmentManagerImpl,证明他们并无分别。所以调用enableDebugLogging方法,无论调用 get${XXX}FragmentManager均会打印日志。

public abstract class FragmentManager {
/**
     * Control whether the framework's internal fragment manager debugging
     * logs are turned on.  If enabled, you will see output in logcat as
     * the framework performs fragment operations.
     */
    public static void enableDebugLogging(boolean enabled) {
        FragmentManagerImpl.DEBUG = enabled;
    }
}

getChildFragmentManagergetFragmentManager的区别有二。

  1. 不同的 instance;
  2. 增加对其持有的 fragment 的lifecycle 的更新;

附上日志:

描述了:从 ViewPager内部的 Fragment点击跳转到新的 Fragment,并点击返回的过程。

Tip:

ViewPager使用getChildFragmentManager,如果换成getFragmentManager。那么 点击返回的时候 viewPager 所持有的 Fragment 是得不到生命周期调用的,所以往往点击返回 后是显示空白的页面。注意日志中ViewPagerChildFragmentmovefrom部分。




FragmentManager: Commit: BackStackEntry{28cbf0d0}
******FragmentManager:   mName=null mIndex=-1 mCommitted=false
******FragmentManager:   Operations:
******FragmentManager:     Op #0: REPLACE DetailFragment{28cbf278 id=0x7f0c0050}
FragmentManager: Setting back stack index 1 to BackStackEntry{28cbf0d0}
FragmentManager: Run: BackStackEntry{28cbf0d0 #1}
FragmentManager: Bump nesting in BackStackEntry{28cbf0d0 #1} by 1
FragmentManager: Bump nesting of DetailFragment{28cbf278 id=0x7f0c0050} to 1
FragmentManager: OP_REPLACE: adding=DetailFragment{28cbf278 id=0x7f0c0050} old=DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: Bump nesting of DispatchFragment{28c8e800 #0 id=0x7f0c0050} to 2
FragmentManager: remove: DispatchFragment{28c8e800 #0 id=0x7f0c0050} nesting=2
FragmentManager: movefrom RESUMED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: movefrom RESUMED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: movefrom RESUMED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: movefrom STARTED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: movefrom STARTED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: movefrom STARTED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: movefrom STOPPED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: movefrom STOPPED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: movefrom STOPPED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: movefrom ACTIVITY_CREATED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: movefrom ACTIVITY_CREATED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: movefrom ACTIVITY_CREATED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: add: DetailFragment{28cbf278 id=0x7f0c0050}
FragmentManager: Allocated fragment index DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: moveto CREATED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: moveto ACTIVITY_CREATED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: moveto STARTED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: moveto RESUMED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: popFromBackStack: BackStackEntry{28cbf0d0 #1}
******FragmentManager:   mName=null mIndex=1 mCommitted=true
******FragmentManager:   Operations:
******FragmentManager:     Op #0: REPLACE DetailFragment{28cbf278 #1 id=0x7f0c0050}
******FragmentManager:       Removed: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: Bump nesting in BackStackEntry{28cbf0d0 #1} by -1
FragmentManager: Bump nesting of DetailFragment{28cbf278 #1 id=0x7f0c0050} to 0
FragmentManager: Bump nesting of DispatchFragment{28c8e800 #0 id=0x7f0c0050} to 1
FragmentManager: remove: DetailFragment{28cbf278 #1 id=0x7f0c0050} nesting=0
FragmentManager: movefrom RESUMED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: movefrom STARTED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: movefrom STOPPED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: movefrom ACTIVITY_CREATED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: movefrom CREATED: DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: Freeing fragment index DetailFragment{28cbf278 #1 id=0x7f0c0050}
FragmentManager: add: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: moveto ACTIVITY_CREATED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: moveto ACTIVITY_CREATED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: moveto ACTIVITY_CREATED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: moveto STARTED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: moveto STARTED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: moveto STARTED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: moveto RESUMED: DispatchFragment{28c8e800 #0 id=0x7f0c0050}
FragmentManager: moveto RESUMED: ViewPagerChildFragment{28cb1038 #0 id=0x7f0c0051}
FragmentManager: moveto RESUMED: ViewPagerChildFragment{28cb1120 #1 id=0x7f0c0051}
FragmentManager: Freeing back stack index 1


package com.weishitech.qichechangtingyinyue.fragment.lilv; import android.widget.Toast; import androidx.viewpager2.widget.ViewPager2; import com.hfd.common.base.BaseFragment; import com.weishitech.qichechangtingyinyue.R; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import com.weishitech.qichechangtingyinyue.fragment.Adapter.SongsPagerAdapter; import java.util.ArrayList; import java.util.List; /** * 个人出租屋税率 */ public class LiLvFragment extends BaseFragment{ ViewPager2 vp2_song; private List<MusicBean.DataBean> songList; private SongsPagerAdapter adapter; @Override protected int setLayout() { return R.layout.fragment_lilv; } @Override protected void initView() { vp2_song = fvbi(R.id.vp2_song); } @Override protected void initClick() { } @Override protected void initData() { songList = new ArrayList<>(); // 设置垂直滑动 vp2_song.setOrientation(ViewPager2.ORIENTATION_VERTICAL); // 设置适配器 adapter = new SongsPagerAdapter(this, songList); vp2_song.setAdapter(adapter); // 默认打开第一首 vp2_song.setCurrentItem(0, false); // 监听页面切换(可选) vp2_song.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { super.onPageSelected(position); Toast.makeText(requireContext(), "正在播放:" + songList.get(position).getTitle(), Toast.LENGTH_SHORT).show(); } }); } // 提供给外部跳转到指定位置 public void scrollToPosition(int pos) { if (vp2_song != null && pos >= 0 && pos < songList.size()) { vp2_song.setCurrentItem(pos, true); } } }package com.weishitech.qichechangtingyinyue.fragment.Adapter; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.viewpager2.adapter.FragmentStateAdapter; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import com.weishitech.qichechangtingyinyue.fragment.lilv.SongItemFragment; import java.util.List; // SongsPagerAdapter.java public class SongsPagerAdapter extends FragmentStateAdapter { private final List<MusicBean.DataBean> data; private final Fragment parentFragment; public SongsPagerAdapter(Fragment fa, List<MusicBean.DataBean> data) { super(fa); this.data = data; this.parentFragment = fa; } @NonNull @Override public Fragment createFragment(int position) { return SongItemFragment.newInstance(data.get(position), position, data); } @Override public int getItemCount() { return data.size(); } } package com.weishitech.qichechangtingyinyue.fragment.lilv; import android.graphics.Color; import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import com.hfd.common.base.BaseFragment; import com.hfd.common.util.DensityUtil; import com.hw.lrcviewlib.LrcDataBuilder; import com.hw.lrcviewlib.LrcRow; import com.hw.lrcviewlib.LrcView; import com.weishitech.qichechangtingyinyue.R; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import java.util.ArrayList; import java.util.List; public class SongItemFragment extends BaseFragment { private MusicBean.DataBean song; private int position; private List<MusicBean.DataBean> songList; private ImageView ivCover; private TextView tvTitle, tvSinger; LrcView lyricView; List<LrcRow> lrcRows; public static SongItemFragment newInstance(MusicBean.DataBean song, int pos, List<MusicBean.DataBean> list) { SongItemFragment f = new SongItemFragment(); Bundle args = new Bundle(); args.putSerializable("song", song); args.putInt("position", pos); args.putSerializable("song_list", new ArrayList<>(list)); f.setArguments(args); return f; } @Override protected int setLayout() { return R.layout.fragment_song_item; } @Override protected void initView() { ivCover = fvbi(R.id.iv_cover); tvTitle = fvbi(R.id.tv_title); tvSinger = fvbi(R.id.tv_singer); lyricView = fvbi(R.id.lyricView); } @Override protected void initClick() { } @Override protected void initData() { Bundle args = getArguments(); if (args != null) { song = (MusicBean.DataBean) args.getSerializable("song"); position = args.getInt("position"); songList = (List<MusicBean.DataBean>) args.getSerializable("song_list"); } String title = song.getTitle(); String cover = song.getCover(); String music = song.getMusic(); String lrc = song.getLrc(); String singer = song.getSinger(); Glide.with(this) .load(cover) .into(ivCover); tvTitle.setText(title); tvSinger.setText(singer); lrcRows = new LrcDataBuilder().BuiltFromAssets(getContext(), lrc); lyricView.getLrcSetting() .setTimeTextSize(40)//时间字体大小 .setSelectLineColor(Color.parseColor("#FFFFFF"))//选中线颜色 .setSelectLineTextSize(25)//选中线大小 .setNormalRowColor(Color.parseColor("#919191")) .setHeightRowColor(Color.parseColor("#FFFFFF"))//高亮字体颜色 .setNormalRowTextSize(DensityUtil.sp2px(getContext(), 17))//正常行字体大小 .setHeightLightRowTextSize(DensityUtil.sp2px(getContext(), 17))//高亮行字体大小 .setTrySelectRowTextSize(DensityUtil.sp2px(getContext(), 17))//尝试选中行字体大小 .setTimeTextColor(Color.parseColor("#FFFFFF"))//时间字体颜色 .setTrySelectRowColor(Color.parseColor("#FFFFFF"));//尝试选中字体颜色 lyricView.commitLrcSettings(); lyricView.setLrcData(lrcRows); } } 没效果
最新发布
12-05
在你的 `initFragments()` 方法中,首次进入页面时: ```kotlin Log.d("重建测试", "FragmentManager fragments: ${fragmentManager.fragments.map { it::class.java.simpleName }}") ``` 打印出 `fragmentManager.fragments` 是空的,这是**正常现象**,原因如下: --- ### ✅ 问题原因详解 在你调用 `initFragments()` 的时候,虽然你已经把 Fragment 实例放入了 `fragmentMap` 中,但此时这些 Fragment **尚未通过 FragmentManager 添加(add 或 replace)到容器中**,所以: - `fragmentManager.fragments` 返回的是 **当前已经被添加到 FragmentManager 中的 Fragment 列表** - 你只是 `newInstance()` 了 Fragment 并放入了 `fragmentMap`,但**还没有真正执行添加操作** - 因此 `fragmentManager.fragments` 为空 --- ### ✅ 示例流程说明 1. **你创建了 Fragment 实例并放入 `fragmentMap`** ```kotlin put(MacFilterStep.MAC_GROUP_LIST, SdnMacGroupListFragment.newInstance()) ``` 2. **你判断是否是首次进入,并调用 `switchFragment()`** ```kotlin currentFragment = switchFragment(null, fragmentMap[currentStep], R.id.content_frame) ``` 3. **但你在调用 `switchFragment()` 之前就打印了 `fragmentManager.fragments`** - 此时还未执行 `switchFragment()`,即还未执行 `FragmentTransaction` 添加 Fragment - 所以 `fragmentManager.fragments` 还是空的 --- ### ✅ 正确做法 如果你希望打印出已经被添加的 Fragment,应该将日志打印 **放在 `switchFragment()` 方法执行之后**: ```kotlin if (!isRestore) { currentStep = MacFilterStep.MAC_GROUP_LIST currentFragment = switchFragment(null, fragmentMap[currentStep], R.id.content_frame) } else { currentStep = viewModel.currentStep currentFragment = fragmentMap[currentStep] } // ✅ 放在这里,才能看到添加后的 Fragment Log.d("重建测试", "FragmentManager fragments: ${fragmentManager.fragments.map { it::class.java.simpleName }}") ``` --- ### ✅ `switchFragment()` 方法可能的实现逻辑(示例) ```kotlin private fun switchFragment( oldFragment: Fragment?, newFragment: Fragment?, containerId: Int ): Fragment? { if (newFragment == null) return null supportFragmentManager.beginTransaction().apply { if (oldFragment != null) { hide(oldFragment) } if (!newFragment.isAdded) { add(containerId, newFragment) } else { show(newFragment) } commit() } return newFragment } ``` > ⚠️ 注意:`commit()` 是异步的,如果你在 `commit()` 后立即打印 `fragmentManager.fragments`,可能仍然看不到更新后的结果。如需确保,可以使用 `supportFragmentManager.executePendingTransactions()`。 --- ### ✅ 修正后的完整代码示例 ```kotlin if (!isRestore) { currentStep = MacFilterStep.MAC_GROUP_LIST currentFragment = switchFragment(null, fragmentMap[currentStep], R.id.content_frame) } else { currentStep = viewModel.currentStep currentFragment = fragmentMap[currentStep] } // ✅ 确保事务执行完成后再打印 supportFragmentManager.executePendingTransactions() Log.d("重建测试", "FragmentManager fragments: ${fragmentManager.fragments.map { it::class.java.simpleName }}") ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值