2021年8月中旬流通领域重要生产资料市场价格变动情况

中国统计信息服务中心 卓创资讯   据对全国流通领域9大类50种重要生产资料市场价格的监测显示,2021年8月中旬与8月上旬相比,29种产品价格上涨,15种下降,6种持平。 2021年8月中旬流通领域重要生产资料市场价格变动情况  产品名称 单位 本期价格(元) 比上期 价格涨跌(元) 涨跌幅 (%) 一、黑色金属         螺纹钢(Φ16-25mm,HRB400E) 吨 5206.4 -24.2 -0.5 线材(Φ6.5mm,HPB300) 吨 5506.6 -13.7 -0.2 普通中板(20mm,Q235) 吨 5691.0 -13.6 -0.2 热轧普通薄板(3mm,Q235) 吨 5840.1 -22.5 -0.4 无缝钢管(219*6,20#) 吨 6146.1 -31.4 -0.5 角钢(5#) 吨 5613.2 -2.6 0.0 二、有色金属         电解铜(1#) 吨 69391.0 -820.0 -1.2 铝锭(A00) 吨 20199.2 324.9 1.6 铅锭(1#) 吨 15234.4 -304.9 -2.0 锌锭(0#) 吨 22725.0 292.1 1.3 三、化工产品         硫酸(98%) 吨 870.6 59.9 7.4 烧碱(液碱,32%) 吨 682.0 2.2 0.3 甲醇(优等品) 吨 2492.1 29.6 1.2 纯苯(石油苯,工业级) 吨 7482.3 -59.6 -0.8 苯乙烯(一级品) 吨 8837.8 -53.9 -0.6 聚乙烯(LLDPE,7042) 吨 8614.1 71.1 0.8 聚丙烯(T30S) 吨 8562.2 12.2 0.1 聚氯乙烯(SG5) 吨 9344.0 173.8 1.9 顺丁胶(BR9000) 吨 14258.8 264.5 1.9 涤纶长丝(FDY150D/96F) 吨 7771.9 -242.4 -3.0 四、石油天然气         液化天然气(LNG) 吨 5579.5 177.0 3.3 液化石油气(LPG) 吨 4773.8 26.4 0.6 汽油(95#国VI) 吨 8035.6 -143.9 -1.8 汽油(92#国VI) 吨 7794.3 -145.0 -1.8 柴油(0#国VI) 吨 6319.4 -58.7 -0.9 石蜡(58#半) 吨 7477.5 20.8 0.3 五、煤炭         无烟煤(洗中块) 吨 1521.3 74.2 5.1 普通混煤(4500大卡) 吨 775.0 0.0 0.0 山西大混(5000大卡) 吨 865.0 0.0 0.0 山西优混(5500大卡) 吨 945.0 0.0 0.0 大同混煤(5800大卡) 吨 970.0 0.0 0.0 焦煤(主焦煤) 吨 2525.0 225.0 9.8 焦炭(二级冶金焦) 吨 2880.1 232.5 8.8 六、非金属建材         普通硅酸盐水泥(P.O 42.5袋装) 吨 438.2 3.7 0.9 普通硅酸盐水泥(P.O 42.5散装) 吨 397.8 12.2 3.2 浮法平板玻璃(4.8/5mm) 吨 3140.4 2.9 0.1 七、农产品(主要用于加工)         稻米(粳稻米) 吨 3899.0 4.7 0.1 小麦(国标三等) 吨 2550.3 19.5 0.8 玉米(黄玉米二等) 吨 2707.7 23.0 0.9 棉花(皮棉,白棉三级) 吨 18402.5 499.1 2.8 生猪(外三元) 千克 14.9 -0.4 -2.6 大豆(黄豆) 吨 5234.6 24.6 0.5 豆粕(粗蛋白含量≥43%) 吨 3678.2 22.5 0.6 花生(油料花生米) 吨 8150.0 4.8 0.1 八、农业生产资料         尿素(小颗料) 吨 2727.9 -97.7 -3.5 复合肥(硫酸钾复合肥,氮磷钾含量45%) 吨 3131.3 7.2 0.2 农药(草甘膦,95%原药) 吨 51500.0 0.0 0.0 九、林产品         天然橡胶(标准胶SCRWF) 吨 13311.1 112.2 0.9 纸浆(漂白化学浆) 吨 5336.3 11.7 0.2 瓦楞纸(高强) 吨 4242.8 88.5 2.1 注:上期为2021年8月上旬。    附注   1.指标解释   流通领域重要生产资料市场价格,是指重要生产资料经营企业的批发和销售价格。与出厂价格不同,生产资料市场价格既包含出厂价格,也包含有经营企业的流通费用、利润和税费等。出厂价格与市场价格互相影响,存在时滞,两者的变动趋势在某一时间段内有可能会出现不完全一致的情况。   2.监测内容   流通领域重要生产资料市场价格监测内容包括9大类50种产品的价格。类别与产品规格说明详见附表。   3.监测范围   监测范围涵盖全国31个省(区、市)300多个交易市场的近2000家批发商、代理商、经销商等经营企业。   4.监测方法   价格监测方法包括信息员现场采价,电话、即时通讯工具和电子邮件询价等。   5.涨跌个数的统计   产品价格上涨、下降、持平个数按照涨跌幅(%)进行统计。   6.发布日期   每月4日、14日、24日发布上一旬数据,节假日顺延。 附表:流通领域重要生产资料市场价格监测产品规格说明表  序号 监测产品 规格型号 说明   一、黑色金属      1   螺纹钢 Φ16-25mm,HRB400E 屈服强度≥400MPa  2 线材 Φ6.5mm,HPB300 屈服强度≥300MPa  3 普通中板 20mm,Q235 屈服强度≥235MPa  4 热轧普通薄板 3mm,Q235 屈服强度≥235MPa  5 无缝钢管 219*6,20# 20#钢材,屈服强度≥245MPa  6 角钢 5# 屈服强度≥235MPa   二、有色金属      7 电解铜 1# 铜与银质量分数≥99.95%  8 铝锭 A00 铝质量分数≥99.7%  9 铅锭 1# 铅质量分数≥99.994% 10 锌锭 0# 锌质量分数≥99.995%   三、化工产品     11  硫酸 98% H2SO4质量分数≥98% 12 烧碱(液碱) 32% NaOH质量分数≥32%的离子膜碱 13 甲醇 优等品 水质量含量≤0.10% 14 纯苯(石油苯) 工业级 苯纯度≥99.8% 15 苯乙烯 一级品 纯度≥99.5% 16 聚乙烯(LLDPE) 7042 熔指:2.0±0.5g/10min 17 聚丙烯 T30S 熔指:3.0±0.9g/10min 18 聚氯乙烯 SG5 K值:66-68 19 顺丁胶 BR9000 块状、乳白色,灰分≤0.20% 20 涤纶长丝 FDY150D/96F 150旦,AA级   四、石油天然气     21 液化天然气 LNG 甲烷含量≥75%,密度≥430kg/m3 22 液化石油气 LPG 饱和蒸汽压1380-1430kPa 23 汽油 95#国VI 国VI标准 24 汽油 92#国VI 国VI标准 25 柴油 0#国VI 国VI标准 26 石蜡 58#半 熔点不低于58℃   五、煤炭     27 无烟煤 洗中块 挥发分≤8% 28 普通混煤 4500大卡 山西粉煤与块煤的混合煤,热值4500大卡 29 山西大混 5000大卡 质量较好的混煤,热值5000大卡 30 山西优混 5500大卡 优质的混煤,热值5500大卡 31 大同混煤 5800大卡 大同产混煤,热值5800大卡 32 焦煤  主焦煤 含硫量<1% 33 焦炭 二级冶金焦 12.01%≤灰分≤13.50%   六、非金属建材     34 普通硅酸盐水泥 P.O 42.5袋装 抗压强度42.5MPa 35 普通硅酸盐水泥 P.O 42.5散装 抗压强度42.5MPa 36 浮法平板玻璃 4.8/5mm 厚度为4.8/5mm的无色透明玻璃   七、农产品(主要用于加工)     37 稻米 粳稻米 杂质≤0.25%,水分≤15.5% 38 小麦 国标三等 杂质≤1.0%,水分≤12.5% 39 玉米 黄玉米二等 杂质≤1.0%,水分≤14.0% 40 棉花(皮棉) 白棉三级 纤维长度≥28mm,白或乳白色 41 生猪 外三元 三种外国猪杂交的肉食猪 42 大豆 黄豆 杂质≤1.0%,水分≤13.0% 43 豆粕 粗蛋白含量≥43% 粗蛋白≥43%,水分≤13.0% 44 花生 油料花生米 杂质≤1.0%,水分≤9.0%   八、农业生产资料     45 尿素 小颗料 总氮≥46%,水分≤1.0% 46 复合肥 硫酸钾复合肥 氮磷钾含量45% 47 农药(草甘膦) 95%原药 草甘膦质量分数≥95%   九、林产品     48 天然橡胶 标准胶SCRWF 杂质含量≤0.05%,灰分≤0.5% 49 纸浆 漂白化学浆 亮度≥80%,黏度≥600cm³/g 50 瓦楞纸 高强 80-160g/m2  

EventBus (四) Sticky事件

什么是Sticky事件?

关于Sticky事件有的同学可能不是很熟悉,Sticky的意思是粘性的。在Android开 发中,Sticky事件只指事件消费者在事件发布之后才注册的也能接收到该事件的特殊类型。Android中就有这样的实例,也就是Sticky Broadcast,即粘性广播。正常情况下如果发送者发送了某个广播,而接收者在这个广播发送后才注册自己的Receiver,这时接收者便无法接收到 刚才的广播,为此Android引入了StickyBroadcast,在广播发送结束后会保存刚刚发送的广播(Intent),这样当接收者注册完 Receiver后就可以接收到刚才已经发布的广播。这就使得我们可以预先处理一些事件,让有消费者时再把这些事件投递给消费者。

AndroidEventBus也提供了这样的功能,有所不同是AndroidEventBus会存储所有的Sticky事件,如果某个事件在不需 要再存储则需要手动进行移除。用户通过Sticky的形式发布事件,而消费者也需要通过Sticky的形式进行注册,当然这种注册除了可以接收 Sticky事件之外和常规的注册功能是一样的,其他类型的事件也会被正常处理。发布、接收Sticky事件的步骤有如下几步 :

1、发布Sticky事件;

EventBus.getDefault().postSticky("hello");

 

2、 某个时刻订阅者以Sticky的形式注册


public class MyReceiver {
    public MyReceiver() {
        EventBus.getDefault().registerSticky(this);
    }

    @Subscriber
    private void onStickyEvent(String info) {
        System.out.println("接收到事件 : " + info);
    }

}

当在某个时刻构造MyReceiver时就会将MyReceiver对象以Sticky的形式注册到EventBus中,此时先前发布的”hello”事件就会被MyReceiver对象接收到,因此就会执行onStickyEvent函数,在该函数中实现具体的逻辑即可。当然,不要忘了在某个时刻将MyReceiver注销,以弱引用的形式持有订阅者的功能还没有完成呐!整个过程就这样结束了~

Sticky事件的运用场景

上文中我们简单讲述了Sticky事件的基本使用步骤,这里我们以一个具体的示例来看看Sticky事件在开发中的使用场景。

在开发过程中,我们经常需要在Activity之间传值,我们的做法就是将数据塞到Intent中,并且为每个数据设置一个key。当我们传递的数 据是一个实体类时,我们的这个类还需要实现序列化接口,比如Parcelable或者Serializable。例如我们需要将一个用户对象传递到用户个 人信息展示页面。我们的常规做法是这样的:

User.java类 :

// 实体类实现序列化
public class User implements Parcelable {
        String name ;
        String phoneNum;
        // 其他字段省略

        public User(String aName) {
            name = aName ;
        }

        public User(Parcel in) {
            super(in);
            name = in.readString();
            phoneNum = in.readString();
        }
       // 代码省略

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeString(phoneNum);
        }

        public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
 }

 

