android生命周期神器--Lifecycle

android生命周期神器–Lifecycle

生命周期

  • 从官方借用下图片
  • 在这里插入图片描述
  • 如上图,Lifecycle将生命周期归类为6个事件和5个状态,这些事件和状态的切换逻辑的实现就在LifecycleRegistry类里的getStateAfter方法
public class LifecycleRegistry extends Lifecycle {
	 static State getStateAfter(Event event) {
	        switch (event) {
	            case ON_CREATE:
	            case ON_STOP:
	                return CREATED;
	            case ON_START:
	            case ON_PAUSE:
	                return STARTED;
	            case ON_RESUME:
	                return RESUMED;
	            case ON_DESTROY:
	                return DESTROYED;
	            case ON_ANY:
	                break;
	        }
	        throw new IllegalArgumentException("Unexpected event value " + event);
	    }
  }
  • 从getStateAfter方法里我们可以看到事件发生后的状态与上图是对应的
  • 另外LifecycleRegistry里还有upEvent和downEvent这两个重要方法,upEvent可以理解为上图第一行的states的下一个事件
  • downEvent可以理解为上图第三行的states的下一个事件
 private static Event downEvent(State state) {
        switch (state) {
            case INITIALIZED:
                throw new IllegalArgumentException();
            case CREATED:
                return ON_DESTROY;
            case STARTED:
                return ON_STOP;
            case RESUMED:
                return ON_PAUSE;
            case DESTROYED:
                throw new IllegalArgumentException();
        }
        throw new IllegalArgumentException("Unexpected state value " + state);
    }

    private static Event upEvent(State state) {
        switch (state) {
            case INITIALIZED:
            case DESTROYED:
                return ON_CREATE;
            case CREATED:
                return ON_START;
            case STARTED:
                return ON_RESUME;
            case RESUMED:
                throw new IllegalArgumentException();
        }
        throw new IllegalArgumentException("Unexpected state value " + state);
    }
  • 为了更方便理解,从网上借了张图,如有侵权请联系删除
  • 在这里插入图片描述
  • 这个upEvent和downEvent有啥用呢,我先问你一个问题,如果你当前处于RESUMED状态,而另一个处于STARTED状态,此时为了让STARTED进入RESUMED状态,根据上图你可以很快地说出STARTED通过ON_RESUME事件即可进入RESUMED状态,那么在代码里upEvent和downEvent就是为了计算得到这个ON_RESUME事件的
  • 也就是说这两个方法主要是为了保持状态的同步用的,在LifecycleRegistry里有个变量mObserverMap存放了所有的观察者,但他们的状态可能不一致,比如最新的观察者已经进入了STARTED状态,而其他观察者有的可能处于CREATED,有的可能处于RESUMED,那么就需要把它们都同步为STARTED状态
  • 那么怎么同步呢,从上图我们可以知道处于CREATED状态的只要ON_START事件就可以进入STARTED,而处于RESUMED的只要ON_PAUSE事件就可以进入STARTED,因此upEvent和downEvent就是用来计算这个事件的
  • 由于只能一个事件改变一个状态这样一步一步来进入我们需要的目标状态,因此我们可以发现代码里就是用循环实现的
  • 比如INITIALIZED的要进入STARTED转态,那就是执行upEvent,第一次调用upEvent得到ON_CREATE,进入CREATED,发现还不是我们的目标状态STARTED,于是再调用upEvent方法得到ON_START,进入STARTED,这时发现已经是我们的目标转态STARTED了,这个循环就退出了
  • 有了我上面的解释后我们再来看看LifecycleRegistry的sync方法是不是就更好理解了
 private void sync() {
        LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
        if (lifecycleOwner == null) {
            throw new IllegalStateException("LifecycleOwner of this LifecycleRegistry is already"
                    + "garbage collected. It is too late to change lifecycle state.");
        }
        while (!isSynced()) {//只要还没达到目标状态,就继续执行,直到大家状态都一致
            mNewEventOccurred = false;
            // no need to check eldest for nullability, because isSynced does it for us.
            if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {//说明旧的状态在最新状态之后,那么通过downEvent就可以一步一步回到最新状态
                backwardPass(lifecycleOwner);//backwardPass里最重要就是用downEvent来计算下一个事件,以便最终通过一个个事件回到最新状态
            }
            Entry<LifecycleObserver, ObserverWithState> newest = mObserverMap.newest();
            if (!mNewEventOccurred && newest != null
                    && mState.compareTo(newest.getValue().mState) > 0) {//说明旧的状态在最新状态之前,那么通过upEvent就可以一步一步回到最新状态
                forwardPass(lifecycleOwner);//forwardPass里最重要就是用upEvent来计算下一个事件,以便最终通过一个个事件回到最新状态
            }
        }
        mNewEventOccurred = false;
    }

