源碼 github.com/harvie1208/…git
關鍵詞:觀察者模式、反射、自定義註解、線程調度github
手寫200行代碼,一步一步實現EventBus核心功能,看完能夠寫一套屬於本身的事件總線庫啦!數組
不知你們日常在看博客的時候有沒有和我遇到同樣的問題,就是看的是懂非懂,好像懂了,又好像沒懂。bash
主要有如下兩點:框架
在求知的路上,我也看了很多文章,有很是優秀的,也有缺這少那的。一路走來填了很多坑,後面我會將所學知識點整理出來分享給你們,儘可能作到通俗易懂的理論加完整案例源碼。一方面是對本身知識點的總結回顧,另外一方面也但願能幫助到有須要的同窗少走彎路。因技術水平有限,若有不正之處,還望各位不吝指教。異步
EventBus顧名思義就是事件總線,實際上就是一個事件發佈者/事件監聽者(訂閱者)
的框架, 發佈者發佈事件,Bus自動處理與分發,監聽者被動的接受。簡化各類異步和跳轉的通訊。oop
1.短信驗證碼登錄場景post
主登錄界面A->輸入手機號界面B->短信驗證碼界面C->登錄成功跳轉首頁D
需求:登錄成功後須要關閉A、B、C三個頁面
複製代碼
2.音樂播放場景ui
假如首頁有5個tab(包含5個fragment),每一個fragment中都有音樂播放狀態小圖標
需求:音樂播放或暫停時,須要更新全部播放狀態圖標
複製代碼
使用觀察者模式,在須要接收事件的方法上添加訂閱註解標識,並將此方法所在對象添加到訂閱者集合,
發送事件時遍歷訂閱者集合,在經過反射調用相關訂閱方法。
複製代碼
public class EventBus {
private static EventBus myBus;
public static EventBus getInstance(){
if (myBus==null){
synchronized (EventBus.class){
if (myBus==null){
myBus = new EventBus();
}
}
}
return myBus;
}
}
複製代碼
建立自定義註解@Subscribe用來標示訂閱方法spa
註解Annontation是Java5開始引入的新特徵,通俗來講就是爲程序的元素(類、方法、成員變量)添加標記用的
@Target(ElementType.METHOD) //表示此註解做用域在方法上
@Retention(RetentionPolicy.RUNTIME) //編譯程序處理完註解信息後存儲在class中,可由VM讀入
public @interface Subscribe {
ThreadModel thread();//用於指定被註解方法執行時所在的線程
}
複製代碼
使用註解
public class MainActivity extends AppCompatActivity {
@Subscribe(thread = ThreadModel.BACKGROUND)//指定在子線程中執行
public void haha(LoginEvent loginEvent){
Log.e("EventBus",loginEvent.getLoginStatus()+Thread.currentThread().getName());
}
}
複製代碼
先聲明一個集合用於存儲類對象和被註解的方法及線程模式
遍歷註冊對象的全部方法,取出帶有@Subscribe註解的方法
獲取參數類型數組,當前僅支持一個參數
獲取指定線程模式
構建訂閱者實例(方法、參數類型、線程模式),加入訂閱集合
public class EventBus {
//存儲訂閱類及方法參數
private Map<Object,List<Subscriber>> subscribeMethod;
public void register(Object obj){
if (obj==null){
return;
}
Class<?> mclazz = obj.getClass();
//獲取本類全部方法
Method[] methods = mclazz.getDeclaredMethods();
List<Subscriber> methods1 = new ArrayList<>();
for (Method method : methods){
//獲取帶有咱們Subscribe註解的方法
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe==null){
continue;
}
//獲取參數類型集合
Class<?>[] typeVariable = method.getParameterTypes();
if (typeVariable.length!=1){
continue;
}
ThreadModel threadModel = subscribe.thread();
Subscriber busMethod = new Subscriber(method,threadModel,typeVariable[0]);
methods1.add(busMethod);
}
if (methods1.size()>0){
subscribeMethod.put(obj,methods1);
}
}
}
複製代碼
public void unRegister(Object object){
if (subscribeMethod.containsKey(object)){
subscribeMethod.remove(object);
}
}
複製代碼
根據發送事件參數類型,遍歷集合找到對應方法
判斷線程模式,主線程用handler處理,子線程用線程池處理
反射調用方法將事件傳過去
public class EventBus {
//存儲訂閱類及方法參數
private Map<Object,List<Subscriber>> subscribeMethod;
//線程調度
private Handler mHandler;
//線程池
private ExecutorService executorService;
private EventBus(){
subscribeMethod = new HashMap<>();
mHandler = new Handler(Looper.getMainLooper());
executorService = Executors.newCachedThreadPool();
}
public void postEvent(Object eventParam){
Set<Object> set = subscribeMethod.keySet();
Iterator<Object> iterable =set.iterator();
while (iterable.hasNext()){
Object obj = iterable.next();
List<Subscriber> busMethodList = subscribeMethod.get(obj);
for (Subscriber busMethod : busMethodList){
if(busMethod.getParamsType() == eventParam.getClass()){
invoke(obj,busMethod,eventParam);
}
}
}
}
private void invoke(final Object obj, final Subscriber busMethod, final Object eventParam){
switch (busMethod.getThreadModel()){
case MAIN:
//經過handler調度到主線程
mHandler.post(new EventRunable(busMethod, obj, eventParam));
break;
default:
//交由線程池處理
executorService.execute(new EventRunable(busMethod, obj, eventParam));
break;
}
}
}
複製代碼
事件參數與接收參數類型一致便可,方法名隨意
EventBus.getInstance().postEvent(new LoginEvent("登陸成功"));
複製代碼
不少看似高大上的框架其實也沒咱們想的那麼難,寫着寫着就順手了,知而不行爲不知,快動起手來吧!