Android 框架煉成 教你如何寫組件間通信框架EventBus
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/41096639 ,本文出自:【張鴻洋的博客】
1、概述
關于Eventbus的介紹,前面已經有兩篇:Android EventBus實戰 沒聽過你就out了和Android EventBus源碼解析 帶你深入理解EventBus , 如果你覺得還有問題,沒關系,接下來我帶大家手把手打造從無到有的編寫這樣的框架~~~
首先我們回顧一下,這玩意就是在register時,掃描類中復合命名規范的方法,存到一個map,然后post的時候,查找到匹配的方法,反射調用;好,那么根據這一句話,我們就開始編寫框架之旅~~~
2、依然是原來的配方
以下出現的實例代碼和Android EventBus實戰 沒聽過你就out了基本一致,所以我就貼出部分
1、ItemListFragment
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
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
我們這里為了方便,直接簡單粗暴的使用惡漢模式創建單例:
private static EventBus eventBus = new EventBus();
public static EventBus getInstatnce()
{
return eventBus;
}
private EventBus()
{
mHandler = new Handler(Looper.getMainLooper());
} 然后在構造方法中初始化了一個mHandler,沒錯,它就是用來在處理在UI線程調用方法的。
接下來看register
2、register
/*
* 我們的強大的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
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;
} 我們這里學習了源碼,也搞了個當前線程中的變量,存儲了一個事件隊列以及事件的狀態;
class PostingThread
{
List<Object> mEventQueue = new ArrayList<Object>();
boolean isMainThread;
boolean isPosting;
}
最終發布的事件先加入到事件隊列,然后再取出來調用postEvent
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很簡單,就是反射調用方法了~
private void invokeMethod(Object eventType, SubscribeMethod subscribeMethod)
{
try
{
subscribeMethod.method
.invoke(subscribeMethod.subscriber, eventType);
} catch (Exception e)
{
e.printStackTrace();
}
} 4、unregister
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的時候,創建一個事件類,把你的每一個參數(或者可能發生沖突的參數),封裝成一個類:
例如:
public class Event
{
public static class UserListEvent
{
public List<User> users ;
}
public static class ItemListEvent
{
public List<Item> items;
}
} 這樣的話,就不會發生什么調用沖突了~~
我建了一個QQ群,方便大家交流。群號:55032675
----------------------------------------------------------------------------------------------------------
博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):
來自: http://blog.csdn.net//lmj623565791/article/details/41096639