浅谈鸿蒙消息机制

概述

概述
EventHandlerEventHandler是HarmonyOS用于处理线程间通信的一种机制,一种用户在当前线程上投递InnerEvent事件或者Runnable任务到异步线程上处理的机制,可以通过EventRunner创建新线程,将耗时的操作放到新线程上执行。
HandlerHandler是Android中通过线程中的消息队列发送和处理Message或Runnable对象的消息机制。

初看EventHandler,很容易联想到Eventbus和Handler,这里InnerEvent其实相当于Android中Handler的Message,所以两者的概述差别不大。
关于两者的定义,相关官方文档中也没有说得很清晰,实际中也不容易说清楚,因为两者的作用太过重要,而定义的存在感没那么高,开发者理解不同,对其定义也就不同,但消息机制的作用则不容忽视:

作用
EventHandler1. 在不同线程间分发和处理InnerEvent事件或Runnable任务。2.延迟处理InnerEvent事件或Runnable任务。
Handler1. 执行定时任务 。2.在不同线程中执行任务

两者的作用基本相同。当有耗时任务的时候,在主线程中创建子线程,在子线程中做耗时操作,当子线程任务执行完后将结果通过消息机制反馈到主线程中,主线程更新UI。来回切换线程是因为:

  • 主线程中执行耗时操作会导致ANR(请求无响应)。
  • 主线程属于不安全线程,如果不加以控制,多任务同时执行时安全问题将会暴露。

运行机制

EventHandler
EventHandler和Handler运行机制如上图所示:

  • EventHandler实现线程间通信的主要流程:
    首先EventHandler投递具体的InnerEvent事件或者Runnable任务到EventRunner所创建的线程的事件队列。然后EventRunner循环从事件队列中获取InnerEvent事件或者Runnable任务。最后处理事件或任务:如果EventRunner取出的事件为InnerEvent事件,则触发EventHandler的回调方法并触发EventHandler的处理方法,在新线程上处理该事件;如果EventRunner取出的事件为Runnable任务,则EventRunner直接在新线程上处理Runnable任务。
    注意:在进行线程间通信的时候,EventHandler只能和EventRunner所创建的线程进行绑定,EventRunner创建时需要判断是否创建成功,只有确保获取的EventRunner实例非空时,才可以使用EventHandler绑定EventRunner。一个EventHandler只能同时与一个EventRunner绑定,一个EventRunner上可以创建多个EventHandler。
  • Handler消息机制:
    Handler创建完毕后,通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过send方法发送一条消息,这条消息同样会在Looper中处理,其实post方法最终也是通过send方法完成的。当send方法被调用的时候,MessageQueue中的enqueueMessage方法也会被调用。enqueueMessage方法就是将接收到的消息放到消息队列中,然后Looper发现有新的消息到来,就会处理这个消息。最终消息中的Runnable或handleMessage方法就会被调用。
    注意:Looper运行在Handler创建所在的线程中,所以Handler中的业务逻辑是在创建Handler的线程中执行的。Looper是是以无限循环的形式去查询是否有消息的,如果有消息就处理,如果没有就一直等到。MessageQueue称之为消息队列,实际上是消息单链表,方便插入和删除消息。

使用

如果开发者需要切换线程,在不同线程中通信,通常会使用EventHandler或Handler,具体使用如下:

EventHandler

  1. 首先创建EventHandler的子类,在子类中重写实现方法processEvent()来处理事件:
public class HMEventHandler extends EventHandler {
    public HMEventHandler(EventRunner runner) throws IllegalArgumentException {
        super(runner);
    }

    @Override
    protected void processEvent(InnerEvent event) {
        super.processEvent(event);
        int id = event.eventId;
        long param = event.param;
        ...
    }
}

创建完子类后,在processEvent方法中通常根据event的id消息的来源,根据消息的来源或类型的不同做出相对应的处理。关于EventHandler的创建方式,源代码中关于构造只给出了一个方法:

public class EventHandler {
    public EventHandler(EventRunner runner) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
 }

因此要使用EventHandler只有这一种方法,估计这样设计是为了便于消息和线程管理。new EventHandler的时候需要EventRunner对象,所以需要提前创建EventRunner对象:

EventRunner runner = EventRunner.create(false);

EventRunner.create方法中还需要传Boolean类型的值,因为EventRunner的工作模式可以分为托管模式和手动模式。false为手动模式:需要开发者自行调用EventRunner的run()方法和stop()方法来确保线程的启动和停止。true为托管模式:不需要开发者调用run()和stop()方法去启动和停止EventRunner。当EventRunner实例化时,系统调用run()来启动EventRunner;当EventRunner不被引用时,系统调用stop()来停止EventRunner,相当于自动模式。
创建完EventRunner,在使用前还是需要判空:

