Android 框架煉成 教你如何寫組件間通信框架EventBus

jopen 8年前發布 | 7K 次閱讀 Android開發 移動開發

轉載請標明出處: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

----------------------------------------------------------------------------------------------------------

博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0側滑

3、Android智能機器人“小慕”的實現




來自: http://blog.csdn.net//lmj623565791/article/details/41096639

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!