Android:Handler的消息机制

栏目: 后端 · 发布时间: 5年前

Android 的消息机制原理是 Android 进阶必学知识点之一,在 Android 面试也是常问问题之一。在 Android 中,子线程是不能直接操作 View ,需要切换到主线程进行。那么这个切换动作就涉及到了 Android 的消息机制,也就是本文要讲的Handler、Looper、MessageQueue、Message它们之间的关系。

知识点分享

Android IntentService学习

Handler

Handler 在消息机制中扮演 发送消息处理消息 的角色,也是我们平常接触最多的类。

Handler如何处理消息?

下面代码展示 Handler 如何处理消息。新建 Handler 对象,并重写handleMessage,在方法内处理相关逻辑,一般处理和主线程相关的逻辑。Handler有很多的构造器,下面构造器常用在主线程。

private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what==1){
                Toast.makeText(MainActivity.this,"handle message",Toast.LENGTH_LONG).show();
            }
            
        }
    };
    
复制代码

Handler是如何发送消息的呢?

通过下面的代码可以了解到, Handler 对象支持发送 MessageRunable 。Runable最终被包装成 Messagecallback 实例变量( Handler 对象处理消息会优先处理 callback 的逻辑),和 Message 一样的方式放到消息队列中。而每个方法都有相关的变形,支持延迟发送,或者未来的某段时间里发送等等。

//在消息池获取消息体,能达到消息重用,如果消息池没有消息,则新建消息
        Message msg = handler.obtainMessage();
        msg.what = 1;
        //发送消息
        handler.sendMessage(msg);
        //发送空消息,参数会自动被包装msg.what=1
        handler.sendEmptyMessage(1);
        //未来的时间里发送消息
        handler.sendEmptyMessageAtTime(1, 1000);
        //延迟发送消息
        handler.sendEmptyMessageDelayed(1, 1000);

        msg = handler.obtainMessage();
        msg.what = 2;
        //将消息发送消息队列前面
        handler.sendMessageAtFrontOfQueue(msg);
        //发送任务,run方法内容将handler被处理。
        handler.post(new Runnable() {
            @Override
            public void run() {
                Log.i("Handler", "Runnable");
            }
        });
复制代码

如果平常使用,我们只需要主线程定义 Hanlder 处理消息的内容,在子线程发送消息即可达到切换流程。

Looper

Looper 负责循环的从消息队列中取消息,发送给 Handler 处理。因为消息队列只用来存储消息,所以需要 Looper 不断的从消息队列中取消息给 Handler 。默认情况,所有线程并不拥有 Looper 。如果在子线程直接执行 Looper.loop 方法,就会发生异常。那主线程为什么不会报错?在App的启动流程中,创建 ActivityThread 时,会调用 Looper.prepare 来创建 LooperMessageQueue ,和 Looper.loop 开启循环。也就是系统为我们在主线程创建 LooperMessageQueue 。所以,在子线创建 Handler 前,需要先调用 Looper.prepare 方法,创建 LooperMessageQueueIntentService 就是这样实现的。 点击看IntentService的知识点。

MessageQueue

MessageQueue内部是以链表的形式组织的,主要作用是存储 Message 。在创建 Looper 的时候,会自动创建 MessageQueue

三者关系形成了Android的消息机制

Handler 发送消息时会将消息插入到 MessageQueue ,而 Looper 不断的从 MessageQueue 中取消息分发给 Handler 处理。

源码解析

Handler的构建

我们先看一下Handler的构造器代码。

public Handler(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;
    }
    
复制代码

Handler 有很多重载的构造器,我们常用在使用默认构造器,最终会调用上面的构造器。

分析一

通过 Looper.myLooper() ,获 Looper 的实例。而在 myLooper 的实现中,是通过 ThreadLocalget 方法来获取的。如果 ThreadLocal 不存在 Looper ,则放回 nullThreaLocal 这里可以简单理解为保存当前线程私有独立的实例,其他线程不可访问。如果 ThreadLocal 不存在 Looper 实例则,返回 null 。这也就是前面说的,在子线程创建 Handler 前,需要先调用 Looper.prepare 方法。否则会抛出 RuntimeException

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
复制代码

