1、概述
關於Eventbus的介紹,前面已經有兩篇:Android EventBus實戰 沒聽過你就out了和Android EventBus原始碼解析 帶你深入理解EventBus , 如果你覺得還有問題,沒關係,接下來我帶大家手把手打造從無到有的編寫這樣的框架~~~
首先我們回顧一下,這玩意就是在register時,掃描類中複合命名規範的方法,存到一個map,然後post的時候,查詢到匹配的方法,反射呼叫;好,那麼根據這一句話,我們就開始編寫框架之旅~~~
2、依然是原來的配方
以下出現的例項程式碼和Android EventBus實戰 沒聽過你就out了基本一致,所以我就貼出部分
1、ItemListFragment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
package com.angeldevil.eventbusdemo; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; import com.angeldevil.eventbusdemo.Event.ItemListEvent; import com.zhy.eventbus.EventBus; public class ItemListFragment extends ListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Register EventBus.getInstatnce().register(this); } @Override public void onDestroy() { super.onDestroy(); // Unregister EventBus.getInstatnce().unregister(this); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 開啟執行緒載入列表 new Thread() { public void run() { try { Thread.sleep(2000); // 模擬延時 // 釋出事件,在後臺執行緒發的事件 EventBus.getInstatnce().post(new ItemListEvent(Item.ITEMS)); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); } public void onEventUI(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.getInstatnce().post(getListView().getItemAtPosition(position)); } } |
2、ItemDetailFragment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.angeldevil.eventbusdemo; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.zhy.eventbus.EventBus; public class ItemDetailFragment extends Fragment { private TextView tvDetail; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // register EventBus.getInstatnce().register(this); } @Override public void onDestroy() { super.onDestroy(); // Unregister EventBus.getInstatnce().unregister(this); } /** List點選時會傳送些事件,接收到事件後更新詳情 */ public void onEventUI(Item item) { if (item != null) tvDetail.setText(item.content); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_item_detail, container, false); tvDetail = (TextView) rootView.findViewById(R.id.item_detail); return rootView; } } |
可以看到,我們在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 2 3 4 5 6 7 8 9 10 11 |
private static EventBus eventBus = new EventBus(); public static EventBus getInstatnce() { return eventBus; } private EventBus() { mHandler = new Handler(Looper.getMainLooper()); } |
然後在構造方法中初始化了一個mHandler,沒錯,它就是用來在處理在UI執行緒呼叫方法的。
接下來看register
2、register
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
/* * 我們的強大的map,儲存我們的方法 */ private static Map<Class, CopyOnWriteArrayList<SubscribeMethod>> mSubscribeMethodsByEventType = new HashMap<Class, CopyOnWriteArrayList<SubscribeMethod>>(); public void register(Object subscriber) { Class clazz = subscriber.getClass(); Method[] methods = clazz.getDeclaredMethods(); CopyOnWriteArrayList<SubscribeMethod> subscribeMethods = null; /** * 遍歷所有方法 */ for (Method method : methods) { String methodName = method.getName(); /** * 判斷方法是否以onEvent的開頭 */ if (methodName.startsWith("onEvent")) { SubscribeMethod subscribeMethod = null; // 方法命中提前在什麼執行緒執行。預設在UI執行緒 String threadMode = methodName.substring("onEvent".length()); ThreadMode mode = ThreadMode.UI; Class<?>[] parameterTypes = method.getParameterTypes(); // 引數的個數為1 if (parameterTypes.length == 1) { Class<?> eventType = parameterTypes[0]; synchronized (this) { if (mSubscribeMethodsByEventType.containsKey(eventType)) { subscribeMethods = mSubscribeMethodsByEventType .get(eventType); } else { subscribeMethods = new CopyOnWriteArrayList<SubscribeMethod>(); mSubscribeMethodsByEventType.put(eventType, subscribeMethods); } } if (threadMode.equals("Async")) { mode = ThreadMode.Async; } // 提取出method,mode,方法所在類物件,存數的型別封裝為SubscribeMethod subscribeMethod = new SubscribeMethod(method, mode, subscriber); subscribeMethods.add(subscribeMethod); } } } } enum ThreadMode { UI, Async } class SubscribeMethod { Method method; ThreadMode threadMode; Object subscriber; public SubscribeMethod(Method method, ThreadMode threadMode, Object subscriber) { this.method = method; this.threadMode = threadMode; this.subscriber = subscriber; } } |
可以看到我們使用了一個Map儲存所有的方法,key為引數的型別class;value為CopyOnWriteArrayList<SubscribeMethod>
這裡我們封裝了一個SubscribeMethod,這個裡面儲存了我們需要執行方法的所有引數,畢竟我們執行時,需要該方法,該方法所在的物件,以及在什麼執行緒執行;三個物件足以,當然也缺一不可了~~
register裡面,我們遍歷該類的所有方法,找到onEvent開頭的,封裝成SubscribeMethod,存在Map裡面,當然了,一個引數型別對應很多方法,所以value是個CopyOnWriteArrayList。
掃描完成,我們就完成了將方法的儲存。
還有一點,我們這裡預設在UI執行緒執行,如果方法是onEventAsync則認為在子執行緒執行,我們也只支援這兩種模式,簡化一點~
3、post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private static ThreadLocal<PostingThread> mPostingThread = new ThreadLocal<PostingThread>() { @Override public PostingThread get() { return new PostingThread(); } }; public void post(Object eventTypeInstance) { //拿到該執行緒中的PostingThread物件 PostingThread postingThread = mPostingThread.get(); postingThread.isMainThread = Looper.getMainLooper() == Looper .myLooper(); //將事件加入事件佇列 List<Object> eventQueue = postingThread.mEventQueue; eventQueue.add(eventTypeInstance); //防止多次呼叫 if (postingThread.isPosting) { return; } postingThread.isPosting = true; //取出所有事件進行呼叫 while (!eventQueue.isEmpty()) { Object eventType = eventQueue.remove(0); postEvent(eventType, postingThread); } postingThread.isPosting = false; } |
我們這裡學習了原始碼,也搞了個當前執行緒中的變數,儲存了一個事件佇列以及事件的狀態;
1 2 3 4 5 6 |
class PostingThread { List<Object> mEventQueue = new ArrayList<Object>(); boolean isMainThread; boolean isPosting; } |
最終釋出的事件先加入到事件佇列,然後再取出來呼叫postEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
private void postEvent(final Object eventType, PostingThread postingThread) { CopyOnWriteArrayList<SubscribeMethod> subscribeMethods = null; synchronized (this) { subscribeMethods = mSubscribeMethodsByEventType.get(eventType .getClass()); } for (final SubscribeMethod subscribeMethod : subscribeMethods) { if (subscribeMethod.threadMode == ThreadMode.UI) { if (postingThread.isMainThread) { invokeMethod(eventType, subscribeMethod); } else { mHandler.post(new Runnable() { @Override public void run() { invokeMethod(eventType, subscribeMethod); } }); } } else { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { invokeMethod(eventType, subscribeMethod); return null; } }; } } } |
postEvent也很簡單,直接根據引數型別,去map改到該方法,根據其threadMode,如果在UI執行緒,則判斷當前執行緒,如果是UI執行緒,直接呼叫,否則通過handler執行;
如果非UI執行緒,這裡我們直接開啟了一個Thread去執行;
invokeMethod很簡單,就是反射呼叫方法了~
1 2 3 4 5 6 7 8 9 10 11 |
private void invokeMethod(Object eventType, SubscribeMethod subscribeMethod) { try { subscribeMethod.method .invoke(subscribeMethod.subscriber, eventType); } catch (Exception e) { e.printStackTrace(); } } |
4、unregister
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public void unregister(Object subscriber) { Class clazz = subscriber.getClass(); Method[] methods = clazz.getDeclaredMethods(); List<SubscribeMethod> subscribeMethods = null; for (Method method : methods) { String methodName = method.getName(); if (methodName.startsWith("onEvent")) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { synchronized (this) { mSubscribeMethodsByEventType.remove(parameterTypes[0]); } } } } } |
unregister時,由於我們沒有存任何的輔助狀態,我們只能再去遍歷了方法了~~不過通過這個,也能反應出EventBus內部好幾個Map的作用了~~
並且,我們也不支援一些狀態的查詢,還是因為我們沒有存一些輔助狀態,例如isRegister等等。
到此,我們的EventBus就寫好了,100多行程式碼,肯定沒有EventBus健壯,主要目的還是學習人家的思想,經過自己寫了這麼個類,我相信對於EventBus的理解就更深刻了~面試的時候,恨不得拿只筆寫給面試官看,哈哈~~
5、EventBus最佳實踐
前面的文章,很多朋友問,如果我多個方法引數都一樣,豈不是post一個此引數,會多個方法呼叫;而此時我想呼叫指定的方法怎麼辦?
還有,專案中會有很多地方去接收List引數,而List<T>中的泛型是不一致的,所以也可能post(List)時,會呼叫很多方法,造成出錯。
的確,上述,不加處理肯定會出現;
但是,推薦大家在使用EventBus的時候,建立一個事件類,把你的每一個引數(或者可能發生衝突的引數),封裝成一個類:
例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Event { public static class UserListEvent { public List<User> users ; } public static class ItemListEvent { public List<Item> items; } } |
這樣的話,就不會發生什麼呼叫衝突了~~