zoukankan      html  css  js  c++  java
  • EventBus 事件总线 案例


    简介

    EventBus是一个【发布 / 订阅】的事件总线。简单点说,就是两人【约定】好怎么通信,一人发布消息,另外一个约定好的人立马接收到你发的消息。
    EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。
    优点:开销小,代码更优雅,将发送者和接收者解耦。
    用处:相信大家都用过【Handle】了进行线程通信,回调方法进行通信,是不是觉得特麻烦。EventBus就可以帮减少很多事,不管你在【任何地方】发布一个事件,接收者都能立马接收到你的消息,不用你考虑android【子线程操作UI线程】的问题!
    EvenBus简化了应用程序内【各组件间】、【组件与后台线程间】的通信。它的效果和Handler的效果大致相同,但是实现原理和使用方法是完全不同的,它是基于【保存】相应方法,然后通过【反射】机制来实现的。

    包含4个成分:发布者,订阅者,事件,总线。
    关系:订阅者订阅事件到总线,发送者发布事件;订阅者可以订阅多个事件,发送者可以发布任何事件,发布者同时也可以是订阅者。
    注册订阅者:EventBus.getDefault().register(this);这个方法通常在onCreate方法中进行注册。
    解绑订阅者:EventBus.getDefault().unregister(this);这个方法通常在onDestroy方法中进行解绑。
    发布者是不需要注册的,只有订阅者才需要注册。

    若收不到事件,比如在Activity的oncreat中发送事件,在Fragment中的oncreat中接收事件,可能是因为发送事件时,接收事件的Fragment还未注册EventBus。
    这种情况可以采用发送Sticky Event(粘性事件)来解决
    这样注册之前发送的sticky事件的最近的一个会保存在内存中,错过这个事件的发送的情况下,也可以通过getStickyEvent收到。
    注册时使用:EventBus.getDefault().registerSticky(this);// 实际中发现不以Sticky的形式注册也完全可以收到Sticky事件
    获取时使用:StickyEventBusBeans bean = EventBus.getDefault().getStickyEvent(StickyEventBusBeans.class);
    还可以移除:EventBus.getDefault().removeStickyEvent(bean);

    约定的收到事件要执行的方法
    要求:方法名必须以【onEvent】开头,必须为非静态、【public】权限、有且仅有一个参数
    • onEvent:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
    • onEventMainThread:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中更新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
    • onEventBackground:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
    • onEventAsync:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.
    注意post方法的参数,EventBus是根据这四个方法的参数来决定哪个类接收事件的,发布者的参数和某个订阅者这四个方法的参数一样,则执行这个订阅者的这个方法
    当订阅者收到事件后,就会自动执行上面这四个方法,如果写了多个,则都会执行。

    EventBus带来的好处和引入的问题
    好处比较明显,就是独立出一个发布订阅模块,调用者可以通过使用这个模块,屏蔽一些线程切换问题,简单地实现发布订阅功能。
    坏处可能比较隐晦,但这些需要足够引起我们的重视
    • 大量的滥用,将导致逻辑的分散,出现问题后很难定位
    • 没办法实现强类型,在编译的时候就发现问题,(Otto实现了这个,但性能有问题)
    • 在实现上通过一个很弱的协议,比如onEventXXX(XXX表示ThreadModel),来实现线程的切换代码可读性有些问题,IDE无法识别这些协议,对IDE不友好

    EventBus 、 Otto 、 BroadcastReceiver三者比较
    1、单从使用上看,EventBus > Otto > BroadcastReceiver,当然BroadcastReceiver作为系统内置组件,有一些前两者没有的功能
    2、EventBus最简洁,Otto最符合Guava EventBus的设计思路, BroadcastReceiver最难使用。
    3、Otto使用注解定义订阅/发布者的角色,@Subscribe为订阅者,@Produce为发布者,方法名称可以自定义。EventBus规定onEvent方法固定作为订阅者接受事件的方法,应该是参考了"约定优于配置"的思想。
    4、Otto为了性能,代码意图清晰,要求@Subscribe,@Produce方法必须定义在直接的作用类上,而不能定义在基类而被继承。
    5、Otto要求发布者也需要register和unregister,而EventBus的发布者是不需要的。

    MainActivity
    public class MainActivity extends Activity {
        TextView textView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EventBus.getDefault().register(this);//在onCreate里面进行事件的订阅,onDestroy里面进行事件的取消
            textView = new TextView(this);
            textView.setText("点击进入第二个Activity");
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
            textView.setGravity(Gravity.CENTER);
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    startActivity(new Intent(MainActivity.this, ItemActivity.class));
                    EventBus.getDefault().postSticky(
                            new StickyEventBusBeans(StickyEventBusBeans.TIME"来自MainActivity " + new SimpleDateFormat("HH:mm:ss").format(new Date())));
                }
            });
            setContentView(textView);
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            EventBus.getDefault().unregister(this);
        }
        //******************************************************************************************
        /**
         * 方法名必须以【onEvent】开头,必须为非静态的、public权限的、有且仅有一个参数
         * 【MainThread】意思这个方法最终要在UI线程执行(不管在哪个线程发布的消息);当指定事件发布的时候,这个方法就会在UI线程自动执行
         */
        public void onEventMainThread(StringEventBusBean event) {
            textView.setText(event.getContent());
        }
        /**
         * 以onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在哪个线程中运行
         */
        public void onEvent(NormalEventBusBeans event) {
            switch (event.getFlag()) {
            case NormalEventBusBeans.LOGIN://主线程发布的事件
                textView.setText("登录成功 " + Thread.currentThread().getName());
                break;
            case NormalEventBusBeans.LOGOUT://子线程发布的事件
                try {
                    Log.i("bqt", Thread.currentThread().getName());
                    Thread.sleep(20 * 1000);//这是在子线程中
                    Log.i("bqt""如果不是在子线程中,肯定会直接卡死(ANR)");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
            }
        }
    }

    第二个Activity
    public class ItemActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_item);
        }
    }

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
        <fragment
            android:id="@+id/item_list"
            android:name="com.bqt.eventbus.ItemListFragment"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <fragment
            android:id="@+id/item_detail_container"
            android:name="com.bqt.eventbus.ItemDetailFragment"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>

    列表Fragment
    public class ItemListFragment extends ListFragment {
        private List<String> items = new ArrayList<String>();
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EventBus.getDefault().register(this);
        }
        @Override
        public void onDestroy() {
            super.onDestroy();
            EventBus.getDefault().unregister(this);
        }
        //******************************************************************************************
        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            // 开启线程加载列表
            new Thread() {
                public void run() {
                    try {
                        items.add("在UI线程中发布一个StringEventBusBean事件,通知右侧的fragment和MainActivity刷新数据");
                        items.add("在UI线程中发布一个NormalEventBusBeans事件,通知MainActivity已登录");
                        items.add("在子线程中发布一个NormalEventBusBeans事件,通知MainActivity已注销");
                        items.add("在子线程中发布一个StringEventBusBean事件,通知右侧的fragment和MainActivity刷新数据");
                        items.add("发布一个StickyEventBusBeans事件,此事件会把之前的覆盖掉");
                        items.add("获取此Sticky事件,并通过一个StringEventBusBean事件把其中的内容转发出去");
                        items.add("移除此Sticky事件 (粘性事件)");
                        Thread.sleep(300)// 模拟延时
                        EventBus.getDefault().post(new ListEventBusBean(items));// 在【后台线程】发布一个参数为ListEventBusBean的事件
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                };
            }.start();
        }
        @Override
        //【This method will be called when an item in the list is selected】【position  The position of the view in the list】【id  The row id of the item that was clicked】
        public void onListItemClick(ListView listView, View view, int position, long id) {
            super.onListItemClick(listView, view, position, id);
            switch (position) {
            case 0:
                EventBus.getDefault().post(new StringEventBusBean("我是 UI线程的 包青天"));
                break;
            case 1:
                EventBus.getDefault().post(new NormalEventBusBeans(NormalEventBusBeans.LOGIN));
                getActivity().finish();
                break;
            case 2:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        EventBus.getDefault().post(new NormalEventBusBeans(NormalEventBusBeans.LOGOUT));
                    }
                }).start();
                getActivity().finish();
                break;
            case 3:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        EventBus.getDefault().post(new StringEventBusBean("我是 子线程的 包青天"));
                    }
                }).start();
                break;
            case 4:
                EventBus.getDefault().postSticky(
                        new StickyEventBusBeans(StickyEventBusBeans.TIME"来自ItemListFragment " + new SimpleDateFormat("HH:mm:ss").format(new Date())));
                break;
            case 5:
                StickyEventBusBeans bean5 = EventBus.getDefault().getStickyEvent(StickyEventBusBeans.class);
                if (bean5 != null) EventBus.getDefault().post(new StringEventBusBean(bean5.getContent()));
                else Toast.makeText(getActivity().getApplicationContext()"不存在粘性事件哦", Toast.LENGTH_SHORT).show();
                break;
            case 6:
                StickyEventBusBeans bean6 = EventBus.getDefault().getStickyEvent(StickyEventBusBeans.class);
                if (bean6 != null) {
                    boolean success = EventBus.getDefault().removeStickyEvent(bean6);//还可以移除所有粘性事件
                    if (success) Toast.makeText(getActivity().getApplicationContext()"移除成功", Toast.LENGTH_SHORT).show();
                } else Toast.makeText(getActivity().getApplicationContext()"不存在粘性事件哦", Toast.LENGTH_SHORT).show();
                break;
            }
        }
        public void onEventMainThread(ListEventBusBean event) {
            ListAdapter adapter = new ArrayAdapter<String>(getActivity(), R.layout.item, R.id.tv_name, event.getItems());
            setListAdapter(adapter);
        }
    }

    详情Fragment
    public class ItemDetailFragment extends Fragment {
        private TextView tv_info;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EventBus.getDefault().register(this);// 看来扯蛋的事情不止一丢丢:不以Sticky的形式注册也完全可以收到Sticky事件
        }
        @Override
        public void onDestroy() {
            super.onDestroy();
            EventBus.getDefault().unregister(this);
        }
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            tv_info = new TextView(getActivity());
            tv_info.setTextColor(Color.BLUE);
            tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
            tv_info.setBackgroundColor(0x3300ff00);
            tv_info.setGravity(Gravity.CENTER);
            onGetStickyEvent();//此广播要自己手动获取(其实只是把此事件保存了起来,并不会主动通知)
            return tv_info;
        }
        //******************************************************************************************
        /** List点击时会发送此事件,接收到事件后更新详情 */
        public void onEventMainThread(StringEventBusBean item) {
            if (item != null) tv_info.setText(item.getContent());
        }
        /**这只是供本地调用的普通方法,对其声明没有任何要求*/
        private void onGetStickyEvent() {
            StickyEventBusBeans bean = EventBus.getDefault().getStickyEvent(StickyEventBusBeans.class);
            if (bean == null) return;
            switch (bean.getFlag()) {
            case StickyEventBusBeans.TIME://这个消息是从MainActivity传过来的,消息发送时ItemDetailFragment还没创建,但是可以用postSticky方式发送的消息仍然可以在这里获取
                tv_info.setText(bean.getContent());
                break;
            default:
                break;
            }
        }
    }

    事件bean
    /**
     * 一个自定义的类,用于通知,不需要传递数据
     */
    public class NormalEventBusBeans {
        /**事件标志 */
        private int flag;
        public NormalEventBusBeans(int flag) {
            this.flag = flag;
        }
        public int getFlag() {
            return flag;
        }
        /**退出登录 */
        public static final int LOGOUT = 1002;
        /**登录*/
        public static final int LOGIN = 1001;
    }

    /**
     * 一个自定义的类,用于传递一个String对象
     */
    public class StringEventBusBean {
        private String content;
        public StringEventBusBean(String content) {
            this.content = content;
        }
        public String getContent() {
            return content;
        }
    }

    /**
     * 一个自定义的类,用于传递一个List<String>集合
     */
    public class ListEventBusBean {
        private List<String> items;
        public ListEventBusBean(List<String> items) {
            this.items = items;
        }
        public List<String> getItems() {
            return items;
        }
    }

    /**
     * 一个自定义的类,用于Sticky类型的通知,可以传两个参数
     */
    public class StickyEventBusBeans {
        /**事件标志 */
        private int flag;
        /**事件内容 */
        private String content;
        public StickyEventBusBeans(int flag, String content) {
            this.content = content;
            this.flag=flag;
        }
        public int getFlag() {
            return flag;
        }
        public String getContent() {
            return content;
        }
        public static final int TIME = 10086;
    }





    附件列表

    • 相关阅读:
      Dobbo
      Redis
      Sql语句模糊查询字符串的两种写法
      Python——labelImg安装
      Python——numpy中的 sum 函数
      Python——pymysql 操作数据库
      Axure RP9 授权码和密钥
      更改 pip install 默认安装依赖的路径(转载)
      pip 升级或者安装拓展包时遇见的问题
      在Windows命令行中编译运行C/C++程序(转载)
    • 原文地址:https://www.cnblogs.com/baiqiantao/p/5601766.html
    Copyright © 2011-2022 走看看