Handler 消息处理分析

为什么使用Handler?
Android的UI要求更新只能在UI线程,因为安卓是单线程模型。如果任意线程都可以更新UI的话,线程安全问题处理起来会相当麻烦复杂,就会出现页面错乱。所以就规定了Android的是单线程模型,只允许在UI线程更新UI操作。

也就是在Android中更新Ui必须在主线程,在子线程更新Ui会报子线程不能修改UI异常。

你想想安卓的页面一会被这个线程修改,一会被别的线程更改。用户体验就会很差,给别人一种不可控制的感觉。

Handler是什么?
主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。
简单说Handler就是谷歌的Android的为了开发人员封装的一套更新UI的机制,消息处理机制。就是为了方便开发者人员的。

Handler 消息发送的分类
Handler 平时发送消息主要是调用两大类方法, 分别是 send 方案 以及 post 方案

send 方案
boolean sendMessage(Message msg)
boolean sendEmptyMessage(int what)
boolean sendEmptyMessageDelayed(int what, long delayMillis)
boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
boolean sendMessageDelayed(Message msg, long delayMillis)
boolean sendMessageAtTime(Message msg, long uptimeMillis)
boolean sendMessageAtFrontOfQueue(Message msg)

post 方案
boolean post(Runnable r)
boolean postAtFrontOfQueue(Runnable r)
boolean postAtTime(Runnable r, long uptimeMillis)
boolean postAtTime(Runnable r, Object token, long uptimeMillis)
boolean postDelayed(Runnable r, long delayMillis)
boolean postDelayed(Runnable r, Object token, long delayMillis)

作者:__Y_Q
链接:https://www.jianshu.com/p/83e037ef5fc2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

haldler工作流程
在这里插入图片描述
handler的主要函数:
在这里插入图片描述
一、Handler发送Message
1.往队列里面插入数据
在handler中有一个优先级队列MessageQueue,里面时按照时间先后顺序来进行排列的。时间为系统开机运行时间加上用户所要求的延迟时间。即
when = SystemClock.uptimeMillis() + delayMillis
SystemClock.uptimeMillis()是获取系统从开机启动到现在的时间,期间不包括休眠的时间,这里获得到的时间是一个相对的时间,而不是通过获取当前的时间(绝对时间)。
在这里插入图片描述
代码如下:
handler发送message

handler.sendMessage(message);
 //位于frameworks\base\core\java\android\os\Handler.java
 public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }
    
public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

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);
    }
/*sendMessageAtTime把的MessageQueue对象判断一下为不为空,为空就是一个异常。那么MessageQueue在哪来的呢?就是之前构造方法里创建的。*/
public Handler(Callback callback, boolean async) {
  
           ...这里是删了一些代码的地方
       
 
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; //MessageQueue是在Looper类里拿的
        mCallback = callback;
        mAsynchronous = async;
    }