然后我们要在某个Activity中将这个用户数据传递给个人信息界面ProfileActivity。代码如下 :

public class MainActivity extends Activity {

    // 某个点击事件
    @Override 
    public void onClick(View v) {
        User aUser = new User("Mr.Simple");
        aUser.phoneNum = "123456";
        // 其他数据

        Intent intent = new Intent(this, ProfileActivity.class);
        intent.putParcelable("user", aUser);
        startActivity(intent);
    }
}

 

在某个点击事件的处理函数中我们通过Intent将数据传递给ProfileActivity。我们再看看ProfileActivity从Intent中取出数据的代码。

public class ProfileActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);
        // 从Bundle中获取数据
        Bundle extraBundle = getIntent().getExtras();
        if (extraBundle != null) {
            User user = extraBundle.getParcelable("user");
        }
    }
}

 

OK,至此整个过程才算结束了。

大哥,我只是需要传个数据啊!何苦啊!
这种方式产生了很多的样板代码,也让逻辑变得更复杂,容易出错。我们再看看使用Sticky事件的实现方式。

User.java类 :

// 实体类实现序列化
public class User  {
        String name ;
        String phoneNum;
        // 其他字段省略

        public User(String aName) {
            name = aName ;
        }

        // 代码省略
 }

 

首先User类不需要实现序列化接口,避免了那些样板代码。然后在MainActivity中直接将User对象作为Sticky事件发布即可。

public class MainActivity extends Activity {

    // 某个点击事件
    @Override 
    public void onClick(View v) {
        User aUser = new User("Mr.Simple");
        aUser.phoneNum = "123456";
        // 其他数据
        // 发布Sticky事件
        EventBus.getDefault().postSticky(aUser);
        // 跳转到ProfileActivity页面
        Intent intent = new Intent(this, ProfileActivity.class);
        startActivity(intent);
    }
}

*后我们看看ProfileActivity如何接收数据。

public class ProfileActivity extends Activity {

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

        // 以Sticky的形式注册
        EventBus.getDefault().registerSticky(this);
    }

    @Subscriber
    private void onStickyEvent(User info){ // 这里实现你的逻辑即可, info即为传递过来的User对象 } }

在ProfileActivity中我们将ProfileActivity自身作为订阅者注册到总线当中,此时ProfileActivity就会 接收到上面发布的Sticky事件,这个事件对象就是User对象。此时就会触发ProfileActivity 中的receiveUser函数,info参数就是Sticky事件的那个用户信息对象,在receiveUser中实现自己的逻辑即可。

是的!我们并没有在onDestory中对订阅者进行注销,也就是没有调用EventBus的unregister()函数,这就是*新版的特性之一,也是目前唯一不需要手动注销的事件总线库。

EventBus (三) 源码解析 带你深入理解EventBus

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:【张鸿洋的博客】

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢?

1、概述

一般使用EventBus的组件类,类似下面这种方式:

  1. public class SampleComponent extends Fragment  
  2. {
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState)  
  5.     {
  6.         super.onCreate(savedInstanceState);  
  7.         EventBus.getDefault().register(this);  
  8.     }
  9.     public void onEventMainThread(param)  
  10.     {
  11.     }
  12.     public void onEventPostThread(param)  
  13.     {
  14.     }
  15.     public void onEventBackgroundThread(param)  
  16.     {
  17.     }
  18.     public void onEventAsync(param)  
  19.     {
  20.     }
  21.     @Override  
  22.     public void onDestroy()  
  23.     {
  24.         super.onDestroy();  
  25.         EventBus.getDefault().unregister(this);  
  26.     }
  27. }

大多情况下,都会在onCreate中进行register,在onDestory中进行unregister ;

看完代码大家或许会有一些疑问:

1、代码中还有一些以onEvent开头的方法,这些方法是干嘛的呢?

在回答这个问题之前,我有一个问题,你咋不问register(this)是干嘛的呢?其实register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储。现在知道onEvent开头的方法是干嘛的了吧。

2、那onEvent后面的那些MainThread应该是什么标志吧?

嗯,是的,onEvent后面可以写四种,也就是上面出现的四个方法,决定了当前的方法*终在什么线程运行,怎么运行,可以参考上一篇博客或者细细往下看。

 

既然register了,那么肯定得说怎么调用是吧。

  1. EventBus.getDefault().post(param);

调用很简单,一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。

现在有没有觉得,撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。

那么,我告诉你,它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?

 

下面我们就去看看EventBus的register和post真面目。

2、register

EventBus.getDefault().register(this);

首先:

EventBus.getDefault()其实就是个单例,和我们传统的getInstance一个意思:

  1. /** Convenience singleton for apps using a process-wide EventBus instance. */  
  2.    public static EventBus getDefault() {  
  3.        if (defaultInstance == null) {  
  4.            synchronized (EventBus.class) {  
  5.                if (defaultInstance == null) {  
  6.                    defaultInstance = new EventBus();  
  7.                }
  8.            }
  9.        }
  10.        return defaultInstance;  
  11.    }

使用了双重判断的方式,防止并发的问题,还能*大的提高效率。

然后register应该是一个普通的方法,我们去看看:

register公布给我们使用的有4个:

  1.  public void register(Object subscriber) {  
  2.         register(subscriber, DEFAULT_METHOD_NAME, false, 0);  
  3.     }
  4.  public void register(Object subscriber, int priority) {  
  5.         register(subscriber, DEFAULT_METHOD_NAME, false, priority);  
  6.     }
  7. public void registerSticky(Object subscriber) {  
  8.         register(subscriber, DEFAULT_METHOD_NAME, true, 0);  
  9.     }
  10. public void registerSticky(Object subscriber, int priority) {  
  11.         register(subscriber, DEFAULT_METHOD_NAME, true, priority);  
  12.     }

本质上就调用了同一个:

  1. private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {  
  2.         List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  3.                 methodName);
  4.         for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  5.             subscribe(subscriber, subscriberMethod, sticky, priority);
  6.         }
  7.     }

四个参数

subscriber 是我们扫描类的对象,也就是我们代码中常见的this;

methodName 这个是写死的:“onEvent”,用于确定扫描什么开头的方法,可见我们的类中都是以这个开头。

sticky 这个参数,解释源码的时候解释,暂时不用管

