当前位置: 首页 > news >正文

三剑客 Handler、Looper 和 MessageQueue

Handler

可以用 Handler 发送处理与某线程的 MessageQueue 相关联的 Message/Runnable 对象。每个 Handler 实例只能与一个线程和它的消息队列相关联(创建 Handler 时并不一定是绑定到当前线程。)。Handler 将 Message 和 Runnable 传递给绑定的消息队列,并在它们从队列里被取出时执行对应逻辑。

Handler 主要有两个用途:

  • 在未来某个时间点处理 Messages 或者执行 Runnables;
  • 将一段逻辑切换到另一个线程执行。

可以使用 Handler 的以下方法来调度 Messages 和 Runnables:

  • post(Runnable)
  • postAtTime(Runnable, long)
  • postDelayed(Runnable, Object, long)
  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message, long)
  • sendMessageDelayed(Message, long)

其中 postXXX 系列用于将 Runnable 对象加入队列,sendXXX 系列用于将 Message 对象加入队列,Message 对象通常会携带一些数据,可以在 Handler 的 handlerMessage(Message) 方法中处理(需要实现一个 Handler 子类)。

在调用 Handler 的 postXXX 和 sendXXX 时,可以指定当队列准备好时立即处理它们,也可以指定延时一段时间后处理,或某个绝对时间点处理。后面这两种能实现超时、延时、周期循环及其它基于时间的行为。

为应用程序创建一个进程时,其主线程专用于运行消息队列,该消息队列负责管理顶层应用程序对象(activities,broadcast receivers 等)以及它们创建的窗口。我们可以在主线程创建Handler,然后创建自己的线程,然后通过 Handler 与主线程进行通信,方法是从新线程调用我们前面讲到的 postXXX 或 sendXXX 方法,传递的 Runnable 或 Message 将被加入 Handler 关联的消息队列中,并适时进行处理。

Looper

用于为线程执行消息循环的类。线程默认没有关联的消息循环,如果要创建一个,可以在执行消息循环的线程里面调用 prepare() 方法,然后调用 loop() 处理消息,直到循环停止。

大多数与消息循环的交互都是通过 Handler 类。

下面是实现一个 Looper 线程的典型例子,在 prepare() 和 loop() 之间初始化 Handler 实例,用于与 Looper 通信:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // 在这里处理传入的消息
            }
        };

        Looper.loop();
    }
}
复制代码

MessageQueue

持有将被 Looper 分发的消息列表的底层类。消息都是通过与 Looper 关联的 Handler 添加到 MessageQueue,而不是直接操作 MessageQueue。

可以用 Looper.myQueue() 获取当前线程的 MessageQueue 实例。

Message

定义一个可以发送给 Handler 的消息,包含描述和任意数据对象。消息对象有两个额外的 int 字段和一个 object 字段,这可以满足大部分场景的需求了。

虽然 Message 的构造方法是 public 的,但最推荐的得到一个消息对象的方式是调用 Message.obtain() 或者 Handler.obtainMessage() 系列方法,这些方法会从一个对象回收池里捡回能复用的对象。

Thread 与 Looper

线程默认是没有消息循环的,需要调用 Looper.prepare() 来达到目的,那么我们对这个问题的探索就从 Looper.prepare() 开始。

/** Initialize the current thread as a looper.
 * This gives you a chance to create handlers that then reference
 * this looper, before actually starting the loop. Be sure to call
 * {@link #loop()} after calling this method, and end it by calling
 * {@link #quit()}.
 */
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));
}
复制代码

在有参数版本的 prepare 方法里,我们可以得到两个信息:

  • 一个线程里调用多次 Looper.prepare() 会抛出异常,提示 Only one Looper may be created per thread,即 一个线程只能创建一个 Looper。
  • prepare 里主要干的事就是 sThreadLocal.set(new Looper(quitAllowed))。

源码里是怎么限制一个线程只能创建一个 Looper 的呢?调用多次 Looper.prepare() 并不会关联多个 Looper,还会抛出异常,那能不能直接 new 一个 Looper 关联上呢?答案是不可以,Looper 的构造方法是 private 的。

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

在概览整个 Looper 的所有公开方法后,发现只有 prepare 和 prepareMainLooper 是做线程与 Looper 关联的工作的,而 prepareMainLooper 是 Android 环境调用的,不是用来给应用主动调用的。所以从 Looper 源码里掌握的信息来看,想给一个线程关联多个 Looper 的路不通。