分析二

mQueueLooper 中的消息队列, mCallBack 定义了一个接口,用于回调消息处理。

public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
复制代码

Handler发送消息

Handler所有发送消息方法的变体最终都会以下面方法放去到消息队列中。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

复制代码

这里最重要的就是 enqueueMessage 方法中,将当前 Handler 对象设置给 Messagetarget 变量。然后调用队列 queueenqueueMessage 方法。

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;
            //如果队列为空或者插入message未来处理时间小于当前对头when
            //则将当前消息设为队列头
            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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

复制代码

而在 MessageQueueenqueueMessage 方法中,会先检查 target 是否 nullmessage 是否应在使用,当前线程是否退出,死亡状态。如果是,则抛出异常。如果当前队列是空或者阻塞,直接当前 Message 对象设为队列的头并唤醒线程。如果不是,则根据 Message 对象的 when 插入到队列合适的位置。因此可以看得出, Handler 发送消息时是将消息放到队列中。

Looper和MessageQueue的创建

前面讲过,子线程使用Handler,需要调用 Looper 的静态 prepare 方法。

public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
复制代码

如果当前线程已经有 Looper ,代用Looper就会报错。如果没有, new Looper 并保存到 ThreadLocal 中。 new Looper 非常简单,只是新建一个 MessageQueue ,和持有当前线程。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
复制代码

Looper是如何实现循环的

在调用了 Looper.prepare 创建 LooperMessageQueue 对象后,要调用 Loop.looper 的开始循环分发消息队列中消息的工作。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        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);
            }

            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;
            try {
                //分析二:
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                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);
            }


            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();
        }
    }

复制代码

分析一:通过无限制的 for 循环,读取队列的消息。而 MessageQueuenext 方法内部通过链表的形式,根据 when 属性的顺序返回 message

分析二:调用 Message 对象的 targetdipatchMessage 方法。这里的 target 就是发送消息的 Handler 对象。而在 Handler 对象的 dipatchMessage 方法中,优先执行 Message 对象的 callback 方法,即优先执行我们发送消息时以 Runable 发送的任务,如果有的话。不然检测 Callback 对象的 handleMessage 方法,最后才是我们重写 Hanlder 对象的 handleMessage 方法。因为 Handler 不仅有默认构造函数,还有可以传入 Callback , Looper 等的构造函数。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
复制代码

Message的复用

通过 handler.obtainMessage 而不是 new 方式获得消息实例。因为 obtainMessage 方法会先检测消息池是否有可以复用的消息,没有再去 new 一个消息实例。下面是类Message的 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();
    }
复制代码

sPool 的类型是 Message ,内部通过成员变量 next ,维护一个消息池。虽然叫消息池,内部却通过 next 不断的指向下一个 Message ,以链表维护的这个消息池,默认大小为50。在链表 sPool 不为空的情况,取表头 Message 元素,并将相关属性进行初始化。

那么Message对象是在什么时候被放进消息池中的呢?

Looperloop 方法中,最后调用 MessagerecycleUnchecked 方法

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 = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
复制代码

在同步代码块,可以看到,将 sPool 指向当前要被回收的 Message 对象,而 Messagenext 指向之前的表头。


以上所述就是小编给大家介绍的《Android:Handler的消息机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

八年级数学(华东师大版)-解题升级-解题快速反应一本通(新课标)

八年级数学(华东师大版)-解题升级-解题快速反应一本通(新课标)

孙丽敏等编 / 吉林教育出版社 / 2004-6 / 10.0

本书将与知识点、重点、难点和考点有关的典型题做全析全解,是具有解题题典性质的助学读物。但本书又优于解题题典,不仅展示解题过程,更详细地提供了解题思考过程和切入点的选择方法,教方法导引思路的功能更强。 学生要提高解题能力,必须具备两个条件:一是打好基础,二是能够运动所学知识分析问题和解决问题。本书用例题解析解说知识点、重点、难点和考点,同时提供解题思考过程,在打基础中激活能力,在解题实......一起来看看 《八年级数学(华东师大版)-解题升级-解题快速反应一本通(新课标)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换