priority 优先级,优先级越高,在调用的时候会越先调用。

下面开始看代码:

  1. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  2.                 methodName);

调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。

那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,*后返回一个List。下面看代码:

  1. List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
  2.         String key = subscriberClass.getName() + ‘.’ + eventMethodName;  
  3.         List<SubscriberMethod> subscriberMethods;
  4.         synchronized (methodCache) {  
  5.             subscriberMethods = methodCache.get(key);
  6.         }
  7.         if (subscriberMethods != null) {  
  8.             return subscriberMethods;  
  9.         }
  10.         subscriberMethods = new ArrayList<SubscriberMethod>();  
  11.         Class<?> clazz = subscriberClass;
  12.         HashSet<String> eventTypesFound = new HashSet<String>();  
  13.         StringBuilder methodKeyBuilder = new StringBuilder();  
  14.         while (clazz != null) {  
  15.             String name = clazz.getName();
  16.             if (name.startsWith(“<a href=”http://lib.csdn.net /base/17″ class=”replace_word” title=”Java EE知识 库” target=”_blank” style=”color:#df3434; font-weight:bold;”>java</a>.”) || name.startsWith(“javax.”) || name.startsWith(“android.”)) {  
  17.                 // Skip system classes, this just degrades performance  
  18.                 break;  
  19.             }
  20.             // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  21.             Method[] methods = clazz.getMethods();
  22.             for (Method method : methods) {  
  23.                 String methodName = method.getName();
  24.                 if (methodName.startsWith(eventMethodName)) {  
  25.                     int modifiers = method.getModifiers();  
  26.                     if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {  
  27.                         Class<?>[] parameterTypes = method.getParameterTypes();
  28.                         if (parameterTypes.length == 1) {  
  29.                             String modifierString = methodName.substring(eventMethodName.length());
  30.                             ThreadMode threadMode;
  31.                             if (modifierString.length() == 0) {  
  32.                                 threadMode = ThreadMode.PostThread;
  33.                             } else if (modifierString.equals(“MainThread”)) {  
  34.                                 threadMode = ThreadMode.MainThread;
  35.                             } else if (modifierString.equals(“BackgroundThread”)) {  
  36.                                 threadMode = ThreadMode.BackgroundThread;
  37.                             } else if (modifierString.equals(“Async”)) {  
  38.                                 threadMode = ThreadMode.Async;
  39.                             } else {  
  40.                                 if (skipMethodVerificationForClasses.containsKey(clazz)) {  
  41.                                     continue;  
  42.                                 } else {  
  43.                                     throw new EventBusException(“Illegal onEvent method, check for typos: ” + method);  
  44.                                 }
  45.                             }
  46.                             Class<?> eventType = parameterTypes[0];  
  47.                             methodKeyBuilder.setLength(0);  
  48.                             methodKeyBuilder.append(methodName);
  49.                             methodKeyBuilder.append(‘>’).append(eventType.getName());  
  50.                             String methodKey = methodKeyBuilder.toString();
  51.                             if (eventTypesFound.add(methodKey)) {  
  52.                                 // Only add if not already found in a sub class  
  53.                                 subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));  
  54.                             }
  55.                         }
  56.                     } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {  
  57.                         Log.d(EventBus.TAG, “Skipping method (not public, static or abstract): ” + clazz + “.”  
  58.                                 + methodName);
  59.                     }
  60.                 }
  61.             }
  62.             clazz = clazz.getSuperclass();
  63.         }
  64.         if (subscriberMethods.isEmpty()) {  
  65.             throw new EventBusException(“Subscriber ” + subscriberClass + ” has no public methods called ”  
  66.                     + eventMethodName);
  67.         } else {  
  68.             synchronized (methodCache) {  
  69.                 methodCache.put(key, subscriberMethods);
  70.             }
  71.             return subscriberMethods;  
  72.         }
  73.     }

呵,代码还真长;不过我们直接看核心部分:

22行:看到没,clazz.getMethods();去得到所有的方法:

23-62行:就开始遍历每一个方法了,去匹配封装了。

25-29行:分别判断了是否以onEvent开头,是否是public且非static和abstract方法,是否是一个参数。如果都复合,才进入封装的部分。

32-45行:也比较简单,根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。

*后在54行:将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,*终放回。

注意下63行:clazz = clazz.getSuperclass();可以看到,会扫描所有的父类,不仅仅是当前类。

继续回到register:

  1. for (SubscriberMethod subscriberMethod : subscriberMethods) {  
  2.             subscribe(subscriber, subscriberMethod, sticky, priority);
  3.         }

for循环扫描到的方法,然后去调用suscribe方法。

  1. // Must be called in synchronized block  
  2.    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  
  3.        subscribed = true;  
  4.        Class<?> eventType = subscriberMethod.eventType;
  5.        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
  6.        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);  
  7.        if (subscriptions == null) {  
  8.            subscriptions = new CopyOnWriteArrayList<Subscription>();  
  9.            subscriptionsByEventType.put(eventType, subscriptions);
  10.        } else {  
  11.            for (Subscription subscription : subscriptions) {  
  12.                if (subscription.equals(newSubscription)) {  
  13.                    throw new EventBusException(“Subscriber ” + subscriber.getClass() + ” already registered to event ”  
  14.                            + eventType);
  15.                }
  16.            }
  17.        }
  18.        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
  19.        // subscriberMethod.method.setAccessible(true);  
  20.        int size = subscriptions.size();  
  21.        for (int i = 0; i <= size; i++) {  
  22.            if (i == size || newSubscription.priority > subscriptions.get(i).priority) {  
  23.                subscriptions.add(i, newSubscription);
  24.                break;  
  25.            }
  26.        }
  27.        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
  28.        if (subscribedEvents == null) {  
  29.            subscribedEvents = new ArrayList<Class<?>>();  
  30.            typesBySubscriber.put(subscriber, subscribedEvents);
  31.        }
  32.        subscribedEvents.add(eventType);
  33.        if (sticky) {  
  34.            Object stickyEvent;
  35.            synchronized (stickyEvents) {  
  36.                stickyEvent = stickyEvents.get(eventType);
  37.            }
  38.            if (stickyEvent != null) {  
  39.                // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)  
  40.                // –> Strange corner case, which we don’t take care of here.  
  41.                postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
  42.            }
  43.        }
  44.    }

我们的subscriberMethod中保存了method, threadMode, eventType,上面已经说了;

4-17行:根据subscriberMethod.eventType,去subscriptionsByEventType去查找一个CopyOnWriteArrayList<Subscription> ,如果没有则创建。

顺便把我们的传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority);

这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 这个Map其实就是EventBus存储方法的地方,一定要记住!

22-28行:实际上,就是添加newSubscription;并且是按照优先级添加的。可以看到,优先级越高,会插到在当前List的前面。

30-35行:根据subscriber存储它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;知道就行,非核心代码,主要用于isRegister的判断。

37-47行:判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。

postToSubscription这个方法,我们在post的时候会介绍。

 

到此,我们register就介绍完了。

你只要记得一件事:扫描了所有的方法,把匹配的方法*终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。

 

3、post

register完毕,知道了EventBus如何存储我们的方法了,下面看看post它又是如何调用我们的方法的。

再看源码之前,我们猜测下:register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。

下面看源码:

  1. /** Posts the given event to the event bus. */  
  2.    public void post(Object event) {  
  3.        PostingThreadState postingState = currentPostingThreadState.get();
  4.        List<Object> eventQueue = postingState.eventQueue;
  5.        eventQueue.add(event);
  6.        if (postingState.isPosting) {  
  7.            return;  
  8.        } else {  
  9.            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
  10.            postingState.isPosting = true;  
  11.            if (postingState.canceled) {  
  12.                throw new EventBusException(“Internal error. Abort state was not reset”);  
  13.            }
  14.            try {  
  15.                while (!eventQueue.isEmpty()) {  
  16.                    postSingleEvent(eventQueue.remove(0), postingState);  
  17.                }
  18.            } finally {  
  19.                postingState.isPosting = false;  
  20.                postingState.isMainThread = false;  
  21.            }
  22.        }
  23.    }

currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState;PostingThreadState包含了一个eventQueue和一些标志位。

  1. private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {  
  2.        @Override  
  3.        protected PostingThreadState initialValue() {  
  4.            return new PostingThreadState();  
  5.        }
  6.    }

把我们传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。

10行:判断当前是否是UI线程。

16-18行:遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。

这里大家会不会有疑问,每次post都会去调用整个队列么,那么不会造成方法多次调用么?

可以看到第7-8行,有个判断,就是防止该问题的,isPosting=true了,就不会往下走了。

 

