Use EventBus

原创
2013/10/11 23:59
阅读数 1.2K

从Listview翻页看EventBus模式解耦组件间通信


大家好,又是我。
<br>这次我们来介绍一下在ANDROID中相对底层的一个开源类库 — —EventBus库。Event Bus模式,中文简称“事件总线模式”,也成生产/消费者模式, 它在领域驱动设计中是必不可少的模块,可以用来解耦模块之间的耦合性。当然,这些理论看似NB哄哄的话不是我说的,是百度说的。

<br>在android开发过程中,我们总会遇到各个组件模块之间的通信,当功能点越来越多的时候,组件间的通信难免会变得混乱。代码也会越来越不易于维护和测试,甚至在出现一些很细微的Bug的时候,会无从入手。厄~这些NB哄哄的经验当然也不是我总结的,但是在实际的项目开发中,是深有体会。所以,在讲解listview翻页之前。先让大家了解一下什么是EventBus。
在android的开源类库中, EventBus模式类库是一个美化底层代码的类库,之所以称之为美化,是在android开发中,使用EventBus模式,解耦组件间的通信,让代码变得更为简洁。关于EventBus模式库的介绍中文资料是少得可怜啊。所以,这篇是我看见过最详细的了。 http://yunfeng.sinaapp.com/?p=449。有兴趣的童鞋可以下载EventBus的源码:  https://github.com/greenrobot/EventBus

<br>链接中有介绍的话,我就不再多说了,说多了也是废话。首先,来看看,在没有使用EventBus模式之前,我们组件间是怎么通信的。用代码说话。

要实现某一组件发生状态的变化时,去通知另外一个组件做相应的变化操作,首先要实现一个接口,这个接口定义了一个方法,是用来通信去加载下一页数据的。

  1. public interface NextPageCallBack {
  2.          public void nextPage();
  3. }
复制代码
然后,我们需要在订阅者中注册这个事件,所谓订阅者,也就是消费者:生产者发布事件更新通知,消费者接收到生产者发布的消息,作对应的改变操作。在这个listview翻页的demo里面,消费者就是指这个事件的实现类MainActivity。在这个demo中,我没有采用实现接口的方式注册事件。而是采用构造的方式。如下红色的代码块。
  1. public class MainActivity extends Activity {

  2.         private ListView mListView; 
  3. private ListAdapter mAdapter;
  4. // 当前的页数 
  5. private int pageNow = 0; 
  6.         @Override
  7.         protected void onCreate(Bundle savedInstanceState) {
  8.                 super.onCreate(savedInstanceState);
  9.                 setContentView(R.layout.activity_main);
  10. mListView = (ListView) findViewById(R.id.listview);  
  11. <font color="#ff0000">             adapter = new ListAdapter(MainActivity.this,  
  12.                         new NextPageCallBack() {  
  13.                             public void nextPage() {  
  14.                                 handler.sendEmptyMessage(SHOW);
  15.                             }
  16.                         });  </font>
  17.           
  18. mListView .setAdapter(
  19. mAdapter );// 设置adapter  
  20. mAdapter .notifyDataSetChanged();// 数据更改的通知  
  21.                 initData();  
  22.                 // 加载下一页的数据,此时为加载第一页的数据  
  23.                 nextPage(); 
  24.         } 
  25.   
  26.     // 获取下一页的数据并显示在List里  
  27.     private void nextPage() {  
  28.         if (pageNow < source.size()) {  
  29.             // 模拟获取数据
  30.             ArrayList<HashMap<String, Object>> newData = source.get(pageNow++);  
  31.             // 向适配器里追加内容  
  32. mAdapter .appendItem(newData);  
  33. mAdapter .notifyDataSetChanged();
  34.             Toast.makeText(this, "加载下一页", Toast.LENGTH_SHORT).show();
  35.         }  
  36.     }  
  37.         
  38.     private final int SHOW = 1;  
  39.       
  40.     private Handler handler = new Handler() {  
  41.         public void handleMessage(Message msg) {  
  42.      switch (msg.what) {  
  43.             case SHOW:  
  44.                 nextPage(); //自动加载数据
  45.                 break;  
  46.             }  
  47.         }  
  48.     }; 
  49.     private List<ArrayList<HashMap<String, Object>>> source;  
  50.     // 初始化数据源
  51.     private void initData() {  
  52.         source = new ArrayList<ArrayList<HashMap<String, Object>>>();  
  53.         for (int p = 0; p < 10; p++) {// 有4页的数据  
  54.             ArrayList<HashMap<String, Object>> page = new ArrayList<HashMap<String, Object>>();  
  55.             for (int i = 0; i < 30; i++) {// 每页有15条数据  
  56.                 HashMap<String, Object> item = new HashMap<String, Object>();  
  57.                 item.put("monkey", "monkey:" + i);  
  58.                 page.add(item);  
  59.             }  
  60.             source.add(page);  
  61.         }  
  62.     } 

  63. }
