使用线程安全的EventBus的最佳实践

4
我的应用程序具有用户交互活动和后台服务,数据模型只在后台服务中进行修改。后台服务监听来自用户的操作以及来自网络的传入消息,因此可能会出现并发问题,我尝试使用处理程序来防止这种情况发生。我使用Greenrobots Eventbus作为事件层。
这一切都运行良好,但我想知道是否有更智能/更快/代码更简单(因此错误更少)的方法来处理这种用例?
具体而言:
  • 是否有一种方法可以确保onEvent方法的串行执行而不使用处理程序?
  • 是否有替代方法,而不是为每个可能的事件设置onEvent方法?
  • 对于我在这里做的事情,是否有更好的模式?
这是我的方法:
在oncreate方法中,我注册服务(对于活动,我在onstart中执行此操作)。
@Override
public void onCreate() {
    super.onCreate();
    ...
    EventBus.getDefault().register(this);
}

在onDestroy中,我再次执行取消注册:


@Override
public void onDestroy() {
    super.onDestroy();
    ....
    EventBus.getDefault().unregister(this);
}

每当我对一个传入事件做出反应时,我希望确保串行执行,因为可能会存在并发问题,因为有来自用户交互以及通过网络来自其他用户的传入事件。因此,我决定使用处理程序:
private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Object receivedEvent = msg.obj;
            if(receivedEvent instanceof EditUser)
            {
                processEditUserBroadcast((EditUser)receivedEvent);
            }           
            else if(receivedEvent instanceof JoinParty)
            {
                processJoinPartyBroadcast((JoinParty)receivedEvent);
            }
            else if(receivedEvent instanceof LeaveParty)
            {
                processLeavePartyBroadcast();
            }
            else if(receivedEvent instanceof SendMessage)
            {
                processSendMessageBroadcast((SendMessage)receivedEvent);
            }
            else if(receivedEvent instanceof ReceivedMessage)
            {
                processReceivedMessageBroadcast((ReceivedMessage)receivedEvent);
            }       
            else if(receivedEvent instanceof Reset)
            {
                processResetBroadcast();
            }
            else if(receivedEvent instanceof ImageDownloadFinished)
            {
                processImageDownloadFinishedBroadcast((ImageDownloadFinished)receivedEvent);
            }
        }
    };  
    return handler;
}

对于每个感兴趣的事件,我都有一个onEvent方法,它没有做什么,只是将事件传递给处理程序,以通过一个小的“passToHandler”辅助函数实现串行执行。
public void passToHandler(Handler handler, Object object)
{
    Message message = handler.obtainMessage();
    message.obj = object;
    handler.sendMessage(message);
}

public void onEvent(EditUser editUser)
{
    passToHandler(handler,editUser);
}

public void onEvent(JoinParty joinParty)
{
    passToHandler(handler,joinParty);
}

public void onEvent(LeaveParty leaveParty)
{
    passToHandler(handler,leaveParty);
}

public void onEvent(SendMessage sendMessage)
{
    passToHandler(handler,sendMessage);
}

public void onEvent(ReceivedMessage receivedMessage)
{
    passToHandler(handler,receivedMessage);
}

public void onEvent(Reset reset)
{
    passToHandler(handler,reset);
}

public void onEvent(ImageDownloadFinished imageDownloadFinished)
{
    passToHandler(handler,imageDownloadFinished);
}

“process…”方法是“数据魔力”发生的地方,对于我的问题并不相关。当然,对于每个可能的事件,我都创建了一个类,通常很简洁,就像这样:
public class JoinParty {
    private String partyCode;

    public JoinParty(String partyCode) {
        super();
        this.partyCode = partyCode;
    }
    public String getPartyCode() {
        return partyCode;
    }   
}
1个回答

3
感谢您发布这篇文章,Matthias!我认为您提出了一个非常重要的问题,即GreenRobot EventBus的线程安全性,这可能会被使用者忽略。
我认为您可能是正确的,虽然我对GreenRobot EventBus和Android(但不是Java)还很陌生。如果我正确地阅读了GreenRobot EventBus的源代码,那么采用这种方法的另一个可能好处是将SendMessage事件发布到您的onEvent()方法后立即返回(在调用Handler上的sendMessage之后),允许EventBus继续将其发布给任何其他订阅者,而不会延迟实际处理类的时间。尽管如此,这可能并不是您所期望的。
对于您提供的方法,您需要确保没有其他公共方法可以访问包含所有onEvent()方法和processEditUserBroadcast()等方法的类。否则,虽然您已确保从EventBus接收到的所有事件的处理都在单个线程(以连续方式)中处理,但其他类可能会在不同的线程上调用此类的公共方法,从而再次导致线程安全问题。
如果您知道确实需要支持此类的其他公共方法,则采用您所做的方法至少可以使所有onEvent()方法处理进入单个线程(即根据Looper类文档所述创建Looper的线程的Looper),并且这样至少简化了一些事情。如果您将在此类上拥有其他公共方法,则可能还需要对公共方法和所有其他方法(例如processEditUserBroadcast())应用一些同步,以保证从多个线程安全地访问类的数据成员。或者,根据这些数据成员是什么以及您的需求是什么,您可能只需要使其中一些成员成为volatile、atomic或使用并发集合等。这完全取决于读取和写入访问需求以及这些访问的所需粒度。
这有帮助吗?对于那些熟悉Android、Loopers、Handlers、GreenRobot EventBus等的人,我有说错话的地方吗?

此外,我很高兴你发布了Matthias,因为我正在寻找一种处理EventBus的设计模式,而你的帖子引导我了解了Handler和Looper,这是一个非常好的正确方向。我曾考虑过创建自己的事件处理队列,从各种onEvent方法中添加事件。如果Handler类有一些良好设计的合并事件的模式,那就太好了,但这可能是许多EventBus用户的起点。 - Mark
嗨马克,非常感谢。很高兴听到我的想法并不完全错误。所有的“processXXX()”方法只会在这个类中使用,因为它们严格与传入的事件相关联,并且只能通过发布触发此类方法的事件间接地被其他类访问。 - Matthias Kramer
而且,除了构造函数之外,您没有其他公共方法,对吗? - Mark
正确,至少没有表现出需要线程安全的方式。 - Matthias Kramer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接