//又回到了sendMssagerAtTime方法,讲过的就不说了。它调用了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {          
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
/*enqueueMessage这方法中msg.target = this;this就是Handler啦.target是标记的意思吧。翻译目标(目标),也就是说它把Handler关联起来了。*/

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

 //位于\frameworks\base\core\java\android\os\MessageQueue.java中
 boolean enqueueMessage(Message msg, long when) {
        //第一步
        //判断 msg 的 target 变量是否为 null
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //第二步
        //接着判断 msg 的标记为, 因为此时的 Message 是要入队, 意味着 msg 的标记位应该是未被使用. 如果显示已使用, 则抛出异常
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //第三步
        //加入同步锁
        synchronized (this) {
            //第四步
            //判断消息队列是否正在关闭, 如果是, 直接返回 false, 表示消息入队失败. 并且回收消息.
            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 的发送时间, 以及设置 msg 正在使用的标记位. 接着取出消息链表中头部元素赋值给         p
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //第六步
            //p == null 说明当前要入队的消息是第一个消息. when == 0 表示立即执行. when < p.when 表示当前要入队 msg 的执行时间要早于消息链表头部元素的执行时间. 所以这三个条件, 无论哪个条件成立, 都会把当前要入队的 msg 设置为消息链表的头部.
            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 {
                //第八步
                /*如果头部元素是障栅或者是异步消息,而且还是插入中间位置,我们不会唤醒
mBlocked 这里先认为是 false, 它的赋值是在 MessageQueue.next() 方法中 , 如果当前消息链表没有消息, 或者未到执行时间, 并且也没有可执行的空闲消息的时候, 会被赋值为 true, 并且消息队列开始阻塞, 当有可以立即执行的消息的时候会被赋值为 false。p.target == null 表示头部元素是一个障栅。msg.isAsynchronous() 当前要入队的消息是一个异步消息.*/
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //第九步
                //不断遍历消息队列, 根据 when 的比较找到合适的插入 message 的位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    //第十步
                    //消息指针移动过后, p == null, 说明没有下一个元素了, when < p.when 则说明要入队的这个消息的执行时间是小于当前 p 指向消息的执行时间的. 这两个条件有一个成立说明现在这个位置适合插入要入队的消息, 然后就跳出死循环
                    if (p == null || when < p.when) {
                        break;
                    }
                    //第十一步
                    //若第 10 步没有跳出循环, 并且执行到了 11 步, 那么说明没有满足条件, 队列中还有消息, 不需要唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //第十二步
                //跳出循环,做了两件事
                //1.将要入队的这个消息的 next 指向循环中获取到应该排在这个入队消息之后的 Message
                //2.将要入队的这个消息前面消息的 next 指向自己. 这样就完成了入队操作
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 第十三步
            //是否要唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

send 方案的最后, 都是会调用 enqueueMessage() 入队方法来完成消息的入队.

二.、post 方案
2.1 boolean post(Runnable r)

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

将一个 Runnable 添加到消息队列中, 这个 Runnable 将会在和当前 Handler 关联的线程中执行.

  • 这个方法内部简单, 就是调用了 sendMessageDelayed() 这个方法, 所以可见 boolean post(Runnable r) 这个方法最终还是走到上面说的 send 方案的流程中. 最后还是调用 enqueueMessage() 入队方案.

3.1 Message getPostMessage(Runnable r)

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

从消息对象池中获取一个空的 Message
2.将空 Message 的 callback 指向 Runnable (这个 Callback 是 Message 中的 Runnable)
3.最后返回这个 Message
由此我们可以得知 boolean post(Runnable r) 方法的内部也是通过 Message.obtain 来获取一个 Message 对象. 然后仅仅只是把 Message 对象的 Runnable callback 赋值而已. 最后还是调用了 send 方案的某个流程最终调用到入队方法.

后面剩余的 post 方法依旧如此, 只是调用的是不同的 send 方案中的方法.

2.怎么从队列中取消息
消息的取出主要是通过 Looper 的 loop 方法. 这个方法在第三章的时候已经分析过, 分为以下几步:
获取Looper对象
获取MessageQueue消息队列对象
死循环遍历
通过queue.next()来从MessageQueue的消息队列中获取一个Message msg对象
通过msg.target,dispatchMessage(msg)来处理消息
通过msg.recycleUnchecked() 方法来回收Message到消息对象池中.

下面分析一下

//位于frameworks\base\core\java\android\os\Looper.java
    public static void loop() {
        //获取当前线程 ThreadLocal 存储的 Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取当前 Looper 持有的消息队列
        final MessageQueue queue = me.mQueue;
        //确保权限检查基于本地进程, 而不是基于最初调用进程
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
		//进入 loop 主循环方法
    	//一个死循环, 不停的处理消息队列中的消息,消息的获取是通过 MessageQueue 的 next 方法
        for (;;) {
            //可能会阻塞
            Message msg = queue.next(); // might block
            //注: 这里的 msg = null, 并不是说没消息了, 而是如果消息队列正在关闭的情况下, 会返回 null.
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            //用于分发消息,调用 Message 的 target 变量(也就是 Handler)的 dispatchMessage方法处理消息
            msg.target.dispatchMessage(msg);
            ...
            //将 Message 回收标记后放入消息池
            msg.recycleUnchecked();
        }
    }

//位于frameworks\base\core\java\android\os\MessageQueue.java
Message next() {
        //如果消息循环已经退出了, 则在这里直接 return, 因为调用了 disposed()方法后, mPtr = 0
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //记录空闲时间处理的 IdleHandler 的数量. 初始为 -1
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        //native层需要用到的变量. 初始化为 0, 如果大于 0, 表示还有消息待处理(未到执行时间). -1表示阻塞等待
        int nextPollTimeoutMillis = 0;
        for (;;) {
        //如果还有消息未处理, 就刷新 Binder 命令, 一般在阻塞前调用
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//调用 native 方法, 当 nextPollTimeoutMillis == -1 的时候就阻塞等待, 直到下一条消息可用为止. 否则就继续向下执行. 还记得发送消息时候消息入队操作函数enqueueMessage的最后吗? 里面有一个 nativeWake() 唤醒. 就是唤醒此处. 没有消息的时候, 这里就处于阻塞状态. 当我们发送消息的时候, 这里就会被唤醒
            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;
                //判断第一个消息是不是障栅. (在前面第五篇中说过: 只有障栅的 tatget 才为 null), 如果第一个消息是障栅, 则又开启一个循环, 取出第一个异步消息, 从 do..while 这段代码中. 可以印证出障栅会拦截所有的同步消息.如果 msg != null && ! msg.isAsynchronous() 这个条件成立, 说明就是同步消息, 那么就跳出同步消息继续循环, 直到找到第一条异步消息并赋值给就退出 do..while 循环.
                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 {
                        // 如果当前时间大于等于消息的执行时间, 表示当前消息的执行时间已经到了, 接着将 MessageQueue.mBlocked 设置为 false 表示 MessageQueue 不阻塞, mBlocked 变量与消息入队时,表示需不需要唤醒
                        mBlocked = false;
          //这个 if..esle.. 判断内的逻辑就是将需要立刻执行的消息从消息队列中抽出来, 然后再将消息队列组合起来. 再将要执行消息的 next 赋值为 null,并标记为正在使用. 最后把要执行的消息返回出去. 获取消息结束.例如: 消息链表中有三个消息 A -> B -> C, A是障栅, B是异步, C是同步. 分析 12 走完, 就变成了, B 是单独的一个消息, 并将 B.next 置为 null, 最后组合后的消息链表就为 A -> C.
                        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 {
                   //如果msg == null, 则表示目前没有可执行的消息, 设置 nextPollTimeoutMillis = -1 . 在前面中说过这个变量的作用表示是否阻塞
                    nextPollTimeoutMillis = -1;
                }

                //在已处理所有挂起的消息,请处理退出消息。需要关闭消息队列, 返回 null, 通知 Looper 停止循环
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //当第一次循环才会在空闲的时候去执行 IdleHanler, 从代码可以看出所谓的空闲状态指的就是, 目前没有任何可执行的 Message, 这里的可执行有两个要求, 当前 Message 不会被障栅拦截, 当前 Message 到达了执行时间. 才会为变量 pendingIdleHandlerCount 赋值.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //如果没有在空闲时需要执行的 IdleHandler. 这里是消息队列阻塞(死循环)的重点, 在 msg = null 或者未到执行时间的情况下, 表示消息队列空闲, 但是也没有可执行的 idleHandler, 那么就把 mBlock 变量置为 true, 表示需要唤醒, 并开始下一次循环.  这个时候 nextPollTimeoutMillis 要么为 -1, 要么就为上个消息剩下要执行的时间.  接着刷新 binder 命令, 然后在就开始阻塞, 只要不是 0, 就会阻塞. 等要执行的时间到了就会被唤醒. 或者当有新的消息入队的时候. 就会根据 mBlock 的值来判断是否要唤醒消息队列.
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
//如果有需要在空闲时执行的 IdleHandler, 接着判断是否初始化过 mPendingIdleHandlers 数组, 最小4 个长度. 并把要执行的 IdleHandler 赋值给 mPendingIdleHandlers 数组.
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //执行 idleHandler. 我们只会在第一次迭代时到达此代码块。为什么呢, 这个稍后分析
            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);
                    }
                }
            }

            //IdHandler 只会在消息队列阻塞之前执行一次, 执行之后, pendingIdleHandlerCount 赋值为 0, 之后就不会再执行. 一直到下一次调用 MessageQueue.next() 方法.

            pendingIdleHandlerCount = 0;

            //当执行了 IdleHander 之后, 会消耗一段时间, 这时候消息队列里可能已经有消息到达可执行时间, 所以重置 nextPollTimeoutMillis 回去重新检查消息队列.
            nextPollTimeoutMillis = 0;
        }
    }

