从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模式之前,我们组件间是怎么通信的。用代码说话。
要实现某一组件发生状态的变化时,去通知另外一个组件做相应的变化操作,首先要实现一个接口,这个接口定义了一个方法,是用来通信去加载下一页数据的。
- public interface NextPageCallBack {
- public void nextPage();
- }
- public class MainActivity extends Activity {
-
- private ListView mListView;
- private ListAdapter mAdapter;
- // 当前的页数
- private int pageNow = 0;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mListView = (ListView) findViewById(R.id.listview);
- <font color="#ff0000"> adapter = new ListAdapter(MainActivity.this,
- new NextPageCallBack() {
- public void nextPage() {
- handler.sendEmptyMessage(SHOW);
- }
- }); </font>
-
- mListView .setAdapter(
- mAdapter );// 设置adapter
- mAdapter .notifyDataSetChanged();// 数据更改的通知
- initData();
- // 加载下一页的数据,此时为加载第一页的数据
- nextPage();
- }
-
- // 获取下一页的数据并显示在List里
- private void nextPage() {
- if (pageNow < source.size()) {
- // 模拟获取数据
- ArrayList<HashMap<String, Object>> newData = source.get(pageNow++);
- // 向适配器里追加内容
- mAdapter .appendItem(newData);
- mAdapter .notifyDataSetChanged();
- Toast.makeText(this, "加载下一页", Toast.LENGTH_SHORT).show();
- }
- }
-
- private final int SHOW = 1;
-
- private Handler handler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SHOW:
- nextPage(); //自动加载数据
- break;
- }
- }
- };
- private List<ArrayList<HashMap<String, Object>>> source;
- // 初始化数据源
- private void initData() {
- source = new ArrayList<ArrayList<HashMap<String, Object>>>();
- for (int p = 0; p < 10; p++) {// 有4页的数据
- ArrayList<HashMap<String, Object>> page = new ArrayList<HashMap<String, Object>>();
- for (int i = 0; i < 30; i++) {// 每页有15条数据
- HashMap<String, Object> item = new HashMap<String, Object>();
- item.put("monkey", "monkey:" + i);
- page.add(item);
- }
- source.add(page);
- }
- }
-
- }
- public class ListAdapter extends BaseAdapter {
-
- private ArrayList<HashMap<String, Object>> listItems;
- private Context mContext;
- private LayoutInflater inflater;
- private NextPageCallBack nextPageCallBack;
-
- public ListAdapter(Context context, NextPageCallBack nextPageCallBack){
- this.mContext = context;
- this.nextPageCallBack = nextPageCallBack;
- inflater = LayoutInflater.from(this.mContext);
- listItems = new ArrayList<HashMap<String, Object>>();
- }
-
- public void appendItem(ArrayList<HashMap<String, Object>> listItems) {
- this.listItems.addAll(listItems);
- }
-
- @Override
- public int getCount() {
- // TODO Auto-generated method stub
- return listItems.size();
- }
-
- @Override
- public Object getItem(int arg0) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public long getItemId(int arg0) {
- // TODO Auto-generated method stub
- return 0;
- }
-
- static class MonkeyItem {
- public TextView tvname;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup arg2) {
- // TODO Auto-generated method stub
- MonkeyItem item = null;
- <font color="#ff0000"> if (position == getCount() - 1) {
- nextPageCallBack.nextPage();
- }
- </font>
- if (convertView == null) {
- item = new MonkeyItem();
- convertView = inflater.inflate(R.layout.item, null);
- item.tvname = (TextView) convertView.findViewById(R.id.textview);
- convertView.setTag(item);
- } else {
- item = (MonkeyItem) convertView.getTag();
- }
- item.tvname.setText(listItems.get(position).get("monkey").toString());
-
-
- return convertView;
- }
-
-
-
- }
第一部分附件下载:
<br>从上面各个组件的代码来看,组件间的耦合度是相当高的,特别是在组件间通信越来越频繁和复杂的时候,代码会变得越来越难维护,甚至当你出现一些细微的bug的时候会感觉到无从下手。在软件开发的过程中,模块之间应该尽量减少耦合度,不要违背软件工程中“ 高内聚低耦合 ”的原则。 我也不懂啦。也就是链接介绍说的: 随着应用功能的增加,需要监听的事件越来越多,而越来越多的控件需要监听不同的事件,则导致越来越多的控件需要注册到各种事件管理器上。
<br>为了避免上述说的情况发生,我们来看看如何使用EventBus模式来解耦android组件间的通信。
<br>要使用EventBus组件必须先添加库支持。大家可以官网下载。在本帖的附件demo中也有。
<br>然后,我们新建一个类,这个类定义了某个事件的属性和方法。其实也没有什么。
- public class MyEvent {
-
- private String mString;
-
- public MyEvent(String string) {
- mString = string;
- }
-
- public String getString() {
- return mString;
- }
-
- }
- public class MainActivity extends Activity {
-
- private ListView listView;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- listView = (ListView) findViewById(R.id.lv);
- adapter = new MonkeyListAdapter(MainActivity.this);
- listView.setAdapter(adapter);
- adapter.notifyDataSetChanged();
- initData();
- // 加载下一页的数据,此时为加载第一页的数据
- nextPage();
- <font color="#ff0000"> EventBus.getDefault().register(this); //注册使用,接受各个事件更新</font>
-
-
- }
- private MonkeyListAdapter adapter;
- // 当前的页数
- private int pageNow = 0;
- // 获取下一页的数据并显示在List里
- private void nextPage() {
- if (pageNow < source.size()) {
- // 模拟获取数据
- ArrayList<HashMap<String, Object>> newData = source.get(pageNow++);
- // 向适配器里追加内容
- adapter.appendItem(newData);
- adapter.notifyDataSetChanged();// 数据更改的通知
- }
- }
-
- private final int SHOW = 1;
- private Handler handler = new Handler() {
- public void handleMessage (Message msg) {
- switch (msg.what) {
- case SHOW:
- nextPage();
- break;
- }
- }
- };
-
- private List<ArrayList<HashMap<String, Object>>> source;
- // 初始化数据源
- private void initData() {
- source = new ArrayList<ArrayList<HashMap<String, Object>>>();
- for (int p = 0; p < 10; p++) {// 有4页的数据
- ArrayList<HashMap<String, Object>> page = new ArrayList<HashMap<String, Object>>();
- for (int i = 0; i < 30; i++) {// 每页有30条数据
- HashMap<String, Object> item = new HashMap<String, Object>();
- item.put("monkey", "monkey:" + i);
- page.add(item);
- }
- source.add(page);
- }
- }
-
- <font color="#ff0000"> //接收到通知事件,发生改变(MainThread为主线程操作)
- public void onEventMainThread(MyEvent event){
- handler.sendEmptyMessage(SHOW);
- Toast.makeText(this, event.getString(), Toast.LENGTH_SHORT).show();
- }
- </font>
- }
EventBus可以向不同的线程中发布事件,在ThreadMode 枚举中定义了4个线程,只需要在事件响应函数名称“onEvent”后面添加对应的线程类型名称,则还事件响应函数就会在对应的线程中执行,比如事件函数“onEventAsync”就会在另外一个异步线程中执行,ThreadMode定义的4个线程类型如下:
PostThread:事件响应函数和事件发布在同一线程中执行。这个是默认值,这样可以避免线程切换。
MainThread:事件响应函数会在Android应用的主线程(大部分情况下都是UI线程)中执行。
BackgroundThread:事件响应函数会在一个后台线程中执行。如果事件发布函数不是在主线程中,则会立即在事件发布线程中执行响应函数。如果事件发布函数在主线程中,EventBus则会在唯一的一个后台线程中按照顺序来执行所有的后台事件响应函数。
上面的3种事件响应函数,应该能够很快的执行完,不然的话会阻塞各自的事件发布。
最后,我们来看看生产者。生产者的操作更为简单,只负责发布事件消息给消费者即可,如下红色代码块。
- public class MonkeyListAdapter extends BaseAdapter {
-
- private ArrayList<HashMap<String, Object>> listItems;
- private Context mContext;
- private LayoutInflater inflater;
- String Str;
-
- public MonkeyListAdapter(Context context) {
- this.mContext = context;
- inflater = LayoutInflater.from(this.mContext);
- listItems = new ArrayList<HashMap<String, Object>>();
- }
-
- public void appendItem(ArrayList<HashMap<String, Object>> listItems) {
- this.listItems.addAll(listItems);
- }
-
- @Override
- public int getCount() {
- return listItems.size();
- }
-
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- static class MonkeyItem {
- public TextView tv;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- MonkeyItem item = null;
- <font color="#ff0000"> if (position == getCount() - 1) {
- String hello = "加载下一页";
- //向Event Bus发布事件
- EventBus.getDefault().post(new MyEvent(hello));
- } </font>
- if (convertView == null) {
- item = new MonkeyItem();
- convertView = inflater.inflate(R.layout.monkey_item, null);
- item.tv = (TextView) convertView
- .findViewById(R.id.tv);
-
- convertView.setTag(item);
- } else {
- item = (MonkeyItem) convertView.getTag();
- }
-
- item.tv.setText(listItems.get(position).get("monkey")
- .toString());
-
- return convertView;
- }
-
-
- }