鸿蒙线程开发(二)之EventHandler机制

2,414 阅读9分钟

在开发过程中,我们经常需要在当前线程中处理下载任务等较为耗时的操作,但是又不希望当前的线程受到阻塞。此时,就可以使用EventHandler机制EventHandler是HarmonyOS用于处理线程间通信的一种机制,可以通过EventRunner创建新线程,将耗时的操作放到新线程上执行。这样既不阻塞原来的线程,任务又可以得到合理的处理。比如:主线程使用EventHandler创建子线程,子线程做耗时的下载图片操作,下载完成后,子线程通过EventHandler通知主线程,主线程再更新UI。

基础部分

基本概念

EventRunner是一种事件循环器,循环处理从该EventRunner创建的新线程的事件队列中获取InnerEvent事件或者Runnable任务。InnerEvent是EventHandler投递的事件。

EventHandler是一种用户在当前线程上投递InnerEvent事件或者Runnable任务到异步线程上处理的机制。每一个EventHandler和指定的EventRunner所创建的新线程绑定,并且该新线程内部有一个事件队列。EventHandler可以投递指定的InnerEvent事件或Runnable任务到这个事件队列。EventRunner从事件队列里循环地取出事件,如果取出的事件是InnerEvent事件,将在EventRunner所在线程执行processEvent回调;如果取出的事件是Runnable任务,将在EventRunner所在线程执行Runnable的run回调。一般,EventHandler有两个主要作用:

  1. 在不同线程间分发和处理InnerEvent事件或Runnable任务
  2. 延迟处理InnerEvent事件或Runnable任务

运作机制

EventHandler的运作机制如下图所示:

EventHandler机制.png 使用EventHandler实现线程间通信的主要流程:

  1. EventHandler投递具体的InnerEvent事件或者Runnable任务到EventRunner所创建的线程的事件队列。
  2. EventRunner循环从事件队列中获取InnerEvent事件或者Runnable任务。
  3. 处理事件或任务:
  • 如果EventRunner取出的事件为InnerEvent事件,则触发EventHandler的回调方法并触发EventHandler的处理方法,在新线程上处理该事件。
  • 如果EventRunner取出的事件为Runnable任务,则EventRunner直接在新线程上处理Runnable任务。

约束限制

  • 在进行线程间通信的时候,EventHandler只能和EventRunner所创建的线程进行绑定,EventRunner创建时需要判断是否创建成功,只有确保获取的EventRunner实例非空时,才可以使用EventHandler绑定EventRunner。
  • 一个EventHandler只能同时与一个EventRunner绑定,一个EventRunner可以同时绑定多个EventHandler

开发部分

1、EventHandler开发场景及EventRunner工作模式

1.1、EventHandler开发场景

  • EventHandler投递事件分为两种情况,分别是:使用EventRunner投递事件到新的线程和使用Runnable投递任务到新的线程。两种情况都可以按照优先级和延时处理。投递时,EventHandler的优先级可在IMMEDIATE、HIGH、LOW、IDLE中选择,并设置合适的delayTime。
  • 如果需要,我们可以在新创建的线程里投递事件到原线程进行处理。

1.2、EventRunner工作模式

EventRunner的工作模式可以分为托管模式手动模式。两种模式是在调用EventRunner的create()方法时,通过选择不同的参数来实现的,默认为托管模式

  • 托管模式:不需要我们调用run()和stop()方法去启动和停止EventRunner。当EventRunner实例化时,系统调用run()来启动EventRunner;当EventRunner不被引用时,系统调用stop()来停止EventRunner。
  • 手动模式:需要我们自行调用EventRunner的run()方法和stop()方法来确保线程的启动和停止。

2、部分接口说明

2.1、EventHandler

2.1.1、Priority优先级介绍

EventRunner将根据优先级的高低从事件队列中获取事件或者Runnable任务进行处理。Priority是EventHandler中的静态枚举类,分别有四种值(默认优先级为LOW),如下表格:

属性描述
Priority.IMMEDIATE表示事件被立即投递
Priority.HIGH表示事件先于LOW优先级投递
Priority.LOW表示事件优于IDLE优先级投递,事件的默认优先级是LOW
Priority.IDLE表示在没有其他事件的情况下,才投递该事件

2.1.2、EventHandler部分接口介绍