下面看postSingleEvent

  1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  
  2.         Class<? extends Object> eventClass = event.getClass();  
  3.         List<Class<?>> eventTypes = findEventTypes(eventClass);
  4.         boolean subscriptionFound = false;  
  5.         int countTypes = eventTypes.size();  
  6.         for (int h = 0; h < countTypes; h++) {  
  7.             Class<?> clazz = eventTypes.get(h);
  8.             CopyOnWriteArrayList<Subscription> subscriptions;
  9.             synchronized (this) {  
  10.                 subscriptions = subscriptionsByEventType.get(clazz);
  11.             }
  12.             if (subscriptions != null && !subscriptions.isEmpty()) {  
  13.                 for (Subscription subscription : subscriptions) {  
  14.                     postingState.event = event;
  15.                     postingState.subscription = subscription;
  16.                     boolean aborted = false;  
  17.                     try {  
  18.                         postToSubscription(subscription, event, postingState.isMainThread);
  19.                         aborted = postingState.canceled;
  20.                     } finally {  
  21.                         postingState.event = null;  
  22.                         postingState.subscription = null;  
  23.                         postingState.canceled = false;  
  24.                     }
  25.                     if (aborted) {  
  26.                         break;  
  27.                     }
  28.                 }
  29.                 subscriptionFound = true;  
  30.             }
  31.         }
  32.         if (!subscriptionFound) {  
  33.             Log.d(TAG, “No subscribers registered for event ” + eventClass);  
  34.             if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {  
  35.                 post(new NoSubscriberEvent(this, event));  
  36.             }
  37.         }
  38.     }

将我们的event,即post传入的实参;以及postingState传入到postSingleEvent中。

2-3 行:根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class,以及 父类和接口的Class类型;主要用于匹配,比如你传入Dog extends Dog,他会把Animal也装到该List中。

6-31行:遍历所有的Class,到subscriptionsByEventType去查找subscriptions;哈哈,熟不熟悉,还记得我们register里面把方法存哪了不?

是不是就是这个Map;

12-30行:遍历每个subscription,依次去调用postToSubscription(subscription, event, postingState.isMainThread);
这个方法就是去反射执行方法了,大家还记得在register,if(sticky)时,也会去执行这个方法。

下面看它如何反射执行:

  1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  
  2.         switch (subscription.subscriberMethod.threadMode) {  
  3.         case PostThread:  
  4.             invokeSubscriber(subscription, event);
  5.             break;  
  6.         case MainThread:  
  7.             if (isMainThread) {  
  8.                 invokeSubscriber(subscription, event);
  9.             } else {  
  10.                 mainThreadPoster.enqueue(subscription, event);
  11.             }
  12.             break;  
  13.         case BackgroundThread:  
  14.             if (isMainThread) {  
  15.                 backgroundPoster.enqueue(subscription, event);
  16.             } else {  
  17.                 invokeSubscriber(subscription, event);
  18.             }
  19.             break;  
  20.         case Async:  
  21.             asyncPoster.enqueue(subscription, event);
  22.             break;  
  23.         default:  
  24.             throw new IllegalStateException(“Unknown thread mode: ” + subscription.subscriberMethod.threadMode);  
  25.         }
  26.     }

前面已经说过subscription包含了所有执行需要的东西,大致有:subscriber, subscriberMethod(method, threadMode, eventType), priority;

那么这个方法:*步根据threadMode去判断应该在哪个线程去执行该方法;
case PostThread:

  1. void invokeSubscriber(Subscription subscription, Object event) throws Error {  
  2.           subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

直接反射调用;也就是说在当前的线程直接调用该方法;

case MainThread:

首 先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方 法。说白了就是通过Handler去发送消息,然后执行的。

case BackgroundThread:

如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,*终由Eventbus中的一个线程池去调用

executorService = Executors.newCachedThreadPool();。

case Async:将任务加入到后台的一个队列,*终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。

这么说BackgroundThread和Async有什么区别呢?

BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。

Async则会动态控制并发。

 

到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~

其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

 

4、其余方法

介绍了register和post;大家获取还能想到一个词sticky,在register中,如何sticky为true,会去stickyEvents去查找事件,然后立即去post;

那么这个stickyEvents何时进行保存事件呢?

其实evevntbus中,除了post发布事件,还有一个方法也可以:

  1. public void postSticky(Object event) {  
  2.        synchronized (stickyEvents) {  
  3.            stickyEvents.put(event.getClass(), event);
  4.        }
  5.        // Should be posted after it is putted, in case the subscriber wants to remove immediately  
  6.        post(event);
  7.    }

和post功能类似,但是会把方法存储到stickyEvents中去;

大家再去看看EventBus中所有的public方法,无非都是一些状态判断,获取事件,移除事件的方法;没什么好介绍的,基本见名知意。

 

好了,到此我们的源码解析就结束了,希望大家不仅能够了解这些优秀框架的内部机理,更能够体会到这些框架的很多细节之处,并发的处理,很多地方,为什么它这么做等等。

EventBus (二) 使用详解——EventBus使用进阶

相关文章:

1、《EventBus使用详解(一)——初步使用EventBus》

2、《EventBus使用详解(二)——EventBus使用进阶》

 

一、概述

前一篇给大家装简单演示了EventBus的onEventMainThread()函数的接收,其实EventBus还有另外有个不同的函数,他们分别是:

1、onEvent
2、onEventMainThread
3、onEventBackgroundThread
4、onEventAsync

这四种订阅函数都是使用onEvent开头的,它们的功能稍有不同,在介绍不同之前先介绍两个概念:
告知观察者事件发生时通过EventBus.post函数实现,这个过程叫做事件的发布,观察者被告知事件发生叫做事件的接收,是通过下面的订阅函数实现的。

onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.

二、实战

1、解析

上面列出的这四个函数,关键问题在于,我们怎么指定调用哪个函数呢?

我们先研究一下,上一篇中是怎么调用的onEventMainThread函数,除了在接收端注册与反注册以后,关键问题在于新建的一个类:

新建一个类:

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

发送时:

  1. EventBus.getDefault().post(new FirstEvent(“FirstEvent btn clicked”));    

接收时:

  1. public void onEventMainThread(FirstEvent event) {    
  2.     ……
  3. }

发现什么问题了没?

没错,发送时发送的是这个类的实例,接收时参数就是这个类实例。

所以!!!!!!当发过来一个消息的时候,EventBus怎么知道要调哪个函数呢,就看哪个函数传进去的参数是这个类的实例,哪个是就调哪个。那如果有两个是呢,那两个都会被调用!!!!

为了证明这个问题,下面写个例子,先看下效果

2、实例

先看看我们要实现的效果:

这次我们在上一篇的基础上,新建三个类:FirstEvent、SecondEvent、ThirdEvent,在第二个Activity中发送请求,在MainActivity中接收这三个类的实例,接收时的代码为:

  1. public void onEventMainThread(FirstEvent event) {  
  2.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  3. }
  4. public void onEventMainThread(SecondEvent event) {  
  5.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  6. }
  7. public void onEvent(ThirdEvent event) {  
  8.     Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  9. }

使用两个onEventMainThread分别接收FirstEvent实例的消息和SecondEvent实例的消息,使用onEvent接收ThirdEvent实例的消息。界面操作及结果如下:

%title插图%num

Log输出结果:

%title插图%num

可 以看到,在发送FirstEvent时,在MainActiviy中虽然有三个函数,但只有*个onEventMainThread函数的接收参数是 FirstEvent,所以会传到它这来接收。所以这里识别调用EventBus中四个函数中哪个函数,是通过参数中的实例来决定的。

因为我们是在上一篇例子的基础上完成的,所以这里的代码就不详细写了,只写改动的部分。

1、三个类

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }
  1. package com.harvic.other;  
  2. public class SecondEvent{  
  3.     private String mMsg;  
  4.     public SecondEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = “MainEvent:”+msg;  
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }
  1. package com.harvic.other;  
  2. public class ThirdEvent {  
  3.     private String mMsg;  
  4.     public ThirdEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

2、发送

然后在SecondActivity中新建三个按钮,分别发送不同的类的实例,代码如下:

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. public class SecondActivity extends Activity {  
  11.     private Button btn_FirstEvent, btn_SecondEvent, btn_ThirdEvent;  
  12.     @Override  
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.activity_second);
  16.         btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
  17.         btn_SecondEvent = (Button) findViewById(R.id.btn_second_event);
  18.         btn_ThirdEvent = (Button) findViewById(R.id.btn_third_event);
  19.         btn_FirstEvent.setOnClickListener(new View.OnClickListener() {  
  20.             @Override  
  21.             public void onClick(View v) {  
  22.                 // TODO Auto-generated method stub  
  23.                 EventBus.getDefault().post(
  24.                         new FirstEvent(“FirstEvent btn clicked”));  
  25.             }
  26.         });
  27.         btn_SecondEvent.setOnClickListener(new View.OnClickListener() {  
  28.             @Override  
  29.             public void onClick(View v) {  
  30.                 // TODO Auto-generated method stub  
  31.                 EventBus.getDefault().post(
  32.                         new SecondEvent(“SecondEvent btn clicked”));  
  33.             }
  34.         });
  35.         btn_ThirdEvent.setOnClickListener(new View.OnClickListener() {  
  36.             @Override  
  37.             public void onClick(View v) {  
  38.                 // TODO Auto-generated method stub  
  39.                 EventBus.getDefault().post(
  40.                         new ThirdEvent(“ThirdEvent btn clicked”));  
  41.             }
  42.         });
  43.     }
  44. }

3、接收