总结
总的来说当在 Looper.loop() 方法的死循环内, 调用MessageQueue.next() 方法获取一个 Message 的时候, 大致会分为以下几步.

  • MessageQueue 会先判断队列中是否有障栅存在,有: 返回第一个异步消息;没有: 逐个返回同步消息
  • 当 MessageQueue 中没有任何消息可以处理或者未到消息的执行时间的时候, 就会进入阻塞状态等待新的消息到来被唤醒. 或者有消息的执行时间到了被唤醒. 在阻塞之前会执行一次 IdleHandler
  • 当 MessageQueue 被关闭的时候, 成员变量 mQutting 会被标记为 true, 然后在 Looper 试图从 MessageQueue 取消息的时候返回 null. 而 Message = null就是告诉 Looper 消息队列已经关闭, 应该停止死循环了.(在Looper.loop() 方法分析中有说明 )
  • Handler 中实际上有两个无限循环体, 一个是在 Looper.loop() 中的循环体, 以及 MessageQueue 中的循环体. 真正的阻塞是在 MessageQueue 的循环体中。

消息的分发处理

  1. Handler.dispatchMessage(Message msg)
    在 Looper.loop() 方法内调用. 先从 MessageQueue 中获取到一个消息后, 再调用这个方法传入获取的消息进行分发处理.