复制代码
那消费者何时被通知呢?最后我们来看生产者。在这个demo中,我们的生产者就是listview的adapter。在getview的方法中,当判断到界面已经滑动到listview当前页数据的最后一行时,我们就去调用事件接口,通知界面去加载下一页的数据,如下红色的代码块。
  1. public class ListAdapter extends BaseAdapter {

  2.         private ArrayList<HashMap<String, Object>> listItems;  
  3.     private Context mContext;  
  4.     private LayoutInflater inflater;  
  5.     private NextPageCallBack nextPageCallBack;  
  6.     
  7.     public ListAdapter(Context context, NextPageCallBack nextPageCallBack){
  8.              this.mContext = context;  
  9.          this.nextPageCallBack = nextPageCallBack;  
  10.          inflater = LayoutInflater.from(this.mContext);  
  11.          listItems = new ArrayList<HashMap<String, Object>>();  
  12.     }
  13.     
  14.     public void appendItem(ArrayList<HashMap<String, Object>> listItems) {  
  15.         this.listItems.addAll(listItems);  
  16.     } 
  17.     
  18.         @Override
  19.         public int getCount() {
  20.                 // TODO Auto-generated method stub
  21.                 return listItems.size();
  22.         }

  23.         @Override
  24.         public Object getItem(int arg0) {
  25.                 // TODO Auto-generated method stub
  26.                 return null;
  27.         }

  28.         @Override
  29.         public long getItemId(int arg0) {
  30.                 // TODO Auto-generated method stub
  31.                 return 0;
  32.         }
  33.         
  34.          static class MonkeyItem {  
  35.                 public TextView tvname;  
  36.             } 
  37.          
  38.         @Override
  39.         public View getView(int position, View convertView, ViewGroup arg2) {
  40.                 // TODO Auto-generated method stub
  41.                 MonkeyItem item = null;  
  42. <font color="#ff0000">                if (position == getCount() - 1) {  
  43.                     nextPageCallBack.nextPage();  
  44.                 }  
  45. </font>               
  46.                 if (convertView == null) {  
  47.                     item = new MonkeyItem();  
  48.                     convertView = inflater.inflate(R.layout.item, null);  
  49.                     item.tvname = (TextView) convertView.findViewById(R.id.textview);
  50.                     convertView.setTag(item);  
  51.                 } else {  
  52.                     item = (MonkeyItem) convertView.getTag();  
  53.                 }  
  54.                 item.tvname.setText(listItems.get(position).get("monkey").toString());
  55.                 
  56.           
  57.                 return convertView;  
  58.             }  
  59.         
  60.         

  61. }
复制代码
这样一来,listview翻页的demo就完成了,我们来看看效果图。


第一部分附件下载:


