EventBus详解(基于3.X)

基于Jar包方式实现EventBus

https://blog.csdn.net/weixin_37730482/article/details/72780281

 

 

基于Gradle依赖实现的EventBus

 

一.EventBus GitHub地址

https://github.com/greenrobot/EventBus

 

 

 

 

 

二.Gradle依赖

implementation 'org.greenrobot:eventbus:3.1.1'

 

 

 

 

 

 

三.图解

 

 

 

 

 

 

四.常用方法

 

<1> 注册

 EventBus.getDefault().register(this);

 

<2> 注销

EventBus.getDefault().unregister(this);

 

<3> 发送

EventBus.getDefault().post(new AnyEventType event);

 

可以看出 注册,注销,发送 三个方法和Jar包的方式一致。唯独接收的方式不一样。

 

<4> 接收&线程

根据官方文档来看,这种方式接收的方法名不固定,但是必须要在方法名上添加 @Subscribe。方法名不固定,那么怎么区分接收方所在的线程呢。

答案就是  枚举类 ThreadMode 共有四种常用取值 取值以及对应线程。

[1] POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程

     相当于Jar包方式的 onEvent方法。

[2] MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。

     相当于Jar包方式的 onEventMainThread方法。

[3] BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。

     相当于Jar包方式的 onEventBackgroundThread方法。

[4] ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

     相当于Jar包方式的 onEventAsync方法。

 

 

 

 

 

 

五.示例demo

 

接收方

package com.example.myapplication.eventbus;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.R;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;


/**
 * 模拟EventBus接收方
 */

public class EventBusActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eventbus);


        //注册EventBus
        EventBus.getDefault().register(this);


        //先发送数据
        TextView textView = findViewById(R.id.acivity_eventbus_textview1);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(EventBusActivity.this, EventBusOtherActivity.class);
                startActivity(intent);
            }
        });

    }

    /**
     * EventBus接收方法
     * ThreadMode.POSTING
     */

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessageDefaultEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.MAIN
     */

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageMainEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.BACKGROUND
     */

    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onMessageBackgroundEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.BACKGROUND result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.BACKGROUND 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.ASYNC
     */

    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessageAsyncEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.ASYNC result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.ASYNC 线程----:" + Thread.currentThread().getName());
    }

    /**
     * onDestroy方法
     */

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //EventBus注销
        EventBus.getDefault().unregister(this);
    }

}

 

 

发送方(UI线程发送)

package com.example.myapplication.eventbus;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.R;

import org.greenrobot.eventbus.EventBus;

/**
 * 模拟EventBus发送方
 */

public class EventBusOtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eventbusother);

        //UI线程发送EventBus
        TextView textView1 = findViewById(R.id.acivity_eventbusother_textview1);
        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MsgBean msgBean = new MsgBean();
                msgBean.setMsgType("MainUI");
                msgBean.setMsgContent("我是从UI线程发出的EventBus消息");
                msgBean.setMsgId("123");
                EventBus.getDefault().post(msgBean);
                Log.d("EventBusActivity", "UI线程发送EventBus 线程----:" + Thread.currentThread().getName());
            }
        });

        //子线程发送EventBus
        TextView textView2 = findViewById(R.id.acivity_eventbusother_textview2);
        textView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        MsgBean msgBean = new MsgBean();
                        msgBean.setMsgType("NewThreaUI");
                        msgBean.setMsgContent("我是从子线程线程发出的EventBus消息");
                        msgBean.setMsgId("456");
                        EventBus.getDefault().post(msgBean);
                        Log.d("EventBusActivity", "子线程发送EventBus 线程----:" + Thread.currentThread().getName());
                    }
                }).start();
            }
        });
    }

}

 

结果

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING result----:msgType:MainUI  msgContent:我是从UI线程发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING 线程----:main


*****************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN result----:msgType:MainUI  msgContent:我是从UI线程发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN 线程----:main


******************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.BACKGROUND result----:msgType:MainUI  msgContent:我是从UI线程发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.BACKGROUND 线程----:pool-1-thread-2


