精华内容
下载资源
问答
  • 同步消息和异步消息传递的区别?

    千次阅读 多人点赞 2018-10-23 00:26:47
     同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。即发送方向接收方发送消息,接收方接收此消息并回复发送方。发送者在收到接收者的回复...

    在系统交互时候选择同步还是异步有时候很让人困扰,希望通过阅读这篇文章可以帮助更好的理解同步与异步。

    同步与异步消息的区别

    1、同步消息

          同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。即发送方向接收方发送消息,接收方接收此消息并回复发送方。发送者在收到接收者的回复之前不会发送另一条消息。

    同步执行的特征为:在两个通信应用系统之间必须要进行同步, 两个系统必须都在正常运行, 并且会中断客户端的执行流, 转而执行调用。发送程序和接收程序都必须一直做好相互通信的准备。发送程序首先向接收程序发起一个请求(发送消息)。发送程序紧接着就会堵塞它自身的进程, 直到收到接收程序的响应。发送程序在收到响应后会继续向下进行处理。

    2.异步消息

    异步消息传递涉及不等待来自服务器的消息的客户端。事件用于从服务器触发消息。因此,即使客户机被关闭,消息传递也将成功完成。异步消息传递意味着,它是单向通信的一种方式,而交流的流程是单向的。

    当使用异步消息传送时, 调用者在发送消息以后可以不用等待响应, 可以接着处理其他任务。对于异步通信, 一个应用程序(请求者或发送者)将请求发送给另一个应用程序, 然后可以继续向下执行它自身的其他任务。发送程序无须等待接收程序的执行和返回结果, 而是可以继续处理其他请求。与同步方式不同, 异步方式中两个应用系统(发送程序和接收程序)无须同时都在运行, 也无须同时都在处理通信任务。

    同步和异步消息传递的优点和缺点

            异步消息传递有一些关键优势。它们能够提供灵活性并提供更高的可用性——系统对信息采取行动的压力较小,或者以某种方式立即做出响应。另外,一个系统被关闭不会影响另一个系统。例如,电子邮件——你可以发送数千封电子邮件给你的朋友,而不需要她回复你。

            异步的缺点是它们缺乏直接性。没有直接的相互作用。考虑一下与你的朋友在即时通讯或电话上聊天——除非你的朋友及时回复你,否则这不是聊天或谈话。

    异步消息传递允许更多的并行性。由于进程不阻塞,所以它可以在消息传输时进行一些计算。

    展开全文
  • 在系统交互时候选择同步还是异步有时候很让人困扰...同步与异步消息的区别1、同步消息 同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。即...

    转载 https://www.cnblogs.com/eason-liu/p/8053558.html

    [基础]同步消息和异步消息传递的区别?

    在系统交互时候选择同步还是异步有时候很让人困扰,希望通过阅读这篇文章可以帮助更好的理解同步与异步。

    同步与异步消息的区别

    1、同步消息

          同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。即发送方向接收方发送消息,接收方接收此消息并回复发送方。发送者在收到接收者的回复之前不会发送另一条消息。

    2、异步消息

         异步消息传递涉及不等待来自服务器的消息的客户端。事件用于从服务器触发消息。因此,即使客户机被关闭,消息传递也将成功完成。异步消息传递意味着,它是单向通信的一种方式,而交流的流程是单向的。

    如果这还不好理解,那继续往下读...

    异步:比如A是字符集第一个字母,唯一可行的方法就是向Z走,这意味着是单向通信。

    同步:比如同步是从字母S开始,可能是朝向可能是A或Z,这意味着是双向通信。

    同步和异步消息传递的有点和缺点

            异步消息传递有一些关键优势。它们能够提供灵活性并提供更高的可用性——系统对信息采取行动的压力较小,或者以某种方式立即做出响应。另外,一个系统被关闭不会影响另一个系统。例如,电子邮件——你可以发送数千封电子邮件给你的朋友,而不需要她回复你。

            异步的缺点是它们缺乏直接性。没有直接的相互作用。考虑一下与你的朋友在即时通讯或电话上聊天——除非你的朋友及时回复你,否则这不是聊天或谈话。


    异步消息传递允许更多的并行性。由于进程不阻塞,所以它可以在消息传输时进行一些计算。

    异步消息传递引入了几个问题。如果消息无法传递会发生什么?如果消息在传输中丢失了怎么办?

    与异步消息传递相关的另一个问题与缓冲有关。如果在操作系统管理的空间中对消息进行异步处理,则进程可能会通过大量消息向数据库中写入数据。

    哪个更好——同步还是异步?

    这个问题没有答案。

    展开全文
  • 文章目录一、消息机制原理1.1 基本概念1、Handler2、MessageQueue3、Message4、Looper1.2 消息机制主流程1、发送消息2、消息入队3、消息出队4、消息分发二、同步屏障三、IdleHandler四、消息对象池3.1 创建Message...

    一、消息机制原理

    Handler消息机制老生常谈了,必备八股之一。但是每次看都有新收获,故好好总结一下Handler相关知识。

    1.1 基本概念

    1、Handler

    用于发送和处理消息的类,有多种重载的构造方法,通过一系列sendXXXpostXXX方法来发送消息到消息队列,然后通过实现Handler.Callback接口或重写handleMessage方法处理消息

    2、MessageQueue

    消息队列,它是一个链表结构,用以存放handler发送的消息,实现了获取消息的方法next()和移除消息及消息处理回调的方法(removeXXX系列方法)

    3、Message

    消息,承载一些基本数据,消息队列存放对象。维护了一个消息对象池,可以复用消息,避免创建太多消息对象占用过多内存,导致APP卡顿。

    消息分类:
    在这里插入图片描述

    4、Looper

    消息机制的灵魂,用以不断调度消息对象并且分发给handler处理。Looper是同线程绑定的,不同线程的Looper不一样,通过ThreadLocal实现线程隔离。

    1.2 消息机制主流程

    1、发送消息

    在这里插入图片描述

    可以使用sendMessage(以及一系列 sendXXX的消息发送方法)和post方法发送即时同步消息,或通过sendXXXDelayed和postDelayed发送延迟同步消息。

    如果是通过sendXXX方法发送即时或延时消息,最终都会辗转调用到sendMessageAtTime(@NonNull Message msg, long uptimeMillis)方法,然后调用enqueueMessage方法。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;// ① 设置处理该消息的handler对象
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    		// ② 设置消息类型,同步或异步
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
      		// ③ 交由消息队列的入队方法
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    该方法主要有3个作用,注释中的①②③分别说明了。

    2、消息入队

    消息入队最终是靠消息队列的恩queueMessage方法完成,其代码如下

    boolean enqueueMessage(Message msg, long when) {
      		// ①
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    			// ②
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
              	// ③
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    // ④
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
    			// ⑤
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    注释中标明了5个注意点,👇一一说明下:

    ① 消息对象必须指定target,也就是处理消息的handler对象;而且message对象的flagFLAG_IN_USE。否则将抛出异常。

    ②设置消息对象标志FLAG_IN_USE和时间,创建唤醒字段,用于标记是否需要唤醒消息队列

    ③如果当前消息队列没有消息或要入队的消息when值小于对列头消息when值,则将新消息插入到链表头部。设置needWeak,它又由mBlocked变量决定,mBlocked的设置是在next()方法中,简单来说消息队列仅有延时消息或空队列时,mBlockedtrue

    ④不满足③的情况下,从消息链表头开始遍历,将新消息插入到第一个when值大于新消息when值的消息节点前方。

    例如当前消息队里:100 - 30 -20 -10(数字表示消息的when值)

    100
    30
    20
    10

    现要插入一个新消息50,那么插入后的队列情况是:

    100
    30
    20
    10
    50

    ⑤是否需要唤醒,需要则调用native方法唤醒

    总之,入队方法就是让所有消息根据when的大小尽量有序排列,when越小则越位于消息链表头部

    3、消息出队

    Looper的loop()在一个死循环中不断获取消息,获取到消息就分发给handler处理,获取消息通过MessageQueue#next()方法,这个方法逻辑较多且都比较重要,下面会详细说明。

     Message next() {
            ......
    		// ①
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
       		// ②
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    			// ③
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                  	// ④
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                  	// ⑤
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                  	// ⑥
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    			// ⑦
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    pendingIdleHandlerCount表示IdleHandler的数量。

    nextPollTimeoutMillis表示消息队列休眠的时间,是个阻塞方法。 -1表示无限阻塞,0表示不阻塞

    ③ 实现阻塞的native方法,可通过nativeWake方法唤醒

    ④ 针对同步屏障机制的处理,前文已经说了普通消息在入队前一定会设置target属性,唯独有种方式不会,即postSyncBarrier方法发出的同步屏障消息是不会设置target属性的,同步屏障相关内容后面会详细介绍,这里只要了解普通的同步消息不会走到这步即可。

    ⑤ 对于同步消息,从此步开始真正去获取消息对象。首先明确下代码里的几个对象含义:mMessage始终表示消息链表头部,p表示当前节点,prevMsg表示p节点的前一个节点。

    • 对于即时消息,设置mBlocked=false,表示不阻塞。同步消息的prevMsg始终为null,所以从头结点开始遍历,获取当前节点并返回。

    • 对于延时消息,计算延时时间,然后走到⑥,若now < mMessages.when表示还没到延时消息执行时间,然后会走到if(pendingIdleHandlerCount <= 0)中,设置mBlocked=true,然后开始下次循环,又走到③处,nextPollTimeoutMillis不等于0,于是阻塞。

    ⑥ 用于计算IdleHandler个数,初始化IdleHandler数组。IdleHandler是用于在消息队列空闲时处理一些任务,适用于一些不紧急非高优的任务,后面也会详细介绍。

    ⑦ 重置pendingIdleHandlerCountnextPollTimeoutMillis

    4、消息分发

    前文说了Looper的loop方法不断获取消息并分发,分发的关键代码就是

    public static void loop() {
    				......
            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;
    						......
                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);
                    }
                }
               	......
               	// ④
                msg.recycleUnchecked();
            }
        }
    

    ① 获取Looper对象,如果为空的话抛出异常。

    ② 可以通过Looper#setMessageLogging方法设置打印器,用来输出一些开发者需要的信息,通常在性能监控上需要获取这些信息来评估优化效果。

    ③ 分发消息给handler处理,target就是在消息入队时设置的handler对象。

    ④ 回收消息对象

    步骤③将消息分发给了对应handler,看下dispatchMessage方法的实现

    public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    代码很清晰,首先如果设置msg.callback,就调用handleCallback方法。那么msg.callback在哪里设置的呢?找到赋值的地方,发现postpostDelayed方法

    public final boolean post(@NonNull Runnable r) {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    

    传入了getPostMessage方法,继续看该方法

    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    

    现在明了了,正是这里设置了msg.callback,并且值就是post的参数,一个runnable对象。

    看下handleCallback代码

    private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    其实就是执行了post传入的runnable参数的run方法。

    如果不是通过post方式发送的消息,就会走到else逻辑里。首先判断是否实现了Handler.Callback接口,可在handler的构造函数传入,设置了则调用Handler.Callback接口的handleMessage方法。

    否则调用HandlerhandleMessage方法,它是一个空方法,需要开发者重写来实现业务逻辑。

    总结:消息的分发执行顺序就是post#run方法 -> Handler.Callback.handlerMessage方法 -> Handler#handlerMessage方法

    至此,Handler消息的发送、入队出队、以及分发执行的全流程就阐述完毕了,路径还是很清晰的。但是依然遗留了一些问题,比如同步屏障、IdleHandler等,所以我们继续(🐶)。

    二、同步屏障

    我们知道无论是应用启动还是屏幕刷新都需要完整绘制整个页面内容,目前大多数手机的屏幕刷新率为60Hz,也就是耳熟能详的16ms刷新一次屏幕。那么问题来了,如果主线程的消息队列待执行的消息非常多,怎么能保证绘制页面的消息优先得到执行,来尽力保证不卡顿呢。

    前文分析了整个消息传递处理机制,有一个可疑地方,就是在取消息时。2个疑点

    • 消息的target属性为null
    • 消息被设置为了异步消息
    				...... 								
    				final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                  	// *
                    if (msg != null && msg.target == null) {
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
    
                    if (msg != null) {
                        if (now < msg.when) {
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                          	......
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    

    在标*处,发现有个循环不断过滤掉同步消息,发现进入条件是target对象为null,而正常情况下入队的消息都会设置target。

    从应用启动入手,页面启动过程不详述了,大体调用链路是ViewRootImpl#setView -> ViewRootImpl#requestLayout -> ViewRootImpl#scheduleTraversals

    看下scheduleTraversals方法代码

     void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
              	// ①
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    

    调用了消息队列的postSyncBarrier方法,进去看看

    		/**
    		* @hide  
    		*/
    		public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        private int postSyncBarrier(long when) {
            // Enqueue a new sync barrier token.
            // We don't need to wake the queue because the purpose of a barrier is to stall it.
            synchronized (this) {
                final int token = mNextBarrierToken++;
              	//①
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
              	// ②
                if (when != 0) {
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                }
                if (prev != null) { // invariant: p == prev.next
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    

    ① 发现创建了Message对象,但没有设置target属性。通过前面对handler的分析知道,loop方法分发给handler执行完后会回收message对象,即msg.recycleUnchecked();,它会将message对象的所有属性置空。

    ② 这一步跟普通的消息入队目的一样,就是把这个同步屏障消息按照when值大小插入到链表,when越大越靠近链表尾部。由于同步屏障消息设置的when是系统启动以来的时间,非常长,所以一般来说同步屏障消息基本都插入在尾部。

    第一个问题什么消息的target是null,那就是postSyncBarrier发送的同步屏障消息

    设置同步屏障后代码继续执行,执行 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

    一直深入postCallback查看,发现执行到了postCallbackDelayedInternal方法

    private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long delayMillis) {
           	......	
    
            synchronized (mLock) {
                final long now = SystemClock.uptimeMillis();
                final long dueTime = now + delayMillis;
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    
                if (dueTime <= now) {
                    scheduleFrameLocked(now);
                } else {
                  	// *
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }
    

    *:创建了真正绘制页面的消息对象,并且调用setAsynchronous()将消息设置为了异步

    所以第二个问题什么时候设置消息为异步也知道了。

    总结:对于异步消息,Looper会遍历消息队列找到异步消息执行,确保像刷新屏幕等高优任务及时得到执行。同步消息得不到处理,这就是为什么叫同步屏障的原因。当使用了同步屏障,记得通过removeSyncBarrier移除,不然同步消息不能正常执行。

    **当然,正常情况开发者也不能手动发送和移除同步屏障,因为它们都被hide注释了。**不过了解这一机制和其中蕴含的编程思维还是很有裨益的

    三、IdleHandler

    IdleHandler提供了一种在消息队列空闲时执行的某些操作的手段,适用于执行一些不重要且低优先级的任务。它的使用也很简单调用MessageQueue#addIdleHandler方法将任务添加到消息队列,然后队列空闲时会自动执行,可通过removeIdleHandler方法或自动回收。

    消息队列通过一个ArrayList来储存添加的IdleHandler任务。

    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    // 临时储存IdleHandler任务
    private IdleHandler[] mPendingIdleHandlers;
    

    调用IdleHandler任务的位置在MessageQueue#next()方法中,无关代码已省略

    Message next() {
    		// ①
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
               	......
                synchronized (this) {
                  	// 省略部分为获取消息对象的过程
                    .....
                      
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    // ②
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    				//③
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
              	// ④
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    			// ⑤
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    ① 每次Looper调用next方法时,先将IdleHandler临时数组的大小pendingIdleHandlerCount重置为 -1。

    ② 首次运行时pendingIdleHandlerCount < 0肯定成立,如果当前消息队列为空或者只有延时消息时,认为此时队列空闲可以执行IdleHandler任务了。令pendingIdleHandlerCount为已添加的IdleHandler任务个数。

    mPendingIdleHandlers是一个数组,首次执行时可定为空,所以初始化数组,数组大小最小为4。并且将mIdleHandlers列表中的任务复制到这个临时数组。

    ④ 循环临时数组执行IdleHandler任务,任务从mPendingIdleHandlers数组中取出后,会置空,释放对handler对象的引用。然后调用queueIdle()真正执行IdleHandler任务。

    queueIdle()是一个接口方法,需要自己实现业务逻辑。另外它的返回值决定是否要自动删除该IdleHandler任务,返回true该任务执行后将不会被删除

    ⑤ 重置mPendingIdleHandlers = 0,开启下次循环。

    四、消息对象池

    消息是数据的载体,我们知道创建消息对象是一般不提倡去new一个对象,而是调用Message的一系列obtain的重载方法,原因就是因为可以复用已创建的Message对象,避免创建过多对象占据大量内存。既然是复用,那么一定存在某种数据结构去保存对象,这就是消息对象池,使用的是链表结构。

    3.1 创建Message对象

    消息对象池有几个重要属性,分别是:

    	 	// 同步对象
    		public static final Object sPoolSync = new Object();
    		// 链表头节点
        private static Message sPool;
    		// 消息池大小(链表长度,表示消息个数)
        private static int sPoolSize = 0;
    		// 消息池最大容量
        private static final int MAX_POOL_SIZE = 50;
    

    看下obtain方法

    public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    

    代码很简洁,获取消息对象是同步操作,从头节点开始,如果头结点不为空,取得头结点。然后指针后移,并且消息池大小减1。否则的才通过new方式创建新对象。

    3.2 回收Message对象

    可以调用recycle方法回收消息对象

    public void recycle() {
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");
                }
                return;
            }
            recycleUnchecked();
        }
    

    如果消息标记了FLAG_IN_USE标志,不可回收。然后真正回收的方法是recycleUnchecked();

    void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = UID_NONE;
            workSourceUid = UID_NONE;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    

    可见回收消息,首先就是将其成员变量全部重置为初始值,然后在消息池大小不超过限制容量时,让将要被回收节点的next指向头结点,再把头指针移到当前节点,容量加1。

    五、总结

    本文从Handler消息机制出发,分析了消息从发送、调度和分发处理的全过程。在此过程中,发现涉及到了同步屏障、IdleHandler等知识点,并对其做了分析和说明。有些东西可能在平时开发中用不上,例如消息屏障,但其蕴含的编程思想也是十分值得学习借鉴的。

    展开全文
  • 消息传递作为基本通信机制已经在全世界成功运用。无论是人与人、机器与人还是机器与机器之间,...同步消息传递在这种情况下使用,当消息发送者希望在某个时间范围内收到响应,然后再进行下一个任务。基本上就是他...

    转载自:http://geek.csdn.net/news/detail/71894

    消息传递作为基本通信机制已经在全世界成功运用。无论是人与人、机器与人还是机器与机器之间,消息传递一直都是唯一常用的通信方式。在双方(或更多)之间交换消息有两种基本机制。

    1. 同步消息传递
    2. 异步消息传递

    同步消息传递在这种情况下使用,当消息发送者希望在某个时间范围内收到响应,然后再进行下一个任务。基本上就是他在收到响应前一直处于“阻塞”状态。

    异步消息意味着发送者并不要求立即收到响应,而且也不会阻塞整个流程。响应可有可无,发送者总会执行剩下的任务。

    上面提到的技术,当两台计算机上的程序相互通信的时候,就广泛使用了异步消息传递。随着微服务架构的兴起,很明显我们需要使用异步消息传递模型来构建服务。

    这一直是软件工程中的基本问题,而且不同的人和组织机构会提出不同的方法。我将介绍在企业IT系统中广泛使用的三种最成功的异步消息传递技术。

    Java消息传递服务(Java Messaging Service (JMS))

    JMS是最成功的异步消息传递技术之一。随着Java在许多大型企业应用中的使用,JMS就成为了企业系统的首选。它定义了构建消息传递系统的API。

    这里写图片描述

    图片来源:http://www.javatpoint.com/jms-tutorial
    下面是JMS的主要特性:

    • 面向Java平台的标准消息传递API
    • 在Java或JVM语言比如Scala、Groovy中具有互用性
    • 无需担心底层协议
    • 有queues和topics两种消息传递模型
    • 支持事务
    • 能够定义消息格式(消息头、属性和内容)
    • 高级消息队列协议(Advanced Message Queueing Protocol (AMQP))
    • JMS非常棒而且人们也非常乐意使用它。微软开发了NMS(.NET消息传递服务)来支持他们的平台和编程语言,它效果还不错。但是碰到了互用性的问题。两套使用两种不同编程语言的程序如何通过它们的异步消息传递机制相互通信呢。此时就需要定义一个异步消息传递的通用标准。JMS或者NMS都没有标准的底层协议。它们可以在任何底层协议上运行,但是API是与编程语言绑定的。AMQP解决了这个问题,它使用了一套标准的底层协议,加入了许多其他特征来支持互用性,为现代应用丰富了消息传递需求。
      这里写图片描述
      图片描述
      图片来源:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_MRG/2/html-single/Messaging_Programming_Reference/index.html
      下面是AMQP的主要特性:

    • 独立于平台的底层消息传递协议

    • 消费者驱动消息传递
    • 跨语言和平台的互用性
    • 它是底层协议的
    • 有5种交换类型direct,fanout,topic,headers,system
    • 面向缓存的
    • 可实现高性能
    • 支持长周期消息传递
    • 支持经典的消息队列,循环,存储和转发
    • 支持事务(跨消息队列)
    • 支持分布式事务(XA,X/OPEN,MS DTC)
    • 使用SASL和TLS确保安全性
    • 支持代理安全服务器
    • 元数据可以控制消息流
    • 不支持LVQ
    • 客户端和服务端对等
    • 可扩展

    消息队列遥测传输(Message Queueing Telemetry Transport (MQTT))

    现在我们已经有了面向基于Java的企业应用的JMS和面向所有其他应用需求的AMQP。为什么我们还需要第三种技术?它是专门为小设备设计的。计算性能不高的设备不能适应AMQP上的复杂操作,它们需要一种简单而且可互用的方式进行通信。这是MQTT的基本要求,而如今,MQTT是物联网(IOT)生态系统中主要成分之一。

    这里写图片描述
    图片来源:https://zoetrope.io/tech-blog/brief-practical-introduction-mqtt-protocol-and-its-application-iot
    下面是MQTT的主要特性:

    • 面向流,内存占用低
    • 为小型无声设备之间通过低带宽发送短消息而设计
    • 不支持长周期存储和转发
    • 不允许分段消息(很难发送长消息)
    • 支持主题发布-订阅
    • 不支持事务(仅基本确认)
    • 消息实际上是短暂的(短周期)
    • 简单用户名和密码,基于没有足够信息熵的安全
    • 不支持安全连接
    • 消息不透明
    • Topic是全局的(一个全局的命名空间)
    • 支持最新值队列(Last Value Queue (LVQ) )
    • 客户端和服务端不对称
    • 不能扩展
    展开全文
  • 同步消息传递中,对话的发起者等待重播每个提交的请求,另一方面,在异步消息传递中,发起者对重播不感兴趣。 Java进程以同步方式进行通信的最常见,最有效的方法是通过远程方法调用(RMI)...
  • 另一方面,使用同步消息传递时,对话的发起者等待重播每个提交的请求,而在异步消息传递中,发起者对重播不感兴趣。 Java进程以同步方式进行通信的最常见,最有效的方法是通过远程方法调用(RMI)。 异步通信主要...
  • TCPServer.java: import java.net.*; // TCP/IP Socket编程所用包 import java.io.*; public class TCPServer{ public static void main( String[] args ) throws ... 这个是同步方式的. 简单的基础的通信..聊以参考.
  •  信号量主要用于进程间同步。  sem_post函数执行时,将信号量加1,然后执行后续的操作。  sem_wait函数执行时,等待信号量,如果信号量大于1,则减1,然后执行以后的操作。否则一直等待在这里。 3.程序...
  • 此存储库包含的规范, 是模块化的对等消息传递堆栈,重点是安全消息传递。 有关vac及其设计目标的详细说明,请参见。 状态 整个vac协议正在积极开发中,每个规范都有其自己的status ,这可以通过每个文档顶部的版本...
  • 异步(脉冲)消息主要体现的是一种通知机制,同步消息主要是说消息在传递过程需要双方相互配合的过程。 二、QNX消息传递几个基本概念 1、频道与链接 消息传递是基于服务器与客户端的模式来进行的,QNX6抽象出了”频道...
  • Actor和消息传递

    2018-07-04 21:50:53
    它只能通过消息传递的方式与外界进行通信。2.消息传递:一个Actora可以接收消息(该例中该消息是一个对象),本身可以发送消息,也可以对接收到的消息作出回复。消息传递是异步的。无论是处理消息还是回复消息,...
  • xabber-server:Xabber服务器:先进的XMPP服务器,具有可靠的消息传递,消息编辑和撤消,群聊,活动会话管理以及快速的客户端同步
  • 同步性 因为多线程在你的程序中引入了一个异步行为,所以在你需要的时候必须有加强同步性的方法。举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链表序列,你需要某些方法来确保它们没有相互...
  • TCPServer.java: import java.net.*; // TCP/IP Socket编程所用包 ... 这个是同步方式的. 简单的基础的通信..聊以参考. 转载于:https://www.cnblogs.com/25-to-life/archive/2010/08/22/1805737.html
  • 管程 由一个或多个过程、一个初始化序列和局部数据组成的软件模块,主要特点: 局部数据变量只能被管程的过程访问,任何外部过程都不能访问 一个进程通过调用管程的一个过程进入管程. ...管程的同步工具:...
  • Java消息传递机制

    千次阅读 2019-04-24 19:58:24
    * 消息传递机制(同步回调,异步回调) * 作者:Ai * 时间:2018年4月29日13:12:39——2018年4月29日23:56:42 * 注释:在多线程和观察者模式的基础上,系统中使用消息传递(类似广播的方式 * 消息机制是面向对象...
  • 在多任务操作系统环境下,多进程/多线程间同时运行,并且这些进程之间存在一定的关联,多个进程/线程可能为了完成同一个任务相互协作,这就是进程之间的同步,信号量是用来解决进程间同步与互斥的一种进程间通信机制...
  •  1 进程的互斥与同步方法:消息传递  2  3 低级通信:进程之间交换控制信息的过程。  4 高级通信:进程之间交换批量数据的过程。  5  6 进程之间的同步与互斥是...
  • 有时候 backgroud.js 需要向所有的页签同时同步消息,这时就要获取到所有页签的 tabid 了,下面的方法即可实现。 // 获取所有的页签 chrome.tabs.getAllInWindow(null, function(tabs) // 通过tabid向每一个页签发送...
  • 使用Spring AMQP进行消息传递 本文讨论使用Spring AMQP框架实现AMQP消息通信。先介绍一些消息通信核心概念,然后通过一个实际示例进行实战。 1. 核心概念 1.1. 消息传输 消息传输是应用间进行消息通信的技术,基于...
  • 包括调用消息(同步消息),异步消息,返回消息,阻止消息,超时消息 调用消息:(UML早期版本也称为同步消息) 定义:调用消息(procedure call )消息的发送者把控制传递给消息的接收者,然后停止活动,等待消息...
  • 同步消息Synchronous Message 两个通信应用服务之间必须要进行同步,两个服务之间必须都是正常运行的。发送程序和接收程序都必须一直处于运行状态,并且随时做好相互通信的准备。 发送程序首先向接收程序发起一个...
  • 本系列主要讲解kafka基本设计和原理分析,分...kafka消息传递语义 Kafka集群partitions/replicas默认分配解析 消息传递语义 消息传递保障 本节讨论Kafka如何确保消息在producer和consumer之间传输。有以下三种可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,772
精华内容 1,108
关键字:

同步消息传递