在 MainActivity中,除了注册与注册,我们利用onEventMainThread(FirstEvent event)来接收来自FirstEvent的消息,使用onEventMainThread(SecondEvent event)接收来自SecondEvent 实例的消息,使用onEvent(ThirdEvent event) 来接收ThirdEvent 实例的消息。

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.view.Menu;  
  11. import android.view.MenuItem;  
  12. import android.view.View;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15. public class MainActivity extends Activity {  
  16.     Button btn;
  17.     TextView tv;
  18.     EventBus eventBus;
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);
  23.         EventBus.getDefault().register(this);  
  24.         btn = (Button) findViewById(R.id.btn_try);
  25.         btn.setOnClickListener(new View.OnClickListener() {  
  26.             @Override  
  27.             public void onClick(View v) {  
  28.                 // TODO Auto-generated method stub  
  29.                 Intent intent = new Intent(getApplicationContext(),  
  30.                         SecondActivity.class);  
  31.                 startActivity(intent);
  32.             }
  33.         });
  34.     }
  35.     public void onEventMainThread(FirstEvent event) {  
  36.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  37.     }
  38.     public void onEventMainThread(SecondEvent event) {  
  39.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  40.     }
  41.     public void onEvent(ThirdEvent event) {  
  42.         Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  43.     }
  44.     @Override  
  45.     protected void onDestroy() {  
  46.         // TODO Auto-generated method stub  
  47.         super.onDestroy();  
  48.         EventBus.getDefault().unregister(this);  
  49.     }
  50. }

到这里,代码就结束 了,从上面的代码,我们可以看到,EventBus是怎么接收消息的,是根据参数中类的实例的类型的判定的,所以当如果我们在接收时,同一个类的实例参数有两个函数来接收会怎样?答案是,这两个函数都会执行,下面实验一下:

在MainActivity中接收时,我们在接收SecondEvent时,在上面onEventMainThread基础上另加一个onEventBackgroundThread和onEventAsync,即下面的代码:

  1. //SecondEvent接收函数一  
  2. public void onEventMainThread(SecondEvent event) {  
  3.     Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  4. }
  5. //SecondEvent接收函数二  
  6. public void onEventBackgroundThread(SecondEvent event){  
  7.     Log.d(“harvic”, “onEventBackground收到了消息:” + event.getMsg());  
  8. }
  9. //SecondEvent接收函数三  
  10. public void onEventAsync(SecondEvent event){  
  11.     Log.d(“harvic”, “onEventAsync收到了消息:” + event.getMsg());  
  12. }

完整的代码在这里:

  1. package com.harvic.tryeventbus2;  
  2. import com.harvic.other.FirstEvent;  
  3. import com.harvic.other.SecondEvent;  
  4. import com.harvic.other.ThirdEvent;  
  5. import de.greenrobot.event.EventBus;  
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10. import android.view.Menu;  
  11. import android.view.MenuItem;  
  12. import android.view.View;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15. public class MainActivity extends Activity {  
  16.     Button btn;
  17.     TextView tv;
  18.     EventBus eventBus;
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_main);
  23.         EventBus.getDefault().register(this);  
  24.         btn = (Button) findViewById(R.id.btn_try);
  25.         btn.setOnClickListener(new View.OnClickListener() {  
  26.             @Override  
  27.             public void onClick(View v) {  
  28.                 // TODO Auto-generated method stub  
  29.                 Intent intent = new Intent(getApplicationContext(),  
  30.                         SecondActivity.class);  
  31.                 startActivity(intent);
  32.             }
  33.         });
  34.     }
  35.     public void onEventMainThread(FirstEvent event) {  
  36.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  37.     }
  38.     //SecondEvent接收函数一  
  39.     public void onEventMainThread(SecondEvent event) {  
  40.         Log.d(“harvic”, “onEventMainThread收到了消息:” + event.getMsg());  
  41.     }
  42.     //SecondEvent接收函数二  
  43.     public void onEventBackgroundThread(SecondEvent event){  
  44.         Log.d(“harvic”, “onEventBackground收到了消息:” + event.getMsg());  
  45.     }
  46.     //SecondEvent接收函数三  
  47.     public void onEventAsync(SecondEvent event){  
  48.         Log.d(“harvic”, “onEventAsync收到了消息:” + event.getMsg());  
  49.     }
  50.     public void onEvent(ThirdEvent event) {  
  51.         Log.d(“harvic”, “OnEvent收到了消息:” + event.getMsg());  
  52.     }
  53.     @Override  
  54.     protected void onDestroy() {  
  55.         // TODO Auto-generated method stub  
  56.         super.onDestroy();  
  57.         EventBus.getDefault().unregister(this);  
  58.     }
  59. }

经过上面的分析,当发送SecondEvent实例的消息过来的时候,这三个函数会同时接收到并各自执行,所以当点击Second Event这个button的时候,会出现下面的结果:

%title插图%num

好啦,这篇就到了,讲来讲去就是说一个问题:消息的接收是根据参数中的类名来决定执行哪一个的;

EventBus (一) 使用详解——初步使用EventBus

目录(?)[+]

前言:EventBus是上周项目中用到的,网上的文章大都一样,或者过时,有用的没几篇,经过琢磨,请教他人,也终于弄清楚点眉目,记录下来分享给大家。

 

相关文章:

1、《EventBus使用详解(一)——初步使用EventBus》

2、《EventBus使用详解(二)——EventBus使用进阶》

 

一、概述

EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
1、下载EventBus的类库
源码:https://github.com/greenrobot/EventBus

2、基本使用

(1)自定义一个类,可以是空类,比如:

  1. public class AnyEventType {  
  2.      public AnyEventType(){}  
  3.  }

(2)在要接收消息的页面注册:

  1. eventBus.register(this);  

(3)发送消息

  1. eventBus.post(new AnyEventType event);  

(4)接受消息的页面实现(共有四个函数,各功能不同,这是其中之一,可以选择性的实现,这里先实现一个):

  1. public void onEvent(AnyEventType event) {}  

(5)解除注册

  1. eventBus.unregister(this);  

顺序就是这么个顺序,可真正让自己写,估计还是云里雾里的,下面举个例子来说明下。

首先,在EventBus中,获取实例的方法一般是采用EventBus.getInstance()来获取默认的EventBus实例,当然你也可以new一个又一个,个人感觉还是用默认的比较好,以防出错。

二、实战

先给大家看个例子:

当击btn_try按钮的时候,跳到第二个Activity,当点击第二个activity上面的First Event按钮的时候向*个Activity发送消息,当*个Activity收到消息后,一方面将消息Toast显示,一方面放入textView中显示。

%title插图%num

按照下面的步骤,下面来建这个工程:

1、基本框架搭建

想必大家从一个Activity跳转到第二个Activity的程序应该都会写,这里先稍稍把两个Activity跳转的代码建起来。后面再添加EventBus相关的玩意。

MainActivity布局(activity_main.xml)

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical”>  
  6.     <Button   
  7.         android:id=“@+id/btn_try”  
  8.         android:layout_width=“match_parent”  
  9.         android:layout_height=“wrap_content”  
  10.         android:text=“btn_bty”/>  
  11.     <TextView   
  12.         android:id=“@+id/tv”  
  13.         android:layout_width=“wrap_content”  
  14.         android:layout_height=“match_parent”/>  
  15. </LinearLayout>  

新建一个Activity,SecondActivity布局(activity_second.xml)

  1. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”  
  2.     xmlns:tools=“http://schemas.android.com/tools”  
  3.     android:layout_width=“match_parent”  
  4.     android:layout_height=“match_parent”  
  5.     android:orientation=“vertical”  
  6.     tools:context=“com.harvic.try_eventbus_1.SecondActivity” >  
  7.     <Button   
  8.         android:id=“@+id/btn_first_event”  
  9.         android:layout_width=“match_parent”  
  10.         android:layout_height=“wrap_content”  
  11.         android:text=“First Event”/>  
  12. </LinearLayout>  

MainActivity.java (点击btn跳转到第二个Activity)

  1. public class MainActivity extends Activity {  
  2.     Button btn;
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);
  7.         btn = (Button) findViewById(R.id.btn_try);
  8.         btn.setOnClickListener(new View.OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View v) {  
  11.                 // TODO Auto-generated method stub  
  12.                 Intent intent = new Intent(getApplicationContext(),  
  13.                         SecondActivity.class);  
  14.                 startActivity(intent);
  15.             }
  16.         });
  17.     }
  18. }

到这,基本框架就搭完了,下面开始按步骤使用EventBus了。

2、新建一个类FirstEvent

  1. package com.harvic.other;  
  2. public class FirstEvent {  
  3.     private String mMsg;  
  4.     public FirstEvent(String msg) {  
  5.         // TODO Auto-generated constructor stub  
  6.         mMsg = msg;
  7.     }
  8.     public String getMsg(){  
  9.         return mMsg;  
  10.     }
  11. }

这个类很简单,构造时传进去一个字符串,然后可以通过getMsg()获取出来。

3、在要接收消息的页面注册EventBus:

在上面的GIF图片的演示中,大家也可以看到,我们是要在MainActivity中接收发过来的消息的,所以我们在MainActivity中注册消息。