*********************************************************************************


D/EventBusActivity: EventBus接收方法 ThreadMode.ASYNC result----:msgType:MainUI  msgContent:我是从UI线程发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.ASYNC 线程----:pool-1-thread-1


************************************************************************************

D/EventBusActivity: UI线程发送EventBus 线程----:main

这样能准确说明四个取值对应的接收方所在的线程。

 

 

发送方(子线程发放) 接收方不变 结果


D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING result----:msgType:NewThreaUI  msgContent:我是从子线程线程发出的EventBus消息  msgId:456

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING 线程----:Thread-6


*************************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN result----:msgType:NewThreaUI  msgContent:我是从子线程线程发出的EventBus消息  msgId:456

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN 线程----:main

*************************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.BACKGROUND result----:msgType:NewThreaUI  msgContent:我是从子线程线程发出的EventBus消息  msgId:456

D/EventBusActivity: EventBus接收方法 ThreadMode.BACKGROUND 线程----:Thread-6

**************************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.ASYNC result----:msgType:NewThreaUI  msgContent:我是从子线程线程发出的EventBus消息  msgId:456

D/EventBusActivity: EventBus接收方法 ThreadMode.ASYNC 线程----:pool-1-thread-1

**************************************************************************************

D/EventBusActivity: 子线程发送EventBus 线程----:Thread-6


这样能准确说明四个取值对应的接收方所在的线程。

 

 

 

 

 

六.黏性事件

 

<1> 简介

所谓的黏性事件,就是指发送了该事件之后再订阅者依然能够接收到的事件。

使用黏性事件的时候有两个地方需要做些修改。

一个是订阅事件的地方。即添加  sticky 属性

@Subscribe(threadMode = ThreadMode.MAIN ,sticky = true)

另一个是发布事件的地方。即post方法改成postSticky方法

EventBus.getDefault().postSticky(msgBean);

 

 

<2> 代码

package com.example.myapplication.eventbus;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.R;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;


/**
 * 模拟EventBus接收方
 */

public class EventBusActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eventbus);


        //先发送数据
        TextView textView1 = findViewById(R.id.acivity_eventbus_textview1);
        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MsgBean msgBean = new MsgBean();
                msgBean.setMsgType("onClick");
                msgBean.setMsgContent("我是从onClick发出的EventBus消息");
                msgBean.setMsgId("123");
                EventBus.getDefault().postSticky(msgBean);
                Log.d("EventBusActivity", "onClick发送EventBus 线程----:" + Thread.currentThread().getName());
            }
        });

        TextView textView2 = findViewById(R.id.acivity_eventbus_textview2);
        textView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //注册EventBus
                EventBus.getDefault().register(EventBusActivity.this);
            }
        });

    }

    /**
     * EventBus接收方法
     * ThreadMode.POSTING
     */

    @Subscribe(threadMode = ThreadMode.POSTING, sticky = true)
    public void onMessageDefaultEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.MAIN
     */

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageMainEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.BACKGROUND
     */

    @Subscribe(threadMode = ThreadMode.BACKGROUND, sticky = false)
    public void onMessageBackgroundEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.BACKGROUND result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.BACKGROUND 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.ASYNC
     */

    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void onMessageAsyncEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.ASYNC result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.ASYNC 线程----:" + Thread.currentThread().getName());
    }

    /**
     * onDestroy方法
     */

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //EventBus注销
        EventBus.getDefault().unregister(EventBusActivity.this);
    }

}

 

 

<3> 结果

D/EventBusActivity: onClick发送EventBus 线程----:main


********************************************************************************

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING result----:msgType:onClick  msgContent:我是从onClick发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING 线程----:main


********************************************************************************


D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN result----:msgType:onClick  msgContent:我是从onClick发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN 线程----:main

 

 

<4> 说明

[1] 代码中

ThreadMode.POSTING和ThreadMode.MAIN属性对应的方法添加了sticky = true 两者都收到了粘性事件。