<br>从上面各个组件的代码来看,组件间的耦合度是相当高的,特别是在组件间通信越来越频繁和复杂的时候,代码会变得越来越难维护,甚至当你出现一些细微的bug的时候会感觉到无从下手。在软件开发的过程中,模块之间应该尽量减少耦合度,不要违背软件工程中“ 高内聚低耦合 ”的原则。 我也不懂啦。也就是链接介绍说的: 随着应用功能的增加,需要监听的事件越来越多,而越来越多的控件需要监听不同的事件,则导致越来越多的控件需要注册到各种事件管理器上。

<br>为了避免上述说的情况发生,我们来看看如何使用EventBus模式来解耦android组件间的通信。
<br>要使用EventBus组件必须先添加库支持。大家可以官网下载。在本帖的附件demo中也有。


<br>然后,我们新建一个类,这个类定义了某个事件的属性和方法。其实也没有什么。
  1. public class MyEvent {

  2.         private String mString;

  3.         public MyEvent(String string) {
  4.                 mString = string;
  5.         }

  6.         public String getString() {
  7.                 return mString;
  8.         }

  9. }
复制代码
<br>第二步,我们到订阅者(消费者)中去注册这个事件到EventBus中,表示,该组件愿意接收状态改变通知。然后我们需要写一个方法,该方法实现了消费者接收到生产者的通知后需要实现的操作。如下红色代码块。
  1. public class MainActivity extends Activity {

  2.         private ListView listView;
  3.         @Override
  4.         protected void onCreate(Bundle savedInstanceState) {
  5.                 super.onCreate(savedInstanceState);
  6.                 setContentView(R.layout.activity_main);
  7.                 listView = (ListView) findViewById(R.id.lv);
  8.                 adapter = new MonkeyListAdapter(MainActivity.this);
  9.                 listView.setAdapter(adapter);
  10.                 adapter.notifyDataSetChanged();
  11.                 initData();
  12.                 // 加载下一页的数据,此时为加载第一页的数据
  13.                 nextPage();
  14. <font color="#ff0000">                EventBus.getDefault().register(this);        //注册使用,接受各个事件更新</font>
  15.                 
  16.                 
  17.         }
  18.         private MonkeyListAdapter adapter;
  19.         // 当前的页数
  20.         private int pageNow = 0;
  21.         // 获取下一页的数据并显示在List里
  22.         private void nextPage() {
  23.                 if (pageNow < source.size()) {
  24.                         // 模拟获取数据
  25.                         ArrayList<HashMap<String, Object>> newData = source.get(pageNow++);
  26.                         // 向适配器里追加内容
  27.                         adapter.appendItem(newData);
  28.                         adapter.notifyDataSetChanged();// 数据更改的通知
  29.                 }
  30.         }

  31.         private final int SHOW = 1;
  32.         private Handler handler = new Handler() {
  33.                 public void handleMessage (Message msg) {
  34.                         switch (msg.what) {
  35.                         case SHOW:
  36.                                 nextPage();
  37.                                 break;
  38.                         }
  39.                 }
  40.         };

  41.         private List<ArrayList<HashMap<String, Object>>> source;
  42.         // 初始化数据源
  43.         private void initData() {
  44.                 source = new ArrayList<ArrayList<HashMap<String, Object>>>();
  45.                 for (int p = 0; p < 10; p++) {// 有4页的数据
  46.                         ArrayList<HashMap<String, Object>> page = new ArrayList<HashMap<String, Object>>();
  47.                         for (int i = 0; i < 30; i++) {// 每页有30条数据
  48.                                 HashMap<String, Object> item = new HashMap<String, Object>();
  49.                                 item.put("monkey", "monkey:" + i);
  50.                                 page.add(item);
  51.                         }
  52.                         source.add(page);
  53.                 }
  54.         }

  55. <font color="#ff0000">        //接收到通知事件,发生改变(MainThread为主线程操作)
  56.         public void onEventMainThread(MyEvent event){
  57.                 handler.sendEmptyMessage(SHOW);
  58.                 Toast.makeText(this, event.getString(), Toast.LENGTH_SHORT).show();
  59.     }
  60. </font>        
  61. }
