Android 框架炼成 教你如何写组件间通信框架EventBus

1、概述

关于Eventbus的介绍,前面已经有两篇:Android EventBus实战 没听过你就out了Android EventBus源码解析 带你深入理解EventBus , 如果你觉得还有问题,没关系,接下来我带大家手把手打造从无到有的编写这样的框架~~~

首先我们回顾一下,这玩意就是在register时,扫描类中复合命名规范的方法,存到一个map,然后post的时候,查找到匹配的方法,反射调用;好,那么根据这一句话,我们就开始编写框架之旅~~~

2、依然是原来的配方

以下出现的实例代码和Android EventBus实战 没听过你就out了基本一致,所以我就贴出部分

1、ItemListFragment

  1. package com.angeldevil.eventbusdemo;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v4.app.ListFragment;  
  5. import android.view.View;  
  6. import android.widget.ArrayAdapter;  
  7. import android.widget.ListView;  
  8.   
  9. import com.angeldevil.eventbusdemo.Event.ItemListEvent;  
  10. import com.zhy.eventbus.EventBus;  
  11.   
  12. public class ItemListFragment extends ListFragment  
  13. {  
  14.   
  15.     @Override  
  16.     public void onCreate(Bundle savedInstanceState)  
  17.     {  
  18.         super.onCreate(savedInstanceState);  
  19.         // Register  
  20.         EventBus.getInstatnce().register(this);  
  21.     }  
  22.   
  23.     @Override  
  24.     public void onDestroy()  
  25.     {  
  26.         super.onDestroy();  
  27.         // Unregister  
  28.         EventBus.getInstatnce().unregister(this);  
  29.     }  
  30.   
  31.     @Override  
  32.     public void onViewCreated(View view, Bundle savedInstanceState)  
  33.     {  
  34.         super.onViewCreated(view, savedInstanceState);  
  35.         // 开启线程加载列表  
  36.         new Thread()  
  37.         {  
  38.             public void run()  
  39.             {  
  40.                 try  
  41.                 {  
  42.                     Thread.sleep(2000); // 模拟延时  
  43.                     // 发布事件,在后台线程发的事件  
  44.                     EventBus.getInstatnce().post(new ItemListEvent(Item.ITEMS));  
  45.                 } catch (InterruptedException e)  
  46.                 {  
  47.                     e.printStackTrace();  
  48.                 }  
  49.             };  
  50.         }.start();  
  51.     }  
  52.   
  53.     public void onEventUI(ItemListEvent event)  
  54.     {  
  55.         setListAdapter(new ArrayAdapter<Item>(getActivity(),  
  56.                 android.R.layout.simple_list_item_activated_1,  
  57.                 android.R.id.text1, event.getItems()));  
  58.     }  
  59.   
  60.     @Override  
  61.     public void onListItemClick(ListView listView, View view, int position,  
  62.             long id)  
  63.     {  
  64.         super.onListItemClick(listView, view, position, id);  
  65.         EventBus.getInstatnce().post(getListView().getItemAtPosition(position));  
  66.     }  
  67.   
  68. }  

2、ItemDetailFragment

  1. package com.angeldevil.eventbusdemo;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v4.app.Fragment;  
  5. import android.view.LayoutInflater;  
  6. import android.view.View;  
  7. import android.view.ViewGroup;  
  8. import android.widget.TextView;  
  9.   
  10. import com.zhy.eventbus.EventBus;  
  11.   
  12. public class ItemDetailFragment extends Fragment  
  13. {  
  14.   
  15.     private TextView tvDetail;  
  16.   
  17.     @Override  
  18.     public void onCreate(Bundle savedInstanceState)  
  19.     {  
  20.         super.onCreate(savedInstanceState);  
  21.         // register  
  22.         EventBus.getInstatnce().register(this);  
  23.     }  
  24.   
  25.     @Override  
  26.     public void onDestroy()  
  27.     {  
  28.         super.onDestroy();  
  29.         // Unregister  
  30.         EventBus.getInstatnce().unregister(this);  
  31.     }  
  32.   
  33.     /** List点击时会发送些事件,接收到事件后更新详情 */  
  34.     public void onEventUI(Item item)  
  35.     {  
  36.         if (item != null)  
  37.             tvDetail.setText(item.content);  
  38.     }  
  39.   
  40.     @Override  
  41.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  42.             Bundle savedInstanceState)  
  43.     {  
  44.         View rootView = inflater.inflate(R.layout.fragment_item_detail,  
  45.                 container, false);  
  46.         tvDetail = (TextView) rootView.findViewById(R.id.item_detail);  
  47.         return rootView;  
  48.     }  
  49. }  