Lifecycle源码分析

  • 首先梳理下Activity和Lifecycle接口等的继承关系
//activity继承关系
AppCompatActivity-->FragmentActivity--> androidx.activity.ComponentActivity(ViewModelStoreOwner,LifecycleOwner)
-->androidx.core.app.ComponentActivity(LifecycleOwner)-->Activity

//fragment继承关系
androidx.fragment.app.Fragment(LifecycleOwner)
androidx.lifecycle.ReportFragment-->android.app.Fragment(过时)

//Lifecycle继承关系
LifecycleRegistryOwner(interface,已过时)-->LifecycleOwner(interface)
//Lifecycle只是抽象类,LifecycleRegistry是Lifecycle的唯一子类,实现了其抽象方法
LifecycleRegistry-->Lifecycle(abstract)

  • 上面中用–>表示继承关系,括号则表示实现的接口,于是我们可以看到我们经常使用的AppCompatActivity是继承于FragmentActivity,FragmentActivity又继承于ComponentActivity,ComponentActivity实现了LifecycleOwner接口,然而androidx.activity.ComponentActivity又继承了
    androidx.core.app.ComponentActivity,这个ComponentActivity也实现了LifecycleOwner接口,从这里可以看出要分析生命周期神器LIfecycle就要从LifecycleOwner这个接口入手了

LifecycleOwner

  • 那么LifecycleOwner接口到底长啥样呢
public interface LifecycleOwner {
    /**
     * Returns the Lifecycle of the provider.
     *
     * @return The lifecycle of the provider.
     */
    @NonNull
    Lifecycle getLifecycle();
}
  • LifecycleOwner 接口很简单,就是要返回一个Lifecycle,那么Lifecycle又是什么呢

Lifecycle

public abstract class Lifecycle {

    /**
     * Lifecycle coroutines extensions stashes the CoroutineScope into this field.
     *
     * @hide used by lifecycle-common-ktx
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    AtomicReference<Object> mInternalScopeRef = new AtomicReference<>();

    /**
     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
     * state.
     * <p>
     * The given observer will be brought to the current state of the LifecycleOwner.
     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
     *
     * @param observer The observer to notify.
     */
    @MainThread
    public abstract void addObserver(@NonNull LifecycleObserver observer);

    /**
     * Removes the given observer from the observers list.
     * <p>
     * If this method is called while a state change is being dispatched,
     * <ul>
     * <li>If the given observer has not yet received that event, it will not receive it.
     * <li>If the given observer has more than 1 method that observes the currently dispatched
     * event and at least one of them received the event, all of them will receive the event and
     * the removal will happen afterwards.
     * </ul>
     *
     * @param observer The observer to be removed.
     */
    @MainThread
    public abstract void removeObserver(@NonNull LifecycleObserver observer);

    /**
     * Returns the current state of the Lifecycle.
     *
     * @return The current state of the Lifecycle.
     */
    @MainThread
    @NonNull
    public abstract State getCurrentState();

    @SuppressWarnings("WeakerAccess")
    public enum Event {
        
        ON_CREATE,
        
        ON_START,
        
        ON_RESUME,
        
        ON_PAUSE,
        
        ON_STOP,
       
        ON_DESTROY,
        
        ON_ANY
    }

    /**
     * Lifecycle states. You can consider the states as the nodes in a graph and
     * {@link Event}s as the edges between these nodes.
     */
    @SuppressWarnings("WeakerAccess")
    public enum State {
        /**
         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
         */
        DESTROYED,

        /**
         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
         * the state when it is constructed but has not received
         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
         */
        INITIALIZED,

        /**
         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
         * </ul>
         */
        CREATED,

        /**
         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onStart() onStart} call;
         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
         * </ul>
         */
        STARTED,

        /**
         * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached after {@link android.app.Activity#onResume() onResume} is called.
         */
        RESUMED;