if (runner == null) {
    return;jianzhuang
}
...

判空是因为创建EventRunner可能失败,如创建线程失败时,创建EventRunner失败。只要是Java,在使用对象前基本都需要判空,否则容易出现空指针异常等错误,这估计是Java最大的劣势之一,也成了判断程序健壮性和编写水平高低的标准之一。最终将runner传入,一个EventHandler对象才算创建完毕:

HMEventHandler mHandler = new HMEventHandler(runner);

如果感觉步骤繁琐,可以使用内部类。内部类可能导致程序混乱、重用率低、效率低、容易泄露等问题,根据个人习惯来吧。

  1. 创建消息
  • 创建InnerEvent:
int eventId1 = 0;
long param = 0; 
Object object = null; 
InnerEvent event1 = InnerEvent.get(eventId1, param, object);

消息的创建方式有多种:

public final class InnerEvent implements Sequenceable {
   ...

    public static InnerEvent get() {
        throw new RuntimeException("Stub!");
    }

    public static InnerEvent copyFrom(InnerEvent oldInnerEvent) throws CloneNotSupportedException {
        throw new RuntimeException("Stub!");
    }

    public static InnerEvent get(int eventId, long param, Object object) {
        throw new RuntimeException("Stub!");
    }

    public static InnerEvent get(int eventId) {
        throw new RuntimeException("Stub!");
    }
    ...
  }

从上述代码中不难发现创建的消息可以是空消息,复制的消息,只要有eventId的其他的可有可无的消息。具体消息体是什么样的却决于实际需要。

  • 创建Runnable:
Runnable task1 = new Runnable() {
    @Override
    public void run() {
       ...// 任务
    }
};

具体任务开发者自定义。

  1. 发送消息
  • 发送InnerEvent:
mHandler.sendEvent(event1, 0, EventHandler.Priority.IMMEDIATE);

Priority是消息的优先级,优先级分为四种:

    public static enum Priority {
        IMMEDIATE,
        HIGH,
        LOW,
        IDLE;
        ...
    }
属性描述
Priority.IMMEDIATE立即投递
Priority.HIGH先于LOW优先级投递
Priority.LOW优于IDLE优先级投递,事件的默认优先级是LOW
Priority.IDLE在没有其他事件的情况下,才投递该事件

发送InnerEvent的形式有多种:

public class EventHandler {
    ...
    public void sendEvent(InnerEvent event, long delayTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
    public void sendEvent(int eventId) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
    public void sendEvent(int eventId, long delayTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
 }

根据上述代码可以看出消息的优先级, 是否延时,延时时长可灵活配置,实际使用视具体情况而定。

  • 发送Runnable
mHandler.postTask(task1,0, EventHandler.Priority.IMMEDIATE);

同样postTask的形式也是多样的:

...
    public void postTask(Runnable task, long delayTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public void postTask(Runnable task) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public void postTask(Runnable task, long delayTime) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }

    public void postTask(Runnable task, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
  1. 启动和停止(如果是托管模式,此步不需要)
runner.run(); // 启动EventRunner
...
runner.stop();// 停止EventRunner

除了启动和停止,EventHandler还有其他功能:

  • 发送同步消息:
   ...
    public void sendEvent(int eventId, long delayTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
        public void postSyncTask(Runnable task, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
  • 发送定时消息:
   ...
   public void sendTimingEvent(InnerEvent event, long taskTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
      public void postTimingTask(Runnable task, long taskTime, EventHandler.Priority priority) throws IllegalArgumentException {
        throw new RuntimeException("Stub!");
    }
    ...
  • 移除消息:
   public void removeAllEvent() {
        throw new RuntimeException("Stub!");
    }
    ...
        public void removeEvent(int eventId, long param, Object object) {
        throw new RuntimeException("Stub!");
    }
    ...

具体功能还有详细的划分,如果需要仔细了解,请查看EventHandler.class源码或参考HarmonyOS开发文档

Handler

  1. 创建Handler对象。
...
    @Deprecated
    public Handler() {
        this(null, false);
    }
    @Deprecated
    public Handler(@Nullable Callback callback) {
        this(callback, false);
    }

    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }
    
    public Handler(@Nullable Callback callback, boolean async) {
         if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
       ...
    }
    ...

相对于EventHandler,Handler的构造方法比较丰富,创建方法也是多种多样。一般比较简单的使用方式是使用无参的构造方法:

    private Handler mHandler = new Handler(){
    {
      @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
                if(msg.what == 1){
                ...
            }
        }
    };

消息逻辑处理在handleMessage方法中进行。如果Handler的创建是在主线程中创建的,这样写是没有问题的,但是如果Handler的创建是在子线程中创建,这样写就会抛出"Cant`t create handler inside thread that has not called Looper.prepare()"异常,因为在主线程中默认实现了 Looper.prepare(),而子线程中没有,所以在子线程中使用要加上 Looper.prepare():

 Looper.prepare();
 Handler mHandler = new Handler();
 Looper.loop();

只有这样才算是开启了消息循环机制,才能查看消息,处理消息,完成消息通讯。当然Looper中还有其他比较有用的方法,比如:

 Looper.getMainLooper();
 Looper.myLooper();

通过 Looper.getMainLooper()可以在任何地方获取主线程的Looper。通过Looper.myLooper()则获取当前线程的Looper。建议在不需要的时候终止Looper,有两种方案供选择:

  Looper.getMainLooper().quit();
  Looper.getMainLooper().quitSafely();

使用quit()是直接退出Looper,而quitSafely()是等当前的消息队列中的消息都处理完毕后才会安全退出。在子线程中如果创建了Looper,那么在所有消息都处理完后应该调用quit()终止消息循环,否则这个子线程会一直处于等待状态会造成消息阻塞,因为之前开启消息循环调用loop()是一个死循环,结束死循环的唯一方法就是MessageQueue的next方法(查看是否有新的消息返回)返回了null,当Looper退出后,Handler发送消息时调用send()会返回false,Handler发送消息就会失败,此时next()就会返回null,否在会一直阻塞在那里,具体代码如下:

   public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

当MessageQueue的next()返回新的消息,msg.target.dispatchMessage(msg)就会将消息切换到创建Handler时使用的Looper中去处理。关于MessageQueue的工作原理和源码就不解释了,虽然MessageQueue叫消息队列,但是实际上是通过单链表的数据结构来维护消息队列。

  1. 创建消息
  • 创建Message:
Message message = new Message(); 
message.what = ...;
message.obj = ...;

Message长相如下:

public final class Message implements Parcelable {
...
    public int what;
    public int arg1;
    public int arg2;
    public Object obj;
    public Messenger replyTo;
    ...
    }

通常我们会使用what属性来标记消息的来源,what和InnerEvent的eventId基本是一个意思,都是识别消息,然后让开发者进行消息处理,如果一个what无法识别,可以再添加arg1、arg2帮助识别。obj就是我们需要传递的具体消息内容,如果没有内容,可以不传。Messenger就是可以跨进程的信使,详细使用这里就不解释了。

  • 创建Runnable:
    Runnable runnable = new Runnable() {
            @Override
            public void run() {
                ...
            }
        };

run()中一般是具体消息处理的实现。

  1. 发送消息
  • Message:
   mHandler.sendMessage(messag);

发送消息也有其他的形式:

    public final boolean sendMessage(@NonNull Message msg) {
        ...
    }
    public final boolean sendEmptyMessage(int what)
    {
       ...
    }
   ...
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
       ...
    }
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
    ...
    }

关于消息是否是空消息,是否延时,是否定时,是否先执行,Handler中都给出了选择,开发者可根据实际需要自由组合。

  • Runnable:
mHandler.post(runnable);

发送runnable形式也不止一种:

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
   
    public final boolean postAtTime(
            @NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
   ...
    }
    public final boolean postDelayed(
            @NonNull Runnable r, @Nullable Object token, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r, token), delayMillis);
    }
    public final boolean postAtFrontOfQueue(@NonNull Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

关于定时,延时,优先级,标识开发者自由组合。

  1. 其他
...
    public void dispatchMessage(@NonNull Message msg) {
      ...
    }
    ...
     @NonNull
    public static Handler createAsync(@NonNull Looper looper) {
       ...
    }
    ...
     public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
       ...
    }

关于是否同步,是否移除,分发消息,获取消息等等Handler都提供了选择,如何使用根据实际情况来。

总结

  • 关于消息机制,HarmonyOS和Android相差不大,大致相同。
  • HarmonyOS中给消息机制提供了手动模式和托管模式,虽Android没有多种模式,但使用模式相当于HarmonyOS的托管模式。
  • HarmonyOS给消息进行了优先级的划分,Android虽没有,但是对消息是否能排在消息队列前面做了处理,结果相似。
  • HarmonyOS自动化程度较高,无需开发者过多操作。Android则是给开发者提供了丰富的API供调用,开发者参与感更高。
  • HarmonyOS的分布式做的比较好,数据及任务渠道高效、敏捷,而Android则在横向上比较有优势,服务和功能更加丰富、灵活。
  • 任何代码有开头必须有结尾,若程序未实现,则需开发者手动实现。
说明
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值