复制代码
关于 onEventMainThread() 这个方法,我引用一下链接中介绍的文字:

EventBus可以向不同的线程中发布事件,在ThreadMode 枚举中定义了4个线程,只需要在事件响应函数名称“onEvent”后面添加对应的线程类型名称,则还事件响应函数就会在对应的线程中执行,比如事件函数“onEventAsync”就会在另外一个异步线程中执行,ThreadMode定义的4个线程类型如下:
PostThread:事件响应函数和事件发布在同一线程中执行。这个是默认值,这样可以避免线程切换。
MainThread:事件响应函数会在Android应用的主线程(大部分情况下都是UI线程)中执行。
BackgroundThread:事件响应函数会在一个后台线程中执行。如果事件发布函数不是在主线程中,则会立即在事件发布线程中执行响应函数。如果事件发布函数在主线程中,EventBus则会在唯一的一个后台线程中按照顺序来执行所有的后台事件响应函数。

上面的3种事件响应函数,应该能够很快的执行完,不然的话会阻塞各自的事件发布。


最后,我们来看看生产者。生产者的操作更为简单,只负责发布事件消息给消费者即可,如下红色代码块。
  1. public class MonkeyListAdapter extends BaseAdapter {  
  2.         
  3.     private ArrayList<HashMap<String, Object>> listItems;  
  4.     private Context mContext;  
  5.     private LayoutInflater inflater;  
  6.     String Str;
  7.   
  8.     public MonkeyListAdapter(Context context) {  
  9.         this.mContext = context;  
  10.         inflater = LayoutInflater.from(this.mContext);  
  11.         listItems = new ArrayList<HashMap<String, Object>>();  
  12.     }  
  13.   
  14.     public void appendItem(ArrayList<HashMap<String, Object>> listItems) {  
  15.         this.listItems.addAll(listItems);  
  16.     }  
  17.   
  18.     @Override  
  19.     public int getCount() {  
  20.         return listItems.size();  
  21.     }  
  22.   
  23.     
  24.     @Override  
  25.     public Object getItem(int position) {  
  26.         return null;  
  27.     }  
  28.   
  29.     @Override  
  30.     public long getItemId(int position) {  
  31.         return 0;  
  32.     }  
  33.   
  34.     static class MonkeyItem {  
  35.         public TextView tv;  
  36.     }  
  37.   
  38.     @Override  
  39.     public View getView(int position, View convertView, ViewGroup parent) {  
  40.         MonkeyItem item = null;  
  41. <font color="#ff0000">        if (position == getCount() - 1) {  
  42.             String hello = "加载下一页";
  43.             //向Event Bus发布事件
  44.             EventBus.getDefault().post(new MyEvent(hello));
  45.         }  </font>
  46.         if (convertView == null) {  
  47.             item = new MonkeyItem();  
  48.             convertView = inflater.inflate(R.layout.monkey_item, null);  
  49.             item.tv = (TextView) convertView  
  50.                     .findViewById(R.id.tv);  
  51.   
  52.             convertView.setTag(item);  
  53.         } else {  
  54.             item = (MonkeyItem) convertView.getTag();  
  55.         }  
  56.   
  57.         item.tv.setText(listItems.get(position).get("monkey")  
  58.                 .toString());  
  59.   
  60.         return convertView;  
  61.     }  
  62.     
  63.     
  64. }
复制代码
这样就完成了使用Android EventBus模式类库解耦组件间的通信了,对比一下之前的代码,是不是会发现,使用了EventBus模式的代码变得更简约和有利于维护了吗?如果你没发现,别着急,那一定是程序还不够复杂。你可以尝试整合更为复杂的程序。这仅仅是我的一点初步理解,不代表

展开阅读全文
打赏
0
4 收藏
分享
加载中
更多评论
打赏
0 评论
4 收藏
0
分享
返回顶部
顶部