可以看到,我们在ItemListFragment里面使用了:

EventBus.getInstatnce().post(new ItemListEvent(Item.ITEMS));去发布了一个事件,然后更新了我们的列表;

点击Item的时候,使用EventBus.getInstatnce().post(getListView().getItemAtPosition(position));发布了一个事件,更新了我们的ItemDetailFragment的列表;

效果:


效果图和之前的一摸一样~~~

但是请注意,现在我们用的是EventBus.getInstatnce();并发是EventBus.getDefault();并且看下包名import com.zhy.eventbus.EventBus;

我想你应该明白了,这是我们自己写的类来实现的~~~~

好了,接下来就带大家一起实现这个类~~

ps :以上代码和效果图,完全是为了博客的完整性,勿见怪~~

3、无中生有

1、getInstance

我们这里为了方便,直接简单粗暴的使用恶汉模式创建单例:

  1. private static EventBus eventBus = new EventBus();  
  2.   
  3. public static EventBus getInstatnce()  
  4. {  
  5.     return eventBus;  
  6. }  
  7.   
  8. private EventBus()  
  9. {  
  10.     mHandler = new Handler(Looper.getMainLooper());  
  11. }  

然后在构造方法中初始化了一个mHandler,没错,它就是用来在处理在UI线程调用方法的。

接下来看register

2、register

  1. /* 
  2.      * 我们的强大的map,存储我们的方法 
  3.      */  
  4.     private static Map<Class, CopyOnWriteArrayList<SubscribeMethod>> mSubscribeMethodsByEventType = new HashMap<Class, CopyOnWriteArrayList<SubscribeMethod>>();  
  5.   
  6.     public void register(Object subscriber)  
  7.     {  
  8.   
  9.         Class clazz = subscriber.getClass();  
  10.         Method[] methods = clazz.getDeclaredMethods();  
  11.   
  12.         CopyOnWriteArrayList<SubscribeMethod> subscribeMethods = null;  
  13.         /** 
  14.          * 遍历所有方法 
  15.          */  
  16.         for (Method method : methods)  
  17.         {  
  18.             String methodName = method.getName();  
  19.             /** 
  20.              * 判断方法是否以onEvent的开头 
  21.              */  
  22.             if (methodName.startsWith("onEvent"))  
  23.             {  
  24.                 SubscribeMethod subscribeMethod = null;  
  25.                 // 方法命中提前在什么线程运行。默认在UI线程  
  26.                 String threadMode = methodName.substring("onEvent".length());  
  27.                 ThreadMode mode = ThreadMode.UI;  
  28.   
  29.                 Class<?>[] parameterTypes = method.getParameterTypes();  
  30.   
  31.                 // 参数的个数为1  
  32.                 if (parameterTypes.length == 1)  
  33.                 {  
  34.                     Class<?> eventType = parameterTypes[0];  
  35.   
  36.                     synchronized (this)  
  37.                     {  
  38.   
  39.                         if (mSubscribeMethodsByEventType.containsKey(eventType))  
  40.                         {  
  41.                             subscribeMethods = mSubscribeMethodsByEventType  
  42.                                     .get(eventType);  
  43.                         } else  
  44.                         {  
  45.                             subscribeMethods = new CopyOnWriteArrayList<SubscribeMethod>();  
  46.                             mSubscribeMethodsByEventType.put(eventType,  
  47.                                     subscribeMethods);  
  48.                         }  
  49.                     }  
  50.   
  51.                     if (threadMode.equals("Async"))  
  52.                     {  
  53.                         mode = ThreadMode.Async;  
  54.                     }  
  55.                     // 提取出method,mode,方法所在类对象,存数的类型封装为SubscribeMethod  
  56.                     subscribeMethod = new SubscribeMethod(method, mode,  
  57.                             subscriber);  
  58.                     subscribeMethods.add(subscribeMethod);  
  59.                 }  
  60.             }  
  61.   
  62.         }  
  63.     }  
  64.   
  65.     enum ThreadMode  
  66.     {  
  67.         UI, Async  
  68.     }  
  69.   
  70.     class SubscribeMethod  
  71.     {  
  72.         Method method;  
  73.         ThreadMode threadMode;  
  74.         Object subscriber;  
  75.   
  76.         public SubscribeMethod(Method method, ThreadMode threadMode,  
  77.                 Object subscriber)  
  78.         {  
  79.             this.method = method;  
  80.             this.threadMode = threadMode;  
  81.             this.subscriber = subscriber;  
  82.         }  
  83.   
  84.     }  