//位于\frameworks\base\core\java\android\os\Handler.java
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //当 Message 存在回调方法, 回调 msg.callback.run() 方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //当创建 Handler 时, 传入的参数为 Callback 的时候, , 回调方法为 Handler.Callback.handleMessage()
            //在前面(https://blog.csdn.net/qq_34888036/article/details/119338205)有分析到这个 Callback.
            //如果我们实现的 Callback 接口中 handleMessage 返回 true, 则表示不会调用 Handler 自身的 HandleMessage() 方法.
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //Handler 自身的回调方法 handlerMessage()
        handleMessage(msg);
    }
}

在分发消息三个方法啊的优先级

Message 的回调方法优先级最高. 既 message.callback.run()
Handler 的回调方法次之, 既 Handler.Callback.handleMessage
Handler 的默认方法优先级最低
很多情况下, 消息分发后的处理都是第三种情况, 一般往往都是通过复写第三种方法, 从而实现自己的业务逻辑.

Message中obtain()与recycle()

在obtain的所有重载方法中,第一行都是Message m = 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();
}

很明显,这是个同步方法,sPoolSync即锁对象,该对象在定义时即被初始化private static final Object sPoolSync = new Object();,随后便只读不写。
然后便是sPool,后面还有Message m = sPool;sPool = m.next;,很明显可以看出来,这是一个链表结构。sPool指向当前message,next指向下一个message。
在解释这段代码前,需要先明确两点:sPool声明为private static Message sPool;;next声明为/package/ Message next;。即前者为该类所有示例共享,后者则每个实例都有。
假设该链表初始状态如下
在这里插入图片描述
执行Message m = sPool;就变成下图
在这里插入图片描述
继续sPool = m.next;
在这里插入图片描述
然后m.next = null;
在这里插入图片描述
接下来m.flags=0;sPoolSize–;return m;便是表示m指向的对象已经从链表中取出并返回了。

回收recycle()

然后再看看sPoolSize是什么时候自增的。按图索骥便可找到recycle()方法和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 = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

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

前半段不必多说,显然是“重置”改对象的个个字段。后半段又是一个同步代码段,同样用图来解释一下(假设当前代码为message.recycle(),则需要被回收的则是message对象)。
假设当前链表如下:
在这里插入图片描述
执行next=sPool;
在这里插入图片描述
执行sPool=this;
在这里插入图片描述
现在可以很清楚的看到,Message类本身就组织了一个栈结构的缓冲池。并使用obtain()方法和recycler()方法来取出和放入。

以下几个message问题:

  • messageQueue有没容量限制的?
    没有大小限制;如果用message 缓存池的话,会限制大小50;如果用new meaasge的方法构建message,没有大小限制

  • 如何创建Message对象;
    Message对象可以通过new关键字,也可以通过obtain方法进行创建;
    为什么选用obtain方法呢?
    android app中会有大量需要通过handler发送消息,会发送大量的message对象;
    为了避免频繁的创建和销毁message对象所造成的性能消耗,所以使用“缓存池”。

sendMessage