通过我们会在OnCreate()函数中注册EventBus,在OnDestroy()函数中反注册。所以整体的注册与反注册的代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.TextView;  
  11. import android.widget.Toast;  
  12. public class MainActivity extends Activity {  
  13.     Button btn;
  14.     TextView tv;
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);
  19.                 //注册EventBus  
  20.         EventBus.getDefault().register(this);  
  21.         btn = (Button) findViewById(R.id.btn_try);
  22.         tv = (TextView)findViewById(R.id.tv);
  23.         btn.setOnClickListener(new View.OnClickListener() {  
  24.             @Override  
  25.             public void onClick(View v) {  
  26.                 // TODO Auto-generated method stub  
  27.                 Intent intent = new Intent(getApplicationContext(),  
  28.                         SecondActivity.class);  
  29.                 startActivity(intent);
  30.             }
  31.         });
  32.     }
  33.     @Override  
  34.     protected void onDestroy(){  
  35.         super.onDestroy();  
  36.         EventBus.getDefault().unregister(this);//反注册EventBus  
  37.     }
  38. }

4、发送消息

发送消息是使用EventBus中的Post方法来实现发送的,发送过去的是我们新建的类的实例!

  1. EventBus.getDefault().post(new FirstEvent(“FirstEvent btn clicked”));  

完整的SecondActivity.java的代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.os.Bundle;  
  6. import android.view.View;  
  7. import android.widget.Button;  
  8. public class SecondActivity extends Activity {  
  9.     private Button btn_FirstEvent;  
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_second);
  14.         btn_FirstEvent = (Button) findViewById(R.id.btn_first_event);
  15.         btn_FirstEvent.setOnClickListener(new View.OnClickListener() {  
  16.             @Override  
  17.             public void onClick(View v) {  
  18.                 // TODO Auto-generated method stub  
  19.                 EventBus.getDefault().post(
  20.                         new FirstEvent(“FirstEvent btn clicked”));  
  21.             }
  22.         });
  23.     }
  24. }

5、接收消息

接收消息时,我们使用EventBus中*常用的onEventMainThread()函数来接收消息,具体为什么用这个,我们下篇再讲,这里先给大家一个初步认识,要先能把EventBus用起来先。

在MainActivity中重写onEventMainThread(FirstEvent event),参数就是我们自己定义的类:

在收到Event实例后,我们将其中携带的消息取出,一方面Toast出去,一方面传到TextView中;

  1. public void onEventMainThread(FirstEvent event) {  
  2.     String msg = “onEventMainThread收到了消息:” + event.getMsg();  
  3.     Log.d(“harvic”, msg);  
  4.     tv.setText(msg);
  5.     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();  
  6. }

完整的MainActiviy代码如下:

  1. package com.example.tryeventbus_simple;  
  2. import com.harvic.other.FirstEvent;  
  3. import de.greenrobot.event.EventBus;  
  4. import android.app.Activity;  
  5. import android.content.Intent;  
  6. import android.os.Bundle;  
  7. import android.util.Log;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.TextView;  
  11. import android.widget.Toast;  
  12. public class MainActivity extends Activity {  
  13.     Button btn;
  14.     TextView tv;
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);
  19.         EventBus.getDefault().register(this);  
  20.         btn = (Button) findViewById(R.id.btn_try);
  21.         tv = (TextView)findViewById(R.id.tv);
  22.         btn.setOnClickListener(new View.OnClickListener() {  
  23.             @Override  
  24.             public void onClick(View v) {  
  25.                 // TODO Auto-generated method stub  
  26.                 Intent intent = new Intent(getApplicationContext(),  
  27.                         SecondActivity.class);  
  28.                 startActivity(intent);
  29.             }
  30.         });
  31.     }
  32.     public void onEventMainThread(FirstEvent event) {  
  33.         String msg = “onEventMainThread收到了消息:” + event.getMsg();  
  34.         Log.d(“harvic”, msg);  
  35.         tv.setText(msg);
  36.         Toast.makeText(this, msg, Toast.LENGTH_LONG).show();  
  37.     }
  38.     @Override  
  39.     protected void onDestroy(){  
  40.         super.onDestroy();  
  41.         EventBus.getDefault().unregister(this);  
  42.     }
  43. }

好了,到这,基本上算初步把EventBus用起来了,下篇再讲讲EventBus的几个函数,及各个函数间是如何识别当前如何调用哪个函数的。

EventBus使用详解

前言:EventBus出来已经有一段时间了,github上面也有很多开源项目中使用了EventBus。所以抽空学习顺便整理了一下。目前EventBus*新版本是3.0,所以本文是基于EventBus3.0的。

相关文章
EventBus使用详解
EventBus源码解析

概述

EventBus是针一款对Android的发布/订阅事件总线。它可以让我们很轻松的实现在Android各个组件之间传递消息,并且代码的可读性更好,耦合度更低。

如何使用

(1)首先需要定义一个消息类,该类可以不继承任何基类也不需要实现任何接口。如:

public class MessageEvent {
    ......
}

(2)在需要订阅事件的地方注册事件

EventBus.getDefault().register(this);

(3)产生事件,即发送消息

EventBus.getDefault().post(messageEvent);

(4)处理消息

@Subscribe(threadMode = ThreadMode.PostThread)
public void XXX(MessageEvent messageEvent) {
    ...
}

在3.0之前,EventBus还没有使用注解方式。消息处理的方法也只能限定于onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,分别代表四种线程模型。而在3.0之后,消息处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认为PostThread),四种线程模型,下面会讲到。
注意,事件处理函数的访问权限必须为public,否则会报异常。

(5)取消消息订阅

EventBus.getDefault().unregister(this);

有何优点

采用消息发布/订阅的一个很大的优点就是代码的简洁性,并且能够有效地降低消息发布者和订阅者之间的耦合度。
举个例子,比如有两个界面,ActivityA和ActivityB,从ActivityA界面跳转到ActivityB界面后,ActivityB要给ActivityA发送一个消息,ActivityA收到消息后在界面上显示出来。我们*先想到的方法就是使用广播,使用广播实现此需求的代码如下:
首先需要在ActivityA中定义一个广播接收器:

public class MessageBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        mMessageView.setText("Message from SecondActivity:" + intent.getStringExtra("message"));
    }
}

还需要在onCreate()方法中注册广播接收器:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //注册事件
    EventBus.getDefault().register(this);
    //注册广播
    IntentFilter intentFilter = new IntentFilter("message_broadcast");
    mBroadcastReceiver = new MessageBroadcastReceiver();
    registerReceiver(mBroadcastReceiver, intentFilter);
    ......
}

然后在onDestory()方法中取消注册广播接收器:

@Override
protected void onDestroy() {
    super.onDestroy();
    ......
    //取消广播注册
    unregisterReceiver(mBroadcastReceiver);
}

*后我们需要在ActivityB界面中发送广播消息:

findViewById(R.id.send_broadcast).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String message = mMessageET.getText().toString();
        if(TextUtils.isEmpty(message)) {
            message = "defaule message";
        }
        Intent intent = new Intent();
        intent.setAction("message_broadcast");
        intent.putExtra("message", message);
        sendBroadcast(intent);
    }
});

看着上面的实现代码,感觉也没什么不妥,挺好的!下面对比看下使用EventBus如何实现。
根据文章*前面所讲的EventBus使用步骤,首先我们需要定义一个消息事件类:

public class MessageEvent {

    private String message;

    public MessageEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

在ActivityA界面中我们首先需要注册订阅事件:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //注册事件
    EventBus.getDefault().register(this);
    ......
}

然后在onDestory()方法中取消订阅:

@Override
protected void onDestroy() {
    super.onDestroy();
    //取消事件注册
    EventBus.getDefault().unregister(this);
}

当然还要定义一个消息处理的方法:

@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowMessageEvent(MessageEvent messageEvent) {
    mMessageView.setText("Message from SecondActivity:" + messageEvent.getMessage());
}

至此,消息订阅者我们已经定义好了,我们还需要在ActivityB中发布消息:

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String message = mMessageET.getText().toString();
        if(TextUtils.isEmpty(message)) {
            message = "defaule message";
        }
        EventBus.getDefault().post(new MessageEvent(message));
    }
});

对比代码一看,有人会说了,这尼玛有什么区别嘛!说好的简洁呢?哥们,别着急嘛!我这里只是举了个简单的例子,仅仅从该例子来看,EventBus的优势没有体现出来。现在我将需求稍微改一下,ActivityA收到消息后,需要从网络服务器获取数据并将数据展示出来。如果使用广播,ActivityA中广播接收器代码应该这么写:

public class MessageBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //从服务器上获取数据
                ......
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //将获取的数据展示在界面上
                        ......
                    }
                });
            }
        }).start();
    }
}

看到这段代码,不知道你何感想,反正我是看着很不爽,嵌套层次太多,完全违反了Clean Code的原则。那使用EventBus来实现又是什么样呢?我们看一下。

@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onGetDataEvent(MessageEvent messageEvent) {
    //从服务器上获取数据
    ......
    EventBus.getDefault().post(new ShowMessageEvent());
}