另外我们从源码里能观察到,Looper 有一个 final 的 mThread 成员,在构造 Looper 对象的时候赋值为 Thread.currentThread(),源码里再无可以修改 mThread 值的地方,所以可知 Looper 只能关联到一个线程,且关联之后不能改变。

说了这么多,还记得 Looper.prepare() 里干的主要事情是 sThreadLocal.set(new Looper(quitAllowed)) 吗?与之对应的,获取本线程关联的 Looper 对象是使用静态方法 Looper.myLooper():

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

// ...

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

// ...

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
复制代码

使用了 ThreadLocal 来确保不同的线程调用静态方法 Looper.myLooper() 获取到的是与各自线程关联的 Looper 对象。

小结: Thread 若与 Looper 关联,将会是一一对应的关系,且关联后关系无法改变。

Looper 与 MessageQueue

public final class Looper {
    // ...
    final MessageQueue mQueue;

    // ...

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

Looper 对象里有一个 MessageQueue 类型成员,在构造的时候 new 出的,并且它是一个 final,没有地方能修改它的指向。

小结: Looper 与 MessageQueue 是一一对应的关系。

Handler 与 Looper

public class Handler {
    // ...
    
    /**
     * ...
     * @hide
     */
    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;
        // ...
    }

    /**
     * ...
     * @hide
     */
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = mLooper.mQueue;
        // ...
    }

    // ...

    final Looper mLooper;
    final MessageQueue mQueue;
    // ...
}
复制代码

Handler 对象里有 final Looper 成员,所以一个 Handler 只会对应一个固定的 Looper 对象。构造 Handler 对象的时候如果不传 Looper 参数,会默认使用当前线程关联的 Looper,如果当前线程没有关联 Looper,会抛出异常。

那么能不能绑定多个 Handler 到同一个 Looper 呢?答案是可以的。例如以下例子,就绑定了两个 Handler 到主线程的 Looper 上,并都能正常使用(日志 receive msg: 1 和 receive msg: 2 能依次输出)。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private Handler mHandler1;
    private Handler mHandler2;

    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.v(TAG, "receive msg: " + msg.what);
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler1 = new Handler(mCallback);
        mHandler2 = new Handler(mCallback);

        mHandler1.sendEmptyMessage(1);
        mHandler2.sendEmptyMessage(2);
    }
}
复制代码

小结: Handler 与 Looper 是多对一的关系,创建 Handler 实例时要么提供一个 Looper 实例,要么当前线程有关联的 Looper。

消息如何分发到对应的 Handler

消息的分发在是 Looper.loop() 这个过程中:

public static void loop() {
    // ...
    for (;;) {
        Message msg = queue.next(); // might block
        // ...
        try {
            msg.target.dispatchMessage(msg);
            // ...
        } finally {
            // ...
        }
        // ...
    }
}
复制代码

这个方法里做的主要工作是从 MessageQueue 里依次取出 Message,然后调用 Message.target.dispatchMessage 方法,Message 对象的这个 target 成员是一个 Handler,它最终会被设置成 sendMessage 的 Handler:

public class Handler {
    // 其它 Handler.sendXXX 方法最终都会调用到这个方法
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // ...
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    // ...
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; // 就是这里了
        // ...
    }
    // ...
}
复制代码

所以是用哪个 Handler.sendMessage,最终就会调用到它的 dispatchMessage 方法:

private static void handleCallback(Message message) {
    message.callback.run();
}
// ...
/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
复制代码

消息分发到这个方法以后,执行优先级分别是 Message.callback、Handler.mCallback,最后才是 Handler.handleMesage 方法。

小结: 在 Handler.sendMessage 时,会将 Message.target 设置为该 Handler 对象,这样从消息队列取出 Message 后,就能调用到该 Handler 的 dispatchMessage 方法来进行处理。

Handler 能用于线程切换的原理

小结: Handler 会对应一个 Looper 和 MessageQueue,而 Looper 与线程又一一对应,所以通过 Handler.sendXXX 和 Hanler.postXXX 添加到 MessageQueue 的 Message,会在这个对应的线程的 Looper.loop() 里取出来,并就地执行 Handler.dispatchMessage,这就可以完成线程切换了。

Runnable 与 MessageQueue

Handler 的 postXXX 系列方法用于调度 Runnable 对象,那它最后也是和 Message 一样被加到 MessageQueue 的吗?可是 MessageQueue 是用一个元素类型为 Message 的链表来维护消息队列的,类型不匹配。