        /**
         * Compares if this State is greater or equal to the given {@code state}.
         *
         * @param state State to compare with
         * @return true if this State is greater or equal to the given {@code state}
         */
        public boolean isAtLeast(@NonNull State state) {
            return compareTo(state) >= 0;
        }
    }
}
  • 从代码里我们可以看到Lifecycle是一个抽象类,里面还定义了生命周期的相关事件和状态的枚举,还有添加删除观察者等,观察者关键就是LifecycleObserver 这个接口,让我们看看它的真面目

LifecycleObserver

/**
 * Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
 * {@link OnLifecycleEvent} annotated methods.
 * <p>
 * @see Lifecycle Lifecycle - for samples and usage patterns.
 */
public interface LifecycleObserver {

}
  • 我们发现这个接口里面啥也没有,仅仅只是一个标记接口,这个标记接口可以配合注解OnLifecycleEvent对生命周期进行观察(实际上就是通过反射的方式:ReflectiveGenericLifecycleObserver),如
class MyObserver : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(MyObserver())

  • 同时LifecycleEventObserver和FullLifecycleObserver是两个继承于LifecycleObserver的接口
interface FullLifecycleObserver extends LifecycleObserver {

    void onCreate(LifecycleOwner owner);

    void onStart(LifecycleOwner owner);

    void onResume(LifecycleOwner owner);

    void onPause(LifecycleOwner owner);

    void onStop(LifecycleOwner owner);

    void onDestroy(LifecycleOwner owner);
}
public interface LifecycleEventObserver extends LifecycleObserver {
    /**
     * Called when a state transition event happens.
     *
     * @param source The source of the event
     * @param event The event
     */
    void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);
}
  • 当我们调用LifecycleRegistry.addObserver时,内部其实使用的就是LifecycleEventObserver,它被包装在ObserverWithState中,当我们调用LifecycleRegistry.setCurrentState时,实际上就是调用LifecycleRegistry.moveToState,最终回调到LifecycleEventObserver里的onStateChanged方法

LifecycleRegistry

  • LifecycleRegistry是抽象类Lifecycle的唯一实现类
public class LifecycleRegistry extends Lifecycle {

    private FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap =
            new FastSafeIterableMap<>();
  	...
  	
    /**
     * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
     *
     * @param state new state
     */
    @MainThread
    public void setCurrentState(@NonNull State state) {
        moveToState(state);
    }

    /**
     * Sets the current state and notifies the observers.
     * <p>
     * Note that if the {@code currentState} is the same state as the last call to this method,
     * calling this method has no effect.
     *
     * @param event The event that was received
     */
    public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
        State next = getStateAfter(event);
        moveToState(next);
    }
	...
 }
  • 看到这里我们大概就有点眉目了,LifecycleRegistry就是最核心的所在,它继承了Lifecycle这个抽象类,拥有了添加观察者的能力,同时又对外提供了handleLifecycleEvent和setCurrentState两个方法,赋予外界设置状态的能力,也就是当状态发生变化时可以通知给LifecycleRegistry,然后LifecycleRegistry再将事件通知给观察者,观察者收到事件后就可以做相应的处理,比如在页面不可见时直接忽略页面的更新等
  • 那么LifecycleRegistry这个重要的对象是在哪被实例化的呢,我们可以在ComponentActivity中找到答案
  • 在androidx.activity.ComponentActivity中,以下只保留了关键代码
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner {
   private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
   private ViewModelStore mViewModelStore;
         
 	@NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
    
	@NonNull
    @Override
    public ViewModelStore getViewModelStore() {
    	...
     	if (mViewModelStore == null) {
         	mViewModelStore = new ViewModelStore();
     	}
        return mViewModelStore;
    }
 }

虽然androidx.core.app.ComponentActivity中也实现了LifecycleOwner接口,但已经被子类androidx.activity.ComponentActivity覆盖了,因此我们只需要关心androidx.activity.ComponentActivity的LifecycleOwner即可

  • 从上面代码中可以看出,LifecycleRegistry 实例就是在ComponentActivity中被new出来的,然后通过getLifecycle方法进行返回,这里就把LifecycleOwner这个接口串连起来了
  • 从上面的分析中我们就可以知道,我们可以通过LifecycleOwner接口拿到LifecycleRegistry 实例对象,然后进行添加观察者,这样就可以监听到生命周期事件了,LiveData就是这样干的,我们可以看看LivaData的observe方法