可以看到我们使用了一个Map存储所有的方法,key为参数的类型class;value为CopyOnWriteArrayList<SubscribeMethod>

这里我们封装了一个SubscribeMethod,这个里面存储了我们需要运行方法的所有参数,毕竟我们运行时,需要该方法,该方法所在的对象,以及在什么线程运行;三个对象足以,当然也缺一不可了~~

register里面,我们遍历该类的所有方法,找到onEvent开头的,封装成SubscribeMethod,存在Map里面,当然了,一个参数类型对应很多方法,所以value是个CopyOnWriteArrayList。

扫描完成,我们就完成了将方法的存储。

还有一点,我们这里默认在UI线程执行,如果方法是onEventAsync则认为在子线程执行,我们也只支持这两种模式,简化一点~

3、post

  1. private static ThreadLocal<PostingThread> mPostingThread = new ThreadLocal<PostingThread>()  
  2.     {  
  3.         @Override  
  4.         public PostingThread get()  
  5.         {  
  6.             return new PostingThread();  
  7.         }  
  8.     };  
  9.       
  10.       
  11.   
  12.     public void post(Object eventTypeInstance)  
  13.     {  
  14.         //拿到该线程中的PostingThread对象  
  15.         PostingThread postingThread = mPostingThread.get();  
  16.         postingThread.isMainThread = Looper.getMainLooper() == Looper  
  17.                 .myLooper();  
  18.         //将事件加入事件队列  
  19.         List<Object> eventQueue = postingThread.mEventQueue;  
  20.         eventQueue.add(eventTypeInstance);  
  21.         //防止多次调用  
  22.         if (postingThread.isPosting)  
  23.         {  
  24.             return;  
  25.         }  
  26.         postingThread.isPosting = true;  
  27.         //取出所有事件进行调用  
  28.         while (!eventQueue.isEmpty())  
  29.         {  
  30.             Object eventType = eventQueue.remove(0);  
  31.             postEvent(eventType, postingThread);  
  32.         }  
  33.         postingThread.isPosting = false;  
  34.   
  35.     }  

我们这里学习了源码,也搞了个当前线程中的变量,存储了一个事件队列以及事件的状态;

  1. class PostingThread  
  2. {  
  3.     List<Object> mEventQueue = new ArrayList<Object>();  
  4.     boolean isMainThread;  
  5.     boolean isPosting;  
  6. }  