在 Handler 源码里能找到答案,这里就以 Handler.post(Runnable) 方法为例,其它几个 postXXX 方法情形与此类似。

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is 
 * attached. 
 *  
 * @param r The Runnable that will be executed.
 * 
 * @return Returns true if the Runnable was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean post(Runnable r)
{
    return  sendMessageDelayed(getPostMessage(r), 0);
}

// ...

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

可以看到,post 系列方法最终也是调用的 send 系列方法,Runnable 对象是被封装成 Message 对象后加入到消息队列的,Message.callback 被设置为 Runnable 本身。如果 Message.callback 不为空,则执行 Message.callback.run() 后就返回。

小结: Runnable 被封装成 Message 之后添加到 MessageQueue。

能否创建关联到其它线程的 Handler

创建 Handler 时会关联到一个 Looper,而 Looper 是与线程一一绑定的,所以理论上讲,如果能得到要关联的线程的 Looper 实例,这是可以实现的。

public final class Looper {
    // ...
    private static Looper sMainLooper;  // guarded by Looper.class
    // ...
    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
}
复制代码

可见获取主线程的 Looper 是能实现的,平时写代码过程中,如果要从子线程向主线程添加一段执行逻辑,也经常这么干,这是可行的:

// 从子线程创建关联到主线程 Looper 的 Handler
Handler mHandler = new Handler(Looper.getMainLooper());

mHandler.post(() -> {
        // ...
        });
复制代码

从子线程创建关联到其它子线程的 Looper :

new Thread() {
    @Override
    public void run() {
        setName("thread-one");
        Looper.prepare();

        final Looper threadOneLooper = Looper.myLooper();

        new Thread() {
            @Override
            public void run() {
                setName("thread-two");
                Handler handler = new Handler(threadOneLooper);

                handler.post(() -> {
                        Log.v("test", Thread.currentThread().getName());
                        });
            }
        }.start();

        Looper.loop();
    }
}.start();
复制代码

执行后日志输出为 thread-one。

小结: 可以从一个线程创建关联到另一个线程 Looper 的 Handler,只要能拿到对应线程的 Looper 实例。

消息可以插队吗

答案是可以的,使用 Handler.sendMessageAtFrontOfQueue 和 Handler.postAtFrontOfQueue 这两个方法,它们会分别将 Message 和 Runnable(封装后)插入到消息队列的队首。

小结: 消息可以插队,使用 Handler.xxxAtFrontOfQueue 方法。

消息可以撤回吗

可以用 Handler.hasXXX 系列方法判断关联的消息队列里是否有等待中的符合条件的 Message 和 Runnable,用 Handler.removeXXX 系列方法从消息队列里移除等待中的符合条件的 Message 和 Runnable。

小结: 尚未分发的消息是可以撤回的,处理过的就没法了。

找到主线程消息循环源码

Looper.prepareMainLooper 是 Android 环境调用的,调用它就是为了初始化主线程 Looper。

public final class ActivityThread {
    public static void main(String[] args) {
        // ...
        Looper.prepareMainLooper();
        // ...
        Looper.loop();
        // ...
    }
}
复制代码

MessageQueue.next()

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // 调用JNI函数Poll消息。nextPollTimeoutMillis是消息队列中没消息时的等待时间。
        // (01) nextPollTimeoutMillis = 0,不等待。
        // (02) nextPollTimeoutMillis = -1,无限等待。
        nativePollOnce(mPtr, nextPollTimeoutMillis);

        synchronized (this) {
            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 {
                    // 如果消息队列中有消息,并且当前时间大于/等于消息中的执行时间,
                    // 则将该消息返回给Looper。
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (false) Log.v("MessageQueue", "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果消息队列中无消息,则设置nextPollTimeoutMillis=-1;
                // 下次调用nativePollOnce()时,则会进入无穷等待状态。
                nextPollTimeoutMillis = -1;
            }

            // 如主线程调用的quit()函数,则退出消息循环。
            if (mQuitting) {
                dispose();
                return null;
            }

            // 查看空闲等待(不是忙等待)对应的pendingIdleHandlerCount数量。
            // 如果pendingIdleHandlerCount=0,则继续下一次循环。
            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)];
            }
            // 将mIdleHandlers转换位数组
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 执行mPendingIdleHandlers中每一个IdleHandler的queueIdle(),
        // 即,进行空闲等待。
        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("MessageQueue", "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;
    }
}
复制代码