@Subscribe(threadMode = ThreadMode.MainThread)
public void onShowDataEvent(ShowMessageEvent showMessageEvent) {
    //将获取的数据展示在界面上
    ......
}

对比一下以上两段代码就能很明显的感觉到EventBus的优势,代码简洁、层次清晰,大大提高了代码的可读性和可维护性。我这只是简单的加了一个小需求而已,随着业务越来越复杂,使用EventBus的优势愈加明显。

常用API介绍

线程模型

在EventBus的事件处理函数中需要指定线程模型,即指定事件处理函数运行所在的想线程。在上面我们已经接触到了EventBus的四种线程模型。那他们有什么区别呢?
在EventBus中的观察者通常有四种线程模型,分别是PostThread(默认)、MainThread、BackgroundThread与Async。

  • PostThread:如果使用事件处理函数指定了线程模型为PostThread,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为PostThread的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。
  • MainThread:如果使用事件处理函数指定了线程模型为MainThread,那么不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。
  • BackgroundThread:如果使用事件处理函数指定了线程模型为BackgroundThread,那么如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。
  • Async:如果使用事件处理函数指定了线程模型为Async,那么无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。同样,此事件处理函数中禁止进行UI更新操作。

为了验证以上四个方法,我写了个小例子。

@Subscribe(threadMode = ThreadMode.PostThread)
public void onMessageEventPostThread(MessageEvent messageEvent) {
    Log.e("PostThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.MainThread)
public void onMessageEventMainThread(MessageEvent messageEvent) {
    Log.e("MainThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.BackgroundThread)
public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
    Log.e("BackgroundThread", Thread.currentThread().getName());
}

@Subscribe(threadMode = ThreadMode.Async)
public void onMessageEventAsync(MessageEvent messageEvent) {
    Log.e("Async", Thread.currentThread().getName());
}

分别使用上面四个方法订阅同一事件,打印他们运行所在的线程。首先我们在UI线程中发布一条MessageEvent的消息,看下日志打印结果是什么。

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("postEvent", Thread.currentThread().getName());
            EventBus.getDefault().post(new MessageEvent());
        }
    });

打印结果如下:

2689-2689/com.lling.eventbusdemo E/postEvent﹕ main
2689-2689/com.lling.eventbusdemo E/PostThread﹕ main
2689-3064/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
2689-2689/com.lling.eventbusdemo E/MainThread﹕ main
2689-3065/com.lling.eventbusdemo E/BackgroundThread﹕ pool-1-thread-2

从日志打印结果可以看出,如果在UI线程中发布事件,则线程模型为PostThread的事件处理函数也执行在UI线程,与发布事件的线程一致。线程模型为Async的事件处理函数执行在名字叫做pool-1-thread-1的新的线程中。而MainThread的事件处理函数执行在UI线程,BackgroundThread的时间处理函数执行在名字叫做pool-1-thread-2的新的线程中。

我们再看看在子线程中发布一条MessageEvent的消息时,会有什么样的结果。

findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.e("postEvent", Thread.currentThread().getName());
                    EventBus.getDefault().post(new MessageEvent());
                }
            }).start();
        }
    });

打印结果如下:

3468-3945/com.lling.eventbusdemo E/postEvent﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/PostThread﹕ Thread-125
3468-3945/com.lling.eventbusdemo E/BackgroundThread﹕ Thread-125
3468-3946/com.lling.eventbusdemo E/Async﹕ pool-1-thread-1
3468-3468/com.lling.eventbusdemo E/MainThread﹕ main

从日志打印结果可以看出,如果在子线程中发布事件,则线程模型为PostThread的事件处理函数也执行在子线程,与发布事件的线程一致(都是Thread-125)。BackgroundThread事件模型也与发布事件在同一线程执行。Async则在一个名叫pool-1-thread-1的新线程中执行。MainThread还是在UI线程中执行。

上面一个例子充分验证了指定不同线程模型的事件处理方法执行所在的线程。

黏性事件

除了上面讲的普通事件外,EventBus还支持发送黏性事件。何为黏性事件呢?简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。具体用法如下:

订阅黏性事件:

EventBus.getDefault().register(StickyModeActivity.this);

黏性事件处理函数:

@Subscribe(sticky = true)
public void XXX(MessageEvent messageEvent) {
    ......
}

发送黏性事件:

EventBus.getDefault().postSticky(new MessageEvent("test"));

处理消息事件以及取消订阅和上面方式相同。

看个简单的黏性事件的例子,为了简单起见我这里就在一个Activity里演示了。

Activity代码:

public class StickyModeActivity extends AppCompatActivity {