接口名描述
EventHandler(EventRunner runner)利用已有的EventRunner来创建EventHandler
current()在processEvent回调中,获取当前的EventHandler
processEvent(InnerEvent event)回调处理事件,在里边处理我们需要的逻辑
sendEvent(InnerEvent event, long delayTime, EventHandler.Priority priority)发送一个指定优先级的延时事件到事件队列
sendSyncEvent(InnerEvent event, EventHandler.Priority priority)发送一个指定优先级的同步事件到事件队列,延时为0ms,优先级不可以是IDLE
postSyncTask(Runnable task, EventHandler.Priority priority)发送一个指定优先级的Runnable同步任务到事件队列,延时为0ms
postTask(Runnable task, long delayTime, EventHandler.Priority priority)发送一个指定优先级的Runnable延时任务到事件队列
sendTimingEvent(InnerEvent event, long taskTime, EventHandler.Priority priority)发送一个带优先级的定时事件到队列,在taskTime时间执行,如果taskTime小于当前时间,立即执行
postTimingTask(Runnable task, long taskTime, EventHandler.Priority priority)发送一个带优先级的定时Runnable任务到队列,在taskTime时间执行,如果taskTime小于当前时间,立即执行
removeEvent(int eventId, long param, Object object)删除指定id、param和object的事件
removeAllEvent()删除该EventHandler的所有事件
getEventName(InnerEvent event)获取事件的名字
getEventRunner()获取该EventHandler绑定的EventRunner
isIdle()判断队列是否为空
hasInnerEvent(Runnable runnable)根据指定的runnable参数,检查是否有还未被处理的任务。可以根据不同的入参进行检查

2.2、EventRunner主要接口介绍

接口名描述
create()创建一个拥有新线程的EventRunner
create(boolean inNewThread)创建一个拥有新线程的EventRunner,inNewThread为true时,EventRunner为托管模式,系统将自动管理该EventRunner;inNewThread为false时,EventRunner为手动模式
create(String newThreadName)创建一个拥有新线程的EventRunner, 新线程的名字是 newThreadName
current()获取当前线程的EventRunner
run()EventRunner为手动模式时,调用该方法启动新的线程
stop()EventRunner为手动模式时,调用该方法停止新的线程

2.3、InnerEvent

2.3.1、InnerEvent的属性介绍

属性描述
eventId事件的ID, 由开发者定义用来辨别事件
object事件携带的Object信息
param事件携带的long型数据

2.3.2、InnerEvent的主要接口介绍

接口名描述
drop()释放一个事件实例
get()获得一个事件实例
get(int eventId, long param, Object object)获得一个指定的eventId,param和object的事件实例
PacMap getPacMap()获取PacMap,如果没有,会新建一个
Runnable getTask()获取Runnable任务
PacMap peekPacMap()获取PacMap
void setPacMap(PacMap pacMap)设置PacMap

3、实战

在实战中,我将使用手动模式来编写案例。托管模式会在代码创建EventRunner实例后,就调用run()进行开启线程,我们只需要在不使用或退出的时候将EventRunner实例赋值为null,系统就会调用stop()来停止EventRunner。

3.1、EventHandler投递InnerEvent事件

案例:EventHandler投递InnerEvent事件,并按照优先级和延时进行处理,开发步骤如下:

1)创建EventHandler的子类,在子类中重写实现方法processEvent()来处理事件。

private static final int EVENT_MESSAGE_NORMAL = 1;
private static final int EVENT_MESSAGE_DELAY = 2;

private class MyEventHandler extends EventHandler {
    private MyEventHandler(EventRunner runner) {
        super(runner);
    }
    // 重写实现processEvent方法
    @Override
    public void processEvent(InnerEvent event) {
        super.processEvent(event);
        if (event == null) {
            return;
        }
        int eventId = event.eventId;
        switch (eventId) {
            case EVENT_MESSAGE_NORMAL:
                // 待执行的操作,由开发者定义
                break;
            case EVENT_MESSAGE_DELAY:
                // 待执行的操作,由开发者定义
                break;
            default:
                break;
        }
    }
}

2)创建EventRunner。

// create()的参数是true时,则为托管模式
EventRunner runner = EventRunner.create(false);

3)创建EventHandler子类的实例。

MyEventHandler myHandler = new MyEventHandler(runner);

4)获取InnerEvent事件。

// 获取事件实例,其属性eventId, param, object由开发者确定,代码中只是示例。
long param = 0L; 
Object object = null; 
InnerEvent normalInnerEvent = InnerEvent.get(EVENT_MESSAGE_NORMAL, param, object);
InnerEvent delayInnerEvent = InnerEvent.get(EVENT_MESSAGE_DELAY, param, object);

5)投递事件,投递的优先级以IMMEDIATE为例,延时选择0ms和2ms。

// 优先级IMMEDIATE,投递之后立即处理,延时为0ms,该语句等价于同步投递sendSyncEvent(event1,EventHandler.Priority.IMMEDIATE);
myHandler.sendEvent(normalInnerEvent, 0, EventHandler.Priority.IMMEDIATE);
myHandler.sendEvent(delayInnerEvent, 2, EventHandler.Priority.IMMEDIATE); // 延时2ms后立即处理