public abstract class LiveData<T> {
 ...
 @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }
 }
  • 实际上FragmentActivity中也实例了LifecycleRegistry对象,并且在内部类HostCallbacks 中覆盖了getLifecycle方法,主要代码如下
public class FragmentActivity extends ComponentActivity  {    

	...
	
	final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    /**
     * A {@link Lifecycle} that is exactly nested outside of when the FragmentController
     * has its state changed, providing the proper nesting of Lifecycle callbacks
     * <p>
     * TODO(b/127528777) Drive Fragment Lifecycle with LifecycleObserver
     */
    final LifecycleRegistry mFragmentLifecycleRegistry = new LifecycleRegistry(this);

	...

 	class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements
            ViewModelStoreOwner,
            OnBackPressedDispatcherOwner {
        public HostCallbacks() {
            super(FragmentActivity.this /*fragmentActivity*/);
        }

        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            // Instead of directly using the Activity's Lifecycle, we
            // use a LifecycleRegistry that is nested exactly outside of
            // when Fragments get their lifecycle changed
            // TODO(b/127528777) Drive Fragment Lifecycle with LifecycleObserver
            return mFragmentLifecycleRegistry;
        }
       }
  }
  • 对于以上FragmentActivity的Lifecycle 用法暂时未知作用,这里做下记录,留待以后深究

  • 另外还有其他问题就是生命周期究竟是在哪触发的呢,这个问题从下图我们就可以看到就是在Fragment和FragmentActivity中触发的

  • 在这里插入图片描述

问题

  • 下面以提问的方式对Lifecycle进行介绍

生命周期通知大部分是在Fragment中触发的,那没有Fragment的Activity呢?它的生命周期通知是啥时触发的

  • 一开始我也很奇怪,生命周期事件大多都是在Fragment中发出的,那Activity呢?后面才知道为了降低侵入性,增加了一个ReportFragment,ComponentActivity在onCreate时就执行了ReportFragment.injectIfNeededIn(this),从而将生命周期转移到Fragment,由Fragment进行触发了

  • ReportFragment的生命周期会通知到其对应Activity的LifecycleRegistry,再由LifecycleRegistry通知到观察者

    public class ComponentActivity extends Activity
            implements LifecycleOwner, KeyEventDispatcher.Component {
    ...
    
        @Override
        @SuppressWarnings("RestrictedApi")
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
             /重点//
    	     //关键就是这一步判断,它会添加ReportFragment
    	     /重点//
            ReportFragment.injectIfNeededIn(this);
        }
        ...
        }
    
    public class ReportFragment extends Fragment {
        private static final String REPORT_FRAGMENT_TAG = "androidx.lifecycle"
                + ".LifecycleDispatcher.report_fragment_tag";
    
        public static void injectIfNeededIn(Activity activity) {
            // ProcessLifecycleOwner should always correctly work and some activities may not extend
            // FragmentActivity from support lib, so we use framework fragments for activities
            android.app.FragmentManager manager = activity.getFragmentManager();
            if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
                manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
                // Hopefully, we are the first to make a transaction.
                manager.executePendingTransactions();
            }
        }
       }
    