    int index = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sticky_mode);
        findViewById(R.id.post).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("test" + index++));
            }
        });
        findViewById(R.id.regist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().registerSticky(StickyModeActivity.this);
            }
        });

        findViewById(R.id.unregist).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().unregister(StickyModeActivity.this);
            }
        });

    }

    @Subscribe(threadMode = ThreadMode.PostThread, sticky = true)
    public void onMessageEventPostThread(MessageEvent messageEvent) {
        Log.e("PostThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.MainThread, sticky = true)
    public void onMessageEventMainThread(MessageEvent messageEvent) {
        Log.e("MainThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.BackgroundThread, sticky = true)
    public void onMessageEventBackgroundThread(MessageEvent messageEvent) {
        Log.e("BackgroundThread", messageEvent.getMessage());
    }

    @Subscribe(threadMode = ThreadMode.Async, sticky = true)
    public void onMessageEventAsync(MessageEvent messageEvent) {
        Log.e("Async", messageEvent.getMessage());
    }

}

布局代码activity_sticky_mode.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.lling.eventbusdemo.StickyModeActivity">

    <Button
        android:id="@+id/post"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Post"/>

    <Button
        android:id="@+id/regist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Regist"/>

    <Button
        android:id="@+id/unregist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UnRegist"/>

</LinearLayout>

代码很简单,界面上三个按钮,一个用来发送黏性事件,一个用来订阅事件,还有一个用来取消订阅的。首先在未订阅的情况下点击发送按钮发送一个黏性事件,然后点击订阅,会看到日志打印结果如下:

15246-15246/com.lling.eventbusdemo E/PostThread﹕ test0
15246-15391/com.lling.eventbusdemo E/Async﹕ test0
15246-15246/com.lling.eventbusdemo E/MainThread﹕ test0
15246-15393/com.lling.eventbusdemo E/BackgroundThread﹕ test0

这就是粘性事件,能够收到订阅之前发送的消息。但是它只能收到*新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到*近的一条消息。这个我们可以验证一下,我们连续点击5次POST按钮发送5条黏性事件,然后再点击REGIST按钮订阅,打印结果如下:

6980-6980/com.lling.eventbusdemo E/PostThread﹕ test4
6980-6980/com.lling.eventbusdemo E/MainThread﹕ test4
6980-7049/com.lling.eventbusdemo E/Async﹕ test4
6980-7048/com.lling.eventbusdemo E/BackgroundThread﹕ test4

由打印结果可以看出,确实是只收到*近的一条黏性事件。

打算出十几台二手的服务器,刚从机房下架

RT,服务器大部分是 dell 的,还有一两台国产的金品

若有需要可以联系企鹅:三,四,六,三个七,四,零,一

第 1 条附言  ·  2018-01-31 10:13:10 +08:00

已出。
Theo14
    1

Theo14   2017-12-27 15:35:59 +08:00

dell 服务器的具体配置:

型号 盘数 内存 电源 CPU DVD 阵列卡
R410 4 16G(8G*2,镁光 8G 2RX4 PC3L-10600R) 480W*1 E5620 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 16G(8G*2,三星 8G 2RX4 PC3L-10600R) 480W*1 E5620 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 16G(8G*2,镁光 8G 2RX4 PC3L-10600R) 480W*1 E5606 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 16G(8G*2,镁光 8G 2RX4 PC3L-10600R) 480W*1 E5620 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 8G(8G*1,镁光 8G 2RX4 PC3L-10600R) 480W*1 E5620 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 8G(2G*4,南亚 2G 2RX8 PC3-10600E) 480W*1 E5504 ( 2GHz ) 1 LSI SAS 1068E
R410 4 16G(4G*4,海力士 4G 2RX8 PC3L-10600E) 480W*1 E5606 ( 2.13GHz ) 1 LSI SAS 1068E
R410 4 8G(2G*4,海力士 2G 1RX8 PC3L-10600R) 480W*1 E5620 ( 2.4GHz ) 1 LSI SAS 1068E
R410 4 64G(16G*4,三星 16G 2RX4 PC3L-10600R) 480W*1 E5620*2 ( 2.4GHz ) 1 E2K-UCP-61-(B)C
R410 4 128G(16G*8,海力士 16G 2RX4 PC3L-12800R 480W*1 E5606*2 ( 2.13GHz ) 1 E2K-UCP-61-(B)C
R420 4 32G(16G*2,镁光 16G 2RX4 PC3L-12800R) 550W*1 E5-2420 ( 1.9GHz ) 1 H710
R420 4 16G(8G*2,南亚 8G 1RX4 PC3-14900R) 550W*1 E5-2407 ( 2.2GHz ) 1 H710
1950 2 8G(2G*4,三星 2G 2RX8 PC2-5600F) 670W*1 E5405 ( 2GHz ) 无 LSI SAS 1068E

金品的具体配置

型号 盘数 内存 电源 CPU DVD 阵列卡
金品服务器 10.233 JP-KU1580 4 12G(4G*3,金士顿) 400W*1 E5506 ( 2.13GHz )

硬盘:
SAS 15K 600G*6,300G*13,146G*3
7.2K 1T*2

还有一台 Juniper SSG-320M-SH 的防火墙

Theo14
    2

Theo14   2017-12-27 15:49:32 +08:00

*好是在北京,可以面交
server
    3

server   2017-12-27 15:54:37 +08:00

微博 去找下梁斌,看他有兴趣不 https://weibo.com/pennyliang
Theo14
    4

Theo14   2017-12-27 15:56:19 +08:00

@server 好 谢谢
radio777
    5

radio777   2017-12-27 17:52:14 +08:00

内存单卖不?
Theo14
    6

Theo14   2017-12-28 08:33:55 +08:00

@radio777 暂时不打算单卖,要是后面有剩下的我跟你说
wangzhangwei
    7

wangzhangwei   2017-12-28 10:30:35 +08:00

不好处理哦,高了没人要低了觉得可惜。我公司现在几十台 HP 都仓库搁着呢

租用服务器,/29 的子网只有 5/8 可用 IP 吗,有没有办法不这么浪费?

前不久在阿里云申请到一个 X.X.X.0 的 IP,

感觉有点不可思议,

难道是子网是 /16 的才可以吗?

也不知为何 IDC 们租出的 /29 子网都只有 5 个 IP,

好像明明有办法把 8 个 IP 全利用起来,比如通过映射?

DravenJohnson
    1

DravenJohnson   2017-11-29 07:25:25 +08:00

/29 是 6 个可用吧?除非他们有别的设置
http://jodies.de/ipcalc?host=156.156.11.0&mask1=29&mask2=一个是 Broadcast 一个是 Gateway/Router

DravenJohnson
    2

DravenJohnson   2017-11-29 07:27:24 +08:00

http://lmgtfy.com/?q=5+usable+ip+addresses+%2F29 Google 有很多答案可以看看
ycqy
    3

ycqy   2017-11-29 07:38:19 +08:00

@DravenJohnson 正式我想说的,5 个还是 6 个,可以先 Google 一下。
我是搜完才问的,我知道,这是基本的社区*仪。
还是感谢你回答。
zjqzxc
    4

zjqzxc   2017-11-29 08:19:35 +08:00

“也不知为何 IDC 们租出的 /29 子网都只有 5 个 IP ”
机器号全 0:网段号;
机器号全 1:广播地址
还有一个:网关地址网络号能不能用的问题,我个人认为*有可能没办法用(正常手段不行)
广播地址*对没办法用,/29 的子网中所有主机都会响响应这个地址
自己管理的网络,网关地址可以指向一台双网卡的服务器达到利用的目的,但阿里云估计做不了(吧)

ipv4 设计之初哪儿想着会有一天地址不够用啊,所以浪费很严重。。。

gstqc
    5

gstqc   2017-11-29 09:30:45 +08:00 via iPhone   ❤️ 1

让机房把整个段路由给你,然后你自己来管理 IP 分配。
可以全部利用上 8 个 IP,但技术成本太高了。
Showfom
    6

Showfom   2017-11-29 09:32:32 +08:00 via iPhone

同意楼上
joshu
    7

joshu   2017-11-29 09:32:48 +08:00 via Android

每台机器上弄 /32 的 ip 地址,然后手动设置到其它 ip 的静态路由,应该可以实现更有效的利用
wwqgtxx
    8

wwqgtxx   2017-11-29 09:34:41 +08:00 via iPhone

@joshu 你设置 /32 的 ip 地址那怎么连网关?
ycqy
    9

ycqy   2017-11-29 09:43:57 +08:00

@gstqc 这个真能行吗,网关怎么配置呢?
如果是可行的,配置下 ip route 什么的,技术成本还好,就是不知道具体怎么操作?
我没想出来怎么在本地配置一下虚拟机实现这个思路,希望指点一下。
gstqc
    10

gstqc   2017-11-29 10:08:26 +08:00

@ycqy 我说的技术成本,是指得找个懂数通和 Linux 的高级工程师。
这比 3 个 IP 成本高很多很多倍。
依瓢画葫芦做出来,没办法维护的。
另外,IP 是阿里云分的,如果阿里云不支持,肯定不会为你单独搞这个功能。

joshu
    11

joshu   2017-11-29 12:13:25 +08:00 via Android

@wwqgtxx ip route add gw_ip dev xxx
ip route add 0/0 via gw_ip dev xxx
/32 只不过访问其它 ip 时都是默认 arp 查询而已,除非明确了下一跳
joshu
    12

joshu   2017-11-29 12:15:15 +08:00 via Android

@wwqgtxx 一般的三层的点对点 v*n 不就是 /32 么
wwqgtxx
    14

wwqgtxx   2017-11-29 12:24:56 +08:00 via iPhone

@joshu 点对点的时候根本就不需要 ip,只不过添加一个 ip 用于兼容 tcp/ip 协议而已
servers007
    15

servers007   2018-09-27 16:18:00 +08:00

有其他用掉了吧,正常讲 /29 是有 6 个可用的

今天分享一个自用不错的服务器。

这家的机子现在不错。稳定性和速度都可以。已经用了将近 1 年。没出现啥问题。推荐给大家用:
https://www.diyvm.com/page.aspx?c=referral&u=62091

http://i.niupic.com/images/2017/11/28/mDYIev.png

我的站: https://www.lieqishi.com/ 大家可以测试测试

6 条回复    2018-07-24 21:37:15 +08:00

whx20202
    1

whx20202   2017-11-28 14:48:41 +08:00

主要是小水管 不过大部分都是,香港这个情况就是这样
先关注一下好了
zlfzy
    2

zlfzy   2017-11-28 14:51:34 +08:00

怎么感觉现在这些小厂远没有大厂实惠
Alwaysonline
    3

Alwaysonline   2017-11-28 15:02:29 +08:00

小厂的蛮尴尬的,境外业务有更好的;境内涉及到域名备案肯定也会选大厂,下备案快,稳定性也好些。
qianbiTH
    4

qianbiTH   2017-12-13 00:12:00 +08:00 via Android

嗯,福利美图。。。
jeffcott
    5

jeffcott   2018-01-04 21:39:40 +08:00

你这个福利是自己爬的吗。。。
mostkia
    6

mostkia   2018-07-24 21:37:15 +08:00

2m 小水管,而且费用也不便宜啊 50 元一个月,就是不知道延时怎么样。话说楼主你的网站禁止 ping 吗?我想看看延时都不行,包全都丢了。。我现在用的也是香港 VPS,ping 大部分时间都在 40MS (浙江电信)偶尔会到 100 多,但很快又会恢复正常,公司的网络是移动的,ping 就高一些( 60MS+偶尔会到 180+,很快又会恢复正常),因为 dns 绕道北京然后再到浙江的缘故。

[服务器] 买了学生服务器,该怎么用?

当时脑子一热,跟上了“薅羊毛”的大军,买了腾讯云的轻量应用服务器,2cpu,4g 内存,80g 硬盘,8M 带宽(原先 6M 不知道为啥现在变成 8 ),1200G 流量 /月,博客的话 github pages 整挺好,唯一限制我的双手是因为国内的服务器,当时不能选择海外地区,这里问问大家,应该做什么 or 卖掉?

12 条回复    2021-07-23 22:36:47 +08:00

lazydao
    1

lazydao   38 天前

喜欢后端开发的话可以玩很多东西,否则二手卖吧。
villivateur
    2

villivateur   38 天前 via Android

去看看 nextcloud 做个人网盘
sickoo
    3

sickoo   38 天前

@villivateur 可以看看,在用阿里云盘。
sickoo
    4

sickoo   38 天前

@lazydao 有 DigitalOcean 一个学生 100 刀的券还没用完。感觉后端上用这个海外的可能更好,如果二手卖的话,需要注意什么呢
kidlj
    5

kidlj   38 天前

VSCode remote 当开发服务器。
Muninn
    6

Muninn   37 天前

用处挺多的吧 可以整个备案 不然域名都用不了
sickoo
    7

sickoo   37 天前

@Muninn 备案在整了得找个地方打印
NatsumeMio
    8

NatsumeMio   36 天前

自己用没必要备案了,网站弄在其他端口就行了。可以考虑一个核去挖 xmr,一年下来也有 50-100 块钱的收益吧。国内机子开网站要备案,不建议搞,博客的话发了什么不好的东西就要拉你去局子了。
sickoo
    9

sickoo   36 天前 via iPhone

@NatsumeMio 这么吓人…我现在只打算当开发服务器了,国内限制太多
ruicky
    10

ruicky   33 天前

@NatsumeMio xmr 这个 方便 说说 怎么配吗?
ch2
    11

ch2   31 天前

开网站、小程序、H5 、游戏服务器、挂机签到、爬虫,可以玩的多的是
sickoo
    12

sickoo   31 天前

@ch2 京豆人