ThreadMode.BACKGROUND属性对应的方法添加了sticky = false 没有收到粘性事件。

ThreadMode.ASYNC属性对应的方法没有添加 sticky 没有收到粘性事件。

 

也就是说设置false和不设置是一样的都收不到粘性事件。

 

 

[2] 此方式接收方的方法命名有一定的规范

方法必须是public修饰符修饰,不能用static关键字修饰,不能是抽象的(abstract)。

方法需要用@Subscribe注解进行修饰。

 

[3] @Subscribe注解 其实有三个参数 

参数1:threadMode 决定接收方处于哪个线程中接收。

参数2:sticky 是否具有粘性事件。

参数3:priority 优先级 是一个整数类型的值,默认是0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。

 

前两个参数都说了 下面讲解第三个参数 

 

 

 

 

 

 

七.事件接收优先级

 

<1> 代码

package com.example.myapplication.eventbus;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.R;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;


/**
 * 模拟EventBus接收方
 */

public class EventBusActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eventbus);


        //先发送数据
        TextView textView1 = findViewById(R.id.acivity_eventbus_textview1);
        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MsgBean msgBean = new MsgBean();
                msgBean.setMsgType("onClick");
                msgBean.setMsgContent("我是从onClick发出的EventBus消息");
                msgBean.setMsgId("123");
                EventBus.getDefault().postSticky(msgBean);
                Log.d("EventBusActivity", "onClick发送EventBus 线程----:" + Thread.currentThread().getName());
            }
        });

        TextView textView2 = findViewById(R.id.acivity_eventbus_textview2);
        textView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //注册EventBus
                EventBus.getDefault().register(EventBusActivity.this);
            }
        });

    }

    /**
     * EventBus接收方法
     * ThreadMode.POSTING
     */

    @Subscribe(threadMode = ThreadMode.POSTING, sticky = true, priority = 10)
    public void onMessageDefaultEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.POSTING 线程----:" + Thread.currentThread().getName());
    }

    /**
     * EventBus接收方法
     * ThreadMode.MAIN
     */

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true, priority = 1000)
    public void onMessageMainEvent(MsgBean msgBean) {
        if (msgBean == null) {
            return;
        }
        String result = "msgType:" + msgBean.getMsgType() + "  msgContent:" + msgBean.getMsgContent() + "  msgId:" + msgBean.getMsgId();
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN result----:" + result);
        Log.d("EventBusActivity", "EventBus接收方法 ThreadMode.MAIN 线程----:" + Thread.currentThread().getName());
    }

    /**
     * onDestroy方法
     */

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //EventBus注销
        EventBus.getDefault().unregister(EventBusActivity.this);
    }

}

 

<2> 结果

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN result----:msgType:onClick  msgContent:我是从onClick发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.MAIN 线程----:main


*******************************************************************************


D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING result----:msgType:onClick  msgContent:我是从onClick发出的EventBus消息  msgId:123

D/EventBusActivity: EventBus接收方法 ThreadMode.POSTING 线程----:main


...

 

说明

即 priority值越高 优先级越高 最先收到消息。

 

 

 

 

 

八.Gradle依赖方式需配置代码混淆

依照官方文档需要添加混淆,否则打包后的APK会有收不到消息的现象。

-keepattributes *Annotation*
-keepclassmembers class * {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}

 

 

 

 

 

 

九.EventBus的线程切换原理 

 

由上可知 接收方的方法配置 ThreadMode 四个属性会有不同的线程 那么它们的原理是什么呢?

 

源码

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

 

private final Poster mainThreadPoster;

private final BackgroundPoster backgroundPoster;

private final AsyncPoster asyncPoster;

 

final class BackgroundPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!executorRunning) {
                executorRunning = true;
                eventBus.getExecutorService().execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                while (true) {
                    PendingPost pendingPost = queue.poll(1000);
                    if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
            }
        } finally {
            executorRunning = false;
        }
    }

}

 

class AsyncPoster implements Runnable, Poster {

    private final PendingPostQueue queue;
    private final EventBus eventBus;

    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }

}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值