6)启动和停止EventRunner,如果为托管模式,则不需要此步骤。

runner.run();
// 待执行操作
runner.stop();// 开发者根据业务需要在适当时机停止EventRunner

3.2、EventHandler投递Runnable任务

案例:EventHandler投递Runnable任务,并按照优先级和延时进行处理,开发步骤如下:

1)创建EventHandler的子类,创建EventRunner,并创建EventHandler子类的实例,步骤与EventHandler投递InnerEvent场景的步骤1-3相同。 2)创建Runnable任务。

Runnable normalTask = new Runnable() {
    @Override
    public void run() {
        // 待执行的操作,由开发者定义
    }
};
Runnable delayTask = new Runnable() {
    @Override
    public void run() {
        // 待执行的操作,由开发者定义
    }
};

3)投递Runnable任务,投递的优先级以IMMEDIATE为例,延时选择0ms和2ms。

// 优先级为immediate,延时0ms,该语句等价于同步投递myHandler.postSyncTask(task1,EventHandler.Priority.immediate);
myHandler.postTask(normalTask, 0, EventHandler.Priority.IMMEDIATE);

myHandler.postTask(delayTask, 2, EventHandler.Priority.IMMEDIATE);// 延时2ms后立即执行

4)启动和停止EventRunner,如果是托管模式,则不需要此步骤。

runner.run();
// 待执行操作

runner.stop();// 停止EventRunner

3.3、在新创建的线程里投递事件到原线程(*)

案例:EventHandler从新创建的线程投递事件到原线程并进行处理,开发步骤如下:

1)创建EventHandler的子类,在子类中重写实现方法processEvent()来处理事件。

private static final int EVENT_MESSAGE_CROSS_THREAD = 1;

private class MyEventHandler extends EventHandler {
    private MyEventHandler(EventRunner runner) {
        super(runner);
    }
    // 重写实现processEvent方法
    @Override
    public void processEvent(InnerEvent event) {
        super.processEvent(event);
        if (event == null) {
            return;
        }
        int eventId = event.eventId;
        switch (eventId) {
            case EVENT_MESSAGE_CROSS_THREAD:
                Object object = event.object;
                if (object instanceof EventRunner) {
                    // 将原先线程的EventRunner实例投递给新创建的线程
                    EventRunner runner2 = (EventRunner) object;
                    // 将原先线程的EventRunner实例与新创建的线程的EventHandler绑定
                    EventHandler myHandler2 = new EventHandler(runner2) {
                        @Override
                        public void processEvent(InnerEvent event) {
                            // 需要在原先线程执行的操作
                        }
                    };
                    int eventId2 = 1; 
                    long param2 = 0L; 
                    Object object2 = null; 
                    InnerEvent event2 = InnerEvent.get(eventId2, param2, object2);
                    myHandler2.sendEvent(event2); // 投递事件到原先的线程
                }
                break;
            default:
                break;
        }
    }
}

2)创建EventRunner,以手动模式为例。

EventRunner runner = EventRunner.create(false);// create()的参数是true时,则为托管模式。

3)创建EventHandler子类的实例。

MyEventHandler myHandler = new MyEventHandler(runner);

4)投递事件,在新线程上直接处理。

// 将与当前线程绑定的EventRunner投递到与runner创建的新线程中
myHandler.sendEvent(event);

5)启动和停止EventRunner,如果是托管模式,则不需要此步骤。

runner.run();
// 待执行操作

runner.stop();// 停止EventRunner

小结

在开发部分,我们先了解了EventHandler开发场景、EventRunner工作模式,学习了EventHandler、EventRunner、InnerEvent相关接口以及EventHandler投递事件或任务优先级的设置。最后我们进行了实战,实战的内容也比较简单。总结来说就是,首先先定义一个EventHandler实例并重写processEvent()方法,再创建EventRunner实例对象,利用创建好的EventRunner实例对象来创建EventHandler对象,我们使用InnerEvent.get()来创建/获取一个事件实例,new Runnable()来创建一个任务,然后调用EventHandler相应的方法来进行事件或任务的投递,最后我们在processEvent()方法中处理我们的逻辑。

思考总结

  • 为什么要使用EventHandler机制?
  • 理解EventRunner、InnerEvent、EventHandler。
  • EventHandler的两个主要作用是什么?
  • EventHandler的开发场景以及EventRunner的工作模式?
  • 了解EventHandler、EventRunner以及InnerEvent的部分主要接口。
  • 如何实现在新创建的线程里投递事件到原线程?