一、前言:
EventBus是一款事件發(fā)布與訂閱的框架兼贡,主要用來(lái)替代Intent攻冷、Handler、BroadCast來(lái)實(shí)現(xiàn)組件通信遍希,線(xiàn)程間通信等曼。優(yōu)點(diǎn)是:開(kāi)銷(xiāo)小,代碼優(yōu)雅,以及將發(fā)送者與接收者解耦禁谦,使用非常簡(jiǎn)便胁黑。分析EventBus的博客很多,但是很少有帶大家去實(shí)現(xiàn)一款EventBus框架的州泊。沒(méi)錯(cuò)丧蘸,這里我將帶你實(shí)現(xiàn)一款自己的EventBus框架。不管你之前是否接觸過(guò)EventBus遥皂,都可以通過(guò)學(xué)習(xí)而本篇文章力喷,來(lái)學(xué)習(xí)EventBus。
二演训、準(zhǔn)備
在看這篇博客之前需要對(duì)反射冗懦、注解有一定的認(rèn)識(shí),否則你是不容易看懂的仇祭。
三披蕉、流程
整個(gè)EventBus發(fā)布訂閱的流程可以形象為以下過(guò)程,包括后面的部分都是以這個(gè)流程來(lái)分析的
1乌奇、訂閱過(guò)程:
假設(shè)某公司要招聘Android没讲、IOS、前端礁苗、Java...程序員爬凑,它會(huì)先在招聘中介網(wǎng)站上(比如“前程無(wú)憂(yōu)”)提交這些信息。而“前程無(wú)憂(yōu)”需要記下這些信息试伙,它會(huì)將該公司招聘的所有崗位存入一個(gè)List<招聘崗位>集合中嘁信,因?yàn)檎腥说墓究隙ú恢惯@一家,所以然后"前程無(wú)憂(yōu)"會(huì)以公司為鍵疏叨,招聘崗位List為值存入一個(gè)Map<公司,List<招聘崗位>>中潘靖。這就是一個(gè)訂閱過(guò)程、招聘的公司就是訂閱者蚤蔓、“前程無(wú)憂(yōu)”就是一個(gè)中介卦溢。
2、發(fā)布過(guò)程:
求職者為了找工作秀又,也會(huì)去“前程無(wú)憂(yōu)”上面發(fā)布找工作的消息单寂,假設(shè)我們找的是Android的工作,“前程無(wú)憂(yōu)”就會(huì)去它的Map<公司名,List<招聘崗位>>中找吐辙,看有哪個(gè)公司是招聘Android的宣决,找到之后就會(huì)把Android求職推薦給該公司。
3昏苏、注銷(xiāo)過(guò)程:
假設(shè)公司不需要再招人尊沸,那么怎么辦呢?那就注銷(xiāo)唄威沫,此時(shí)“前程無(wú)憂(yōu)”就會(huì)在它的Map<公司名,List<招聘崗位>>中,將該公司移除掉站刑。
上面的過(guò)程大概可以用下面這張圖來(lái)表示:
理清思路之后我們就可以來(lái)擼碼了瓤湘。
四、正式擼碼
1默责、先看下我們所寫(xiě)代碼的整體結(jié)構(gòu):
![
沒(méi)錯(cuò)壶熏,就這四個(gè)類(lèi)就是實(shí)現(xiàn)了一款EventBus,麻雀雖小句柠,但也是五臟俱全。EventBus類(lèi)就是“前程無(wú)憂(yōu)”棒假,SubscribleMethod類(lèi)就是招聘崗位溯职,ThreadMode和Subscrible是啥先別管它。
2.EventBus類(lèi)的基本實(shí)現(xiàn)(前程無(wú)憂(yōu))
求職者要找工作帽哑,公司要招人谜酒,那么肯定得先有一個(gè)招聘的招聘的中介"前程無(wú)憂(yōu)"。那么我們現(xiàn)在就來(lái)寫(xiě)一個(gè)中介EventBus類(lèi)妻枕,至于怎么寫(xiě)了?大家在使用EventBus的時(shí)候都知道使用以下這句代碼進(jìn)行注冊(cè)(訂閱):
EventBus.getDefault().regist(this);
那么我們就依葫蘆畫(huà)瓢僻族,寫(xiě)一個(gè)getDefault()
方法,代碼如下:
/**
* Created by Administrator on 2017/5/22.
* 招聘中介
*/
public class EventBus {
//招聘中介的信息表(表里面填了各大公司需要招聘的崗位)
//Map<招聘公司,該公司招聘的崗位集合>
private Map<Object,List<SubscribleMethod>> cacheMap;
private static volatile EventBus instance;
private EventBus(){
cacheMap=new HashMap<>();
}
public static EventBus getDefault(){
if(instance==null){
synchronized (EventBus.class){
if(instance==null){
instance=new EventBus();
}
}
}
return instance;
}
}
代碼行數(shù)不多屡谐,采用了一個(gè)雙重檢查鎖式的單例模式來(lái)創(chuàng)建EventBus的實(shí)例述么。cacheMap就是用來(lái)存儲(chǔ)各公司招聘信息的。SubscribleMethod這個(gè)類(lèi)就是招聘崗位信息的封裝類(lèi)愕掏,暫時(shí)不用管它度秘,咱們后面再說(shuō)。
2饵撑、注冊(cè)方法的實(shí)現(xiàn)()
有了EventBus的實(shí)例剑梳,那么我們就來(lái)實(shí)現(xiàn)regist方法,代碼如下:
/**
* 注冊(cè)
* @param obj 公司
*/
public void regist(Object obj){
List<SubscribleMethod> list = cacheMap.get(obj);
//還沒(méi)有注冊(cè)過(guò),那么就將崗位集合put進(jìn)中介的map中,
//如果已經(jīng)注冊(cè)過(guò)了滑潘,那么久不用再次注冊(cè)垢乙,那么什么都不做
if(list==null){
//找到公司obj招的所有崗位的集合
List<SubscribleMethod> methods = findSubscribleMethod(obj);
cacheMap.put(obj,methods);
}else {
throw new RuntimeException("你就已經(jīng)注冊(cè)(訂閱)過(guò)了,不需要再次注冊(cè)(訂閱)");
}
}
上面這個(gè)注冊(cè)方法,參數(shù)就是要注冊(cè)的公司對(duì)象众羡。首先會(huì)去“前程無(wú)憂(yōu)”的Map集合里面找一下侨赡,看是否這個(gè)公司已經(jīng)注冊(cè)(訂閱)過(guò)了,如果已經(jīng)注冊(cè)(訂閱)過(guò)了粱侣,那就拋一個(gè)異常告訴它不需要再注冊(cè)了,如果沒(méi)有注冊(cè)(訂閱)蓖宦,才讓他注冊(cè)(訂閱)齐婴,注冊(cè)的公司obj需要招人,那么就得知道obj這個(gè)公司到底招聘那些崗位,findSubscribleMethod(obj)這個(gè)方法就是找到公司obj的所有招聘崗位(具體實(shí)現(xiàn)咱們后面再說(shuō)稠茂,你現(xiàn)在只要知道它是干啥的就行)柠偶,找到公司obj所有崗位的集合后情妖,后面的自然是將公司招聘信息存入“前程無(wú)憂(yōu)”的招聘信息表Map里面去了, 也就是這句代碼cacheMap.put(obj,methods)。
2诱担、Subscrible是個(gè)啥?
上面的代碼結(jié)構(gòu)圖中出現(xiàn)Subscrible毡证,那它是個(gè)啥呢?我們現(xiàn)在就來(lái)說(shuō)一說(shuō)蔫仙,代碼如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscrible {
ThreadMode value() default ThreadMode.PostThread;
}
沒(méi)錯(cuò)料睛,它就這幾行代碼,灰常簡(jiǎn)單摇邦,可以看出它就是一個(gè)注解恤煞, 簡(jiǎn)單的來(lái)說(shuō),招聘的公司招人它得有個(gè)招聘的方法呀施籍,招聘的公司肯定也得干其他的事居扒,肯定也有非招聘的方法,那么怎么知道它的哪些方法是招聘的方法呢丑慎?那么就給它的招聘方法上面加個(gè)Subscrible注解我們就知道了喜喂,里面的value()先不管它,下面先給個(gè)示例:
/**
*公司招人的方法
* @param content
*/
@Subscrible(ThreadMode.PostThread)
public void receive(AndroidProgrammer content){
Log.i("zkx","Thread "+Thread.currentThread().getName());
//Toast彈不出來(lái),不知道為啥
//Toast.makeText(this,Thread.currentThread().getName(),Toast.LENGTH_SHORT);
}
@Subscrible(ThreadMode.MainThread)這句代碼就表示了這個(gè)方法是一個(gè)招聘方法竿裂,()里面的代碼表示這個(gè)方法是在主線(xiàn)程里面執(zhí)行的玉吁。該方法的參數(shù)類(lèi)型就表明了招的是什么職位,我這里是招聘AndroidProgrammer铛绰,至于ThreadMode請(qǐng)看下面诈茧。
3、ThreadMode是個(gè)啥?
ThreadMode聽(tīng)名字就知道是線(xiàn)程模式的意思捂掰,具體的請(qǐng)看代碼:
public enum ThreadMode {
PostThread,//發(fā)送線(xiàn)程執(zhí)行
MainThread,//主線(xiàn)程執(zhí)行
BackgroundThread,//后臺(tái)線(xiàn)程執(zhí)行
Async
這段其實(shí)就是直接復(fù)制EventBus源碼里面的敢会,英文注釋太長(zhǎng)了,我給去掉了这嚣∨富瑁可以看出他就是一個(gè)枚舉,請(qǐng)看我給出的注釋。再回到
注解Subscrible的代碼中姐帚,我們就可以知道它里面的value()值就是說(shuō)加上這個(gè)注解的在哪個(gè)線(xiàn)程執(zhí)行了吏垮,我的代碼中默認(rèn)指定的是PostThread。
4罐旗、SubscribleMethod是個(gè)啥?
從名字是可以看出它是訂閱方法的意思膳汪,實(shí)際是上它是封裝了招聘崗位信息(公司招聘的方法、招聘崗位的類(lèi)型九秀、招聘方法在哪個(gè)線(xiàn)程執(zhí)行)的一個(gè)JavaBean遗嗽。具體代碼如下:
**
* Created by Administrator on 2017/5/22.
* 招聘崗位信息類(lèi)
*/
public class SubscribleMethod {
//招聘這個(gè)崗位的方法
private Method method;
//招聘這個(gè)崗位的方法在哪個(gè)線(xiàn)程里面執(zhí)行
private ThreadMode threadMode;
//招聘的崗位的類(lèi)型
private Class<?> eventType;
public SubscribleMethod(Method method, ThreadMode threadMode, Class<?> eventType) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public void setThreadMode(ThreadMode threadMode) {
this.threadMode = threadMode;
}
public Class<?> getEventType() {
return eventType;
}
public void setEventType(Class<?> eventType) {
this.eventType = eventType;
}
}
5、深入細(xì)節(jié)
到這里EventBus的各個(gè)類(lèi)的意思已經(jīng)說(shuō)完了鼓蜒,我們來(lái)看一些細(xì)節(jié)的東西痹换。上面“2征字、注冊(cè)方法的實(shí)現(xiàn)”這段,我們說(shuō)到findSubscribleMethod(obj)這個(gè)方法就是找到公司obj的所有招聘崗位娇豫,那么我們就來(lái)看看它是怎么找的匙姜,代碼如下:
/**
* 尋找該公司的招聘崗位
* @param activity 該公司
* @return
*/
private List<SubscribleMethod> findSubscribleMethod(Object activity) {
List<SubscribleMethod> list = new CopyOnWriteArrayList<>();
Class<?> clazz = activity.getClass();
Method[] methods = clazz.getDeclaredMethods();
//循環(huán)查找父類(lèi) 接收方法
while(clazz!=null){//如果該公司又隸屬于某個(gè)更大的公司,那么它也要為這個(gè)更大的公司招人
String name = clazz.getName();
//系統(tǒng)方法直接break
if(name.startsWith("java.")||name.startsWith("javax.")||name.startsWith("android.")){
break;
}
//遍歷所有類(lèi)的方法(其中有Subscrible注解的為招人的崗位(方法))
//每找到一個(gè)招聘崗位就將其放到集合中冯痢,最終返回的集合list就是該公司所有招聘崗位的集合
for (Method method:methods){
//持有Subscrible注解的才是個(gè)接收方法氮昧,才是個(gè)崗位
//發(fā)生線(xiàn)程
Subscrible subscrible = method.getAnnotation(Subscrible.class);
if (subscrible==null){
continue;
}
//拿到方法的參數(shù)類(lèi)型的數(shù)組
Class<?>[] parameters = method.getParameterTypes();
if(parameters.length!=1){//只能接收一個(gè)參數(shù)
throw new RuntimeException("eventbus must be one parameter");
}
//獲得參數(shù)類(lèi)型數(shù)組的第一個(gè)元素(也只有一個(gè)元素)
Class<?> paramClass = parameters[0];
//拿到發(fā)生的線(xiàn)程(默認(rèn)發(fā)生在PostThread)
ThreadMode threadMode = subscrible.value();
//new一個(gè)招聘崗位
SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramClass);
//招聘中介(前程無(wú)憂(yōu)) 把該公司所有崗位緩存到集合
list.add(subscribleMethod);
}
clazz=clazz.getSuperclass();
}
return list;
}
從代碼總可以看出,我們其實(shí)就是通過(guò)反射拿到該招聘公司的所有方法系羞,遍歷這些方法郭计,判斷這些方法是不是招聘的方法,具體的就是看這些方法有沒(méi)有加Subscrible 注解椒振,加了注解的肯定就是招聘方法昭伸,繼而拿到,每一個(gè)招聘方法的參數(shù)類(lèi)型澎迎,也就是招聘的崗位庐杨,然后將所有的崗位存入List集合中。還有一點(diǎn)要注意的是如果公司類(lèi)是繼承自某個(gè)類(lèi)夹供,也就是說(shuō)該公司它是某個(gè)更大的公司的一個(gè)子公司灵份,那么它在招人的同時(shí)也得給它的父公司招人。所以有了我們的最外層的while循環(huán)哮洽,不斷遍歷它的父類(lèi)填渠,直到系統(tǒng)類(lèi)為止。
6鸟辅、發(fā)布的方法
到這里氛什,中介有了,公司也注冊(cè)(訂閱)了匪凉,那么就到我們求職者出場(chǎng)了枪眉,假設(shè)我們要找Android程序員的工作,那么就只需要調(diào)用下面這句代碼就行:
EventBus.getDefault().post(new AndroidProgrammer("zkx","123456"));
其實(shí)就是調(diào)用了EventBus的post方法再层,具體實(shí)現(xiàn)如下:
/**
* 安卓程序員 來(lái)找工作
* @param object 求職的崗位類(lèi)型
*/
public void post(final Object object) {
//拿到集合cacheMap里所有的公司
Set<Object> set = cacheMap.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
//拿到具體的每一個(gè)公司
final Object activity = iterator.next();
//拿出該公司招聘的崗位集合
List<SubscribleMethod> list = cacheMap.get(activity);
//遍歷該公司招聘崗位的集合贸铜,看有沒(méi)有招聘安卓的
for (final SubscribleMethod method:list) {
//如果是屬于同樣的類(lèi)型(如果有崗位匹配)
if(method.getEventType().isAssignableFrom(object.getClass())){
//判斷當(dāng)前接收方法是在哪個(gè)線(xiàn)程
switch (method.getThreadMode()){
//發(fā)送方法的線(xiàn)程和接收的線(xiàn)程在同一個(gè)線(xiàn)程,直接invoke就完事了
case PostThread:
//activity:公司,method:崗位,object:求職者
//崗位匹配,把這個(gè)人招進(jìn)來(lái)
invoke(activity,method,object);
break;
case MainThread:
//判斷發(fā)送線(xiàn)程是在哪個(gè)線(xiàn)程,
//如果發(fā)送線(xiàn)程就是主線(xiàn)程就不用切換線(xiàn)程
if(Looper.myLooper()==Looper.getMainLooper()){
//直接調(diào)用
invoke(activity,method,object);
}else {//發(fā)送是在子線(xiàn)程
handler.post(new Runnable() {
@Override
public void run() {
invoke(activity,method,object);
}
});
}
break;
//指定接收方法發(fā)生在子線(xiàn)程
case BackgroundThread:
//如果當(dāng)前發(fā)送線(xiàn)程就是子線(xiàn)程就不用切換線(xiàn)程
if(Looper.myLooper()!=Looper.getMainLooper()){
//發(fā)生在子線(xiàn)程
invoke(activity,method,object);
}else {
executorService.execute(new Runnable() {
@Override
public void run() {
invoke(activity,method,object);
}
});
}
break;
}
}
}
}
}
代碼有些長(zhǎng)聂受,但是基本上每行代碼都給出了注釋蒿秦。理解起來(lái)應(yīng)該不難。大體的流程就是在”前程無(wú)憂(yōu)”的信息表里面拿到所有的公司蛋济,然后遍歷這些公司渤早,看這些公司的招聘崗位里面有木有Android的崗位(是否匹配),如果有的話(huà)瘫俊,那就利用反射去調(diào)用有招聘Android崗位的公司的招聘方法來(lái)招聘這個(gè)求職者鹊杖。還有需要注意的就是,我們會(huì)根據(jù)發(fā)布方法(求職者發(fā)布求職的方法)和接收方法(公司招人的方法)所在線(xiàn)程的不同扛芽,采取不同的策略骂蓖。具體請(qǐng)看代碼,有詳細(xì)的注釋川尖。反射調(diào)用公司的招聘方法代碼如下:
/**
* @param activity 公司
* @param method 崗位信息封裝類(lèi)
* @param object 求職者
*/
private void invoke(Object activity, SubscribleMethod method, Object object) {
try {
//調(diào)用公司activity的崗位method的招人方法method.getMethod()方法,
//傳參object,代表招Object類(lèi)型崗位的人
method.getMethod().invoke(activity,object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
7注銷(xiāo)方法的實(shí)現(xiàn)(取消訂閱)
公司不招人了登下,那么就得注銷(xiāo)(取消訂閱),代碼很簡(jiǎn)單叮喳,就是將該公司的信息從EventBus的map中移除就行了
public void unregist(Object object){
//在中介的map中將該公司object移除
cacheMap.remove(object);
}
五被芳、最后
到這里我們所有的代碼就寫(xiě)完了,現(xiàn)在就可以使用我們寫(xiě)的這款框架來(lái)進(jìn)行組件間、線(xiàn)程間通信了馍悟,寫(xiě)的不好的或者錯(cuò)誤的畔濒,歡迎批評(píng)指正。
全部代碼下載地址:
https://github.com/zkxok/MyEventBus