next()的作用是获取消息队列的下一条待处理消息。方法中使用了 SystemClock.uptimeMillis() 方法获取了当前的时间。

Android 中的时间

System.currentTimeMillis()

我们一般通过它来获取手机系统的当前时间。事实上,它返回的值是系统时刻距离标准时刻(1970.01.01 00:00:00)的毫秒数。它相当于家里的“挂钟”一样,并不是十分精准,而且可以随意修改。所以它可能经常被网络或者用户校准。正是由于这个原因,这个方法获取的值不适合用来做时间间隔的统计。但是它适合用来获取当前日期,时刻等时间点相关的逻辑。

SystemClock.upTimeMillis()

这个值记录了系统启动到当前时刻经过的时间。但是系统深度睡眠(CPU睡眠,黑屏,系统等待唤醒)之中的时间不算在内。这个值不受系统时间设置,电源策略等因素的影响,因此它是大多数时间间隔统计的基础,例如Thread.sleep(long millis),Object.wait(long millis),System.nanoTime()等。系统保证了这个值只增长不下降,所以它适合所有的不包括系统睡眠时间的时间间隔统计。

SystemClock.elapsedRealtime() & SystemClock.elapsedRealtimeNanos()

这个值与SystemClock.upTimeMillis()类似。它是系统启动到当前时刻经过的时间,包括了系统睡眠经过的时间。在CPU休眠之后,它依然保持增长。所以它适合做更加广泛通用的时间间隔的统计。

小结

如果想要避免用户修改时间,网络校准时间对时间间隔统计的影响,使用SystemClock类相关的方法就可以了,至于选择upTimeMillis()还是elapsedRealtime()就要根据自己的需求确定了。 ##系统还提供了几个时间控制相关的工具:

  • 标准方法Thread.sleep(long millis) 和 Object.wait(long millis)是基于SystemClock.upTimeMillis()的。所以在系统休眠之后它们的回调也会延期,直到系统被唤醒才继续计时。并且这两个同步方法会响应InterruptException,所以在使用它们的时候必须要处理InterruptException异常。
  • SystemClock.sleep(long millis) 与 Thread.sleep(long millis) 方法是类似的,只不过SystemClock.sleep(long millis) 不响应InterruptException异常。
  • Handler类的 postDelay()方法也是基于SystemClock.upTimeMillis()方法的。
  • AlarmManager可以定时发送消息,即使在系统睡眠、应用停止的状态下也可以发送。我们在创建定时事件的时候有两个参数可以选择RTC和ELAPSED_REALTIME,它们对应的方法就是System.currentTimeMillis() ~ RTC,SystemClock.elapsedRealtime() ~ ELAPSED_REALTIME。这样一对应,它们的区别也就非常明显了。

相关文章:

  • 使用brew安装MySQL
  • Log4j学习总结(1)——快速入门教程
  • CMDB基于配置文件加载插件的范例
  • RabbitMQ学习总结(2)——安装、配置与监控
  • 它承受着该等级不该有的简单, leetcode 564 寻找最近的回文数
  • Bean注入
  • HashTable详解
  • 《Fluid Engine Development》 学习笔记3-光滑粒子流体动力学
  • Express 相关整合
  • Set集合学习
  • redis与lua
  • 并发数和TPS的理解
  • java 过滤list的几种方式
  • java中的重载(overload)和重写(override)区别
  • 这是一套Java菜鸟到大牛的学习路线之高级教程,由工作了10年的资深Java架构师整理。...
  • 【node学习】协程
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • const let
  • dva中组件的懒加载
  • exports和module.exports
  • gcc介绍及安装
  • iOS | NSProxy
  • Java多线程(4):使用线程池执行定时任务
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • node学习系列之简单文件上传
  • PAT A1017 优先队列
  • SegmentFault 2015 Top Rank
  • 半理解系列--Promise的进化史
  • 仿天猫超市收藏抛物线动画工具库
  • 两列自适应布局方案整理
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 前端js -- this指向总结。
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 与 ConTeXt MkIV 官方文档的接驳
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 说说我为什么看好Spring Cloud Alibaba
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • ​香农与信息论三大定律
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (一)认识微服务
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)大型网站的系统架构
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .NET运行机制
  • 。Net下Windows服务程序开发疑惑
  • @Repository 注解
  • [2018-01-08] Python强化周的第一天
  • [C#7] 1.Tuples(元组)
  • [C/C++] C/C++中数字与字符串之间的转换
  • [ESP32 IDF]web server