最终发布的事件先加入到事件队列,然后再取出来调用postEvent

  1. private void postEvent(final Object eventType, PostingThread postingThread)  
  2.     {  
  3.         CopyOnWriteArrayList<SubscribeMethod> subscribeMethods = null;  
  4.         synchronized (this)  
  5.         {  
  6.             subscribeMethods = mSubscribeMethodsByEventType.get(eventType  
  7.                     .getClass());  
  8.         }  
  9.   
  10.         for (final SubscribeMethod subscribeMethod : subscribeMethods)  
  11.         {  
  12.   
  13.             if (subscribeMethod.threadMode == ThreadMode.UI)  
  14.             {  
  15.                 if (postingThread.isMainThread)  
  16.                 {  
  17.                     invokeMethod(eventType, subscribeMethod);  
  18.                 } else  
  19.                 {  
  20.                     mHandler.post(new Runnable()  
  21.                     {  
  22.                         @Override  
  23.                         public void run()  
  24.                         {  
  25.                             invokeMethod(eventType, subscribeMethod);  
  26.                         }  
  27.                     });  
  28.                 }  
  29.             } else  
  30.             {  
  31.                 new AsyncTask<Void, Void, Void>()  
  32.                 {  
  33.   
  34.                     @Override  
  35.                     protected Void doInBackground(Void... params)  
  36.                     {  
  37.                         invokeMethod(eventType, subscribeMethod);  
  38.                         return null;  
  39.                     }  
  40.                 };  
  41.                   
  42.             }  
  43.   
  44.         }  
  45.   
  46.     }  

postEvent也很简单,直接根据参数类型,去map改到该方法,根据其threadMode,如果在UI线程,则判断当前线程,如果是UI线程,直接调用,否则通过handler执行;

如果非UI线程,这里我们直接开启了一个Thread去执行;

invokeMethod很简单,就是反射调用方法了~

  1. private void invokeMethod(Object eventType, SubscribeMethod subscribeMethod)  
  2.     {  
  3.         try  
  4.         {  
  5.             subscribeMethod.method  
  6.                     .invoke(subscribeMethod.subscriber, eventType);  
  7.         } catch (Exception e)  
  8.         {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  

4、unregister

  1. public void unregister(Object subscriber)  
  2.     {  
  3.         Class clazz = subscriber.getClass();  
  4.         Method[] methods = clazz.getDeclaredMethods();  
  5.   
  6.         List<SubscribeMethod> subscribeMethods = null;  
  7.   
  8.         for (Method method : methods)  
  9.         {  
  10.             String methodName = method.getName();  
  11.   
  12.             if (methodName.startsWith("onEvent"))  
  13.             {  
  14.                 Class<?>[] parameterTypes = method.getParameterTypes();  
  15.                 if (parameterTypes.length == 1)  
  16.                 {  
  17.                     synchronized (this)  
  18.                     {  
  19.                         mSubscribeMethodsByEventType.remove(parameterTypes[0]);  
  20.                     }  
  21.                 }  
  22.             }  
  23.         }  
  24.   
  25.     }  

unregister时,由于我们没有存任何的辅助状态,我们只能再去遍历了方法了~~不过通过这个,也能反应出EventBus内部好几个Map的作用了~~

并且,我们也不支持一些状态的查询,还是因为我们没有存一些辅助状态,例如isRegister等等。

到此,我们的EventBus就写好了,100多行代码,肯定没有EventBus健壮,主要目的还是学习人家的思想,经过自己写了这么个类,我相信对于EventBus的理解就更深刻了~面试的时候,恨不得拿只笔写给面试官看,哈哈~~

5、EventBus最佳实践

前面的文章,很多朋友问,如果我多个方法参数都一样,岂不是post一个此参数,会多个方法调用;而此时我想调用指定的方法怎么办?

还有,项目中会有很多地方去接收List参数,而List<T>中的泛型是不一致的,所以也可能post(List)时,会调用很多方法,造成出错。

的确,上述,不加处理肯定会出现;

但是,推荐大家在使用EventBus的时候,创建一个事件类,把你的每一个参数(或者可能发生冲突的参数),封装成一个类:

例如:

  1. public class Event  
  2. {  
  3.     public static class UserListEvent  
  4.     {  
  5.         public List<User> users ;  
  6.     }  
  7.     public static class ItemListEvent  
  8.     {  
  9.         public List<Item> items;  
  10.     }  
  11.   
  12. }  

这样的话,就不会发生什么调用冲突了~~



源码点击下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值