参考资料:http://blog.csdn.net/lmj623565791/article/details/40920453
简介
最终效果如下图所示:
不同页面,不同进程要相互调用或者产生数据交换怎么办呢?
一般的做法:
目录Fragment在onCreate中去开启线程去访问网络获取数据,获取完成以后,通过handler去更新界面;
在目录的Fragment中提供一个接口,然后详细信息面板去注册这个接口,当发生点击时,去回调这个接口,让详细信息面板发生改变;
广播、静态方法等等。
EventBus定义:是一个发布 / 订阅的事件总线。包含4个成分:发布者,订阅者,事件,总线:
使用方法
几个主要的方法:
- EventBus.getDefault().register(this);//订阅事件
- EventBus.getDefault().post(object);//发布事件
- EventBus.getDefault().unregister(this);//取消订阅
代码学习:
MainActivity:
public class MainActivity extends FragmentActivity {
private TextView txtData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtData=(TextView) findViewById(R.id.txt_data);
//注册eventBus
EventBus.getDefault().register(this);
}
@Override
protected void onDestroy() {
// Unregister
EventBus.getDefault().unregister(this);
super.onDestroy();
}
/** List点击时会发送些事件,接收到事件后更新详情 */
public void onEventMainThread(Item item) {
if (item != null)
txtData.setText(item.content);
}
}
代码片中:
- 在Activity的Oncreate()中订阅事件(注册);
- 在Activity被销毁时取消订阅(反注册)
- onEventMainThread订阅函数,接收list点击时发布的事件,将内容显示到界面
ItemListFragment:
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
// 开启线程加载列表
new Thread()
{
public void run()
{
try
{
Thread.sleep(2000); // 模拟延时
// 发布事件,在后台线程发的事件
EventBus.getDefault().post(new ItemListEvent(Item.ITEMS));
} catch (InterruptedException e)
{
e.printStackTrace();
}
};
}.start();
}
public void onEventMainThread(ItemListEvent event)
{
setListAdapter(new ArrayAdapter<Item>(getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1, event.getItems()));
}
@Override
public void onListItemClick(ListView listView, View view, int position,
long id)
{
super.onListItemClick(listView, view, position, id);
EventBus.getDefault().post(getListView().getItemAtPosition(position));
}
代码解析:
- 在子线程中模拟从服务器端拉取数据,然后通过EventBus发布事件
- onEventMainThread()订阅函数,接收子线程发布的事件,将列表内容通过适配器显示到界面
- List的点击事件,通过EventBus发布事件
DetailFragment:
/** List点击时会发送些事件,接收到事件后更新详情 */
public void onEventMainThread(Item item)
{
if (item != null)
tvDetail.setText(item.content);
}
在代码片中:
- onEventMainThread订阅函数,接收列表的点击事件,将列表内容显示到新的fragment中
Item实体类:
public static List<Item> ITEMS = new ArrayList<Item>();
static
{
// Add 6 sample items.
addItem(new Item("1", "Item 1"));
addItem(new Item("2", "Item 2"));
addItem(new Item("3", "Item 3"));
addItem(new Item("4", "Item 4"));
addItem(new Item("5", "Item 5"));
addItem(new Item("6", "Item 6"));
}
private static void addItem(Item item)
{
ITEMS.add(item);
}
public Item(String id, String content)
{
this.id = id;
this.content = content;
}
@Override
public String toString()
{
return content;
}
代码解析:
- Item实体类,列表的每一项,在列表的点击时间时作为发布事件的参数
Event实体类:
/** 列表加载事件 */
public static class ItemListEvent
{
private List<Item> items;
public ItemListEvent(List<Item> items)
{
this.items = items;
}
public List<Item> getItems()
{
return items;
}
}
代码解析:
- Event实体类,在子线程数据加载完成时作为发布事件的参数
注意几个不同的订阅函数:
- onEventMainThread代表这个方法会在UI线程执行;
- onEventPostThread代表这个方法会在当前发布事件的线程执行;
- BackgroundThread这个方法,如果在非UI线程发布的事件,则直接执行,和发布在同一个线程中。如果在UI线程发布的事件,则加入后台任务队列,使用线程池一个接一个调用;
- Async 加入后台任务队列,使用线程池调用,注意没有BackgroundThread中一个接一个。
原理解析
Register方法:
private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
methodName);
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
代码解析:
- 调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber的class,以及methodName,返回一个List;
那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个list;
本质上就是扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ;
value:CopyOnWriteArrayList )中;
post方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case PostThread:
invokeSubscriber(subscription, event); //通过反射调用
break;
case MainThread:
//省略部分代码 break;
case BackgroundThread:
//省略部分代码 break;
case Async:
asyncPoster.enqueue(subscription, event);
break;
default:
//省略部分代码
}
代码解析:
- register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。
总结:
register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。
优缺点:
- 代码非常的优雅。耦合基本没有,简化事件流程,对于复杂的页面交互,多进程通信很有帮助;
- 消耗更多内存,反射可能会效率低。