Android 框架炼成 教你如何写组件间通信框架EventBus

Wesley13
• 阅读 478

转载请标明出处: 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的列表;

效果:

Android 框架炼成 教你如何写组件间通信框架EventBus

效果图和之前的一摸一样~~~

但是请注意,现在我们用的是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,这个里面存储了我们需要运行方法的所有参数,毕竟我们运行时,需要该方法,该方法所在的对象,以及在什么线程运行;三个对象足以,当然也缺一不可了~~

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中的泛型是不一致的,所以也可能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智能机器人“小慕”的实现

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Android蓝牙连接汽车OBD设备
//设备连接public class BluetoothConnect implements Runnable {    private static final UUID CONNECT_UUID  UUID.fromString("0000110100001000800000805F9B34FB");
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这