为啥要用ViewModelProviders去创建ViewModel的实例

  • 首先说下原因,这是为了在适当的时机去销毁ViewModel所占用的资源,同时也为了实现多实例共享同一个ViewModel

  • 接着就说明是如何做到以上两点的,请看下面分析

  • 为了更直观些,现把Activity的继承关系梳理下,其中括号表示接口

    AppCompatActivity–>FragmentActivity–>ComponentActivity(LifecycleOwner,ViewModelStoreOwner)–>Activity

  • 为了让Activity自动管理ViewModel,我们就需要利用ViewModelProviders.of(this).get(XXViewModel.class);得到ViewModel对象

  • 至于为啥要用这个去创建ViewModel,是因为其在内部通过this对象拿到了ViewModelStore对象,这个ViewModelStore就是保存了当前对象所持有的ViewModel(说白了ViewModelStore就是一个HaspMap的简单封装而已,用于存储ViewModel),拿到ViewModelStore对象就可以通过XXViewModel.class这个key去得到ViewModel对象,这时ViewModelStore不一定存在此key对应的ViewModel对象,如果没有那么就通过KeyedFactory去创建一个了,再把刚创建的塞回ViewModelStore。

  • 这样跟我们单独new一个ViewModel对象有啥区别呢?我们单独new的对象并没有加入到ViewModelStore里管理,因此ViewModel是独立的,一个实例对象里的viewModel无法被其他实例访问到,假如我们的场景就是这样的,ViewModel并不需要在多个实例中共享一个,那么简单new一个也没啥问题;假如我们需要在多个实例中共享同一个ViewModel,那么通过ViewModelProviders来获取ViewModel就是比较好的办法了;

  • 那么什么场景下有共享ViewModel的需求呢,比如你想在Activity下的多个Fragment之间共用一个ViewModel,那么利用ViewModelProviders就是最方便快捷的选择。

  • 通过ViewModelProviders会将ViewModel存储进ViewmodelStore,ViewmodelStore由于ViewModelStoreOwner接口的原因可以被生命周期获取到进而进行管理,也就是会在适当的时机帮你销毁viewmodel的资源,同时有了ViewmodelStore的管理也能轻而易举实现多个Fragment共享同一个Viewmodel,显然自己手动new一个Viewmodel是没有这些好处的

  • 因此总结下来就是,Viewmodel最好不要手动创建,交给ViewModelProviders去管理,那么事情会变得简单而且不易出错。

  • 当然,如果你的Viewmodel非常简单,既没有共享的需求,也没什么资源需要特别释放(即不需要在ViewModel.onCleared里操作什么),那么手动创建也是可以的。

  • 如果你想说自己手动创建viewmodel后扔进viewmodelstore里,不好意思,办不到,因为目前Viewmodelstore的put方法并不是public的(反射不推荐),因此乖乖用ViewModelProviders吧

为啥说ViewModel放入Viewmodelstore后在Activity Destroy后会自动释放资源

  • 原因就在ComponentActivity里查找
  • 在这里插入图片描述
  • 从代码中我们很清楚看到ComponentActivity添加了一个观察者,当销毁时就会调用clear方法

为啥说在ViewModel不能持有Activity或Fragment的引用

  • 因为viewmodel的生命周期比activity长,如果activity configuration发生变化(比如屏幕旋转),新的实例会重新使用已存在的viewmodel,试想一下,如果viewmodel持有旧activity的引用,那么就会导致旧activity无法被回收,造成内存泄漏
  • 那么为何说ViewModel的生命周期比activity长呢,因为ViewModel是可以给多个实例共用的,这就导致ViewModel的生命周期大于引用它的多实例中生命周期最短的那一个,简单点来说,就是ViewModel被一个存活10秒和20秒的实例引用,那么ViewModel就存活20秒(20>10)

为啥说LiveData在Activity或Fragment不可见时不会去通知观察者

  • 我们知道在使用LiveData添加观察者时需要添加LifecycleOwner和Observer,添加的这两个对象实际上会被包装在LifecycleBoundObserver中,LifecycleBoundObserver.shouldBeActive就是根据当前状态判断是否需要通知观察者,而LiveData决定是否通知观察者就是在LiveData.considerNotify(ObserverWrapper observer)方法中处理的,可想而知,此方法中就是用到了LifecycleBoundObserver.shouldBeActive()作出决定的
  • 这里说明一下,LifecycleBoundObserver是LiveData的内部类,LifecycleBoundObserver继承了抽象类ObserverWrapper
  • LiveData部分代码如下,主要看重点标记的代码
    public abstract class LiveData<T> {
    private void considerNotify(ObserverWrapper observer) {
            if (!observer.mActive) {
                return;
            }
            /重点//
            //关键就是这一步判断,如果不可见直接就返回了,不会去通知观察者
            /重点//
            if (!observer.shouldBeActive()) {
                observer.activeStateChanged(false);
                return;
            }
            if (observer.mLastVersion >= mVersion) {
                return;
            }
            observer.mLastVersion = mVersion;
            //noinspection unchecked
            observer.mObserver.onChanged((T) mData);
        }
    
    
    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
            @NonNull
            final LifecycleOwner mOwner;
    
            LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
                super(observer);
                mOwner = owner;
            }
            /重点//
    		//关键就是这一步判断,最新状态如果是STARTED才会去通知观察者
    		/重点//
            @Override
            boolean shouldBeActive() {
                return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
            }
         }
    
     }
    

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值