package cn.vn.hand;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity implements OnClickListener {
    private final String TAG = "MainActivity";
    private TextView mTipTv;
    private Button mDownloadBt;
    private boolean isDownloading = false;
    public final int MSG_DOWN_FAIL = 1;
    public final int MSG_DOWN_SUCCESS = 2;
    public final int MSG_DOWN_START = 3;
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what){
            case MSG_DOWN_FAIL:
                mTipTv.setText("download fial");
                break;
            case MSG_DOWN_SUCCESS:
                mTipTv.setText("download success");
                break;
            case MSG_DOWN_START:
                mTipTv.setText("download start");
                break;
            }
        };
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
 
    private void initView() {
        mTipTv = (TextView) findViewById(R.id.tv_tip);
        mDownloadBt = (Button) findViewById(R.id.bt_start);
        mDownloadBt.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.bt_start:
            if(!isDownloading){
                new MyThread().start();
            }
            break;
        default:
            break;
        }
    }
 
    class MyThread extends Thread {
        @Override
        public void run() {
            isDownloading = true;
            Log.d(TAG,"MyThread start run");
            //发送消息给mHander
            mHandler.sendEmptyMessage(MSG_DOWN_START);
            try { //让线程睡眠3s。
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message msg = new Message();
            msg.what = MSG_DOWN_SUCCESS;
            //msg.arg1 = 111;  可以设置arg1、arg2、obj等参数,传递这些数据
            //msg.arg2 = 222; msg.obj = obj;
            mHandler.sendMessage(msg);
            isDownloading = false;
            Log.d(TAG,"MyThread stop run");
        }
    }
}

使用步骤:

1.在UI线程中创建handler对象mHandler,并实现handleMessage方法,根据Message的what值进行不同的处理操作。
2.创建Message对象
3.根据需要设置Message的参数,Message.what一般都是必要的,用来区分不同的Message,做出不同的操作。还可以设置Message两个int型字段arg1、arg2。当然除了这简单的数据外,还可以设置携带复杂数据,其obj字段类型为Object类型,可以为任意类类型的数据。也可以通过Message的setData方法设置Bundle类型的数据,可以通过getData方法获取该Bundle数据。
4.mHandler.sendMessage(Message)方法将Message传入Handler中的消息队列中,然后handleMessage中对消息进行处理。

创建Handler的线程和其handleMessage运行的线程是同一线程,mHandler是在主线程中创建的,所以其handleMessage方法也是在主线程中运行。mHandler.sendMessage(Message)可以在主线程中也可以在子线程中,发送消息的线程与其执行的线程没有联系,最终都会在其创建的线程中处理这些消息。
sendMessage还有许多变形,可以发送空message(只携带what参数)、延时消息、定时消息等。使用方式很简单。
在这里插入图片描述
对于延时、定时消息,有时我们可能会想取消消息,这就可以通过removeMessages(int what)、或removeMessages(int what, Object object)、removeCallbacksAndMessages(Object token)将指定消息移除。

post

package cn.vn.hand;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity implements OnClickListener {
    private final String TAG = "MainActivity";
    private TextView mTipTv;
    private Button mDownloadBt;
    private boolean isDownloading = false;
    public final int MSG_DOWN_FAIL = 1;
    public final int MSG_DOWN_SUCCESS = 2;
    public final int MSG_DOWN_START = 3;
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            Log.d(TAG, "handlemessage what="+msg.what);
            switch(msg.what){
            case MSG_DOWN_FAIL:
                mTipTv.setText("download fial");
                break;
            case MSG_DOWN_SUCCESS:
                mTipTv.setText("download success");
                break;
            case MSG_DOWN_START:
                mTipTv.setText("download start");
                break;
            }
        };
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }
 
    private void initView() {
        mTipTv = (TextView) findViewById(R.id.tv_tip);
        mDownloadBt = (Button) findViewById(R.id.bt_start);
        mDownloadBt.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.bt_start:
            if(!isDownloading){
                //new MyThread().start();
                new postThread().start();
            }
            break;
        default:
            break;
        }
    }
 
    class postThread extends Thread{
        @Override
        public void run() {
            isDownloading = true;
            Log.d(TAG,"run threadid="+Thread.currentThread().getId()+
                    ",name="+Thread.currentThread().getName());
            try { //让线程睡眠3s。
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "Runnable threadid="+Thread.currentThread().getId()
                            +",name="+Thread.currentThread().getName());
                    //更新ui
                    mTipTv.setText("download success");
                }
            });
            isDownloading = false;
        }
    }
}

Handler的post方法参数为Runnable对象,mHandler是在主线程中创建的,所以Runnalbe会在主线中运行(与Runnable创建的线程无关、与mHandler.post方法调用的线程无关)
post方法与sendMessage类似也有多个相似方法:
在这里插入图片描述
post延时、定时处理Runnable也可以进行取消,可以通过removeCallbacks(Runnable r)、removeCallbacks(Runnable r, Object token)、removeCallbacksAndMessages(Object token)方法进行取消。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值