记得很多年前的一次面试中,面试官问了这么一个问题,你在项目中一般如何实现线程切换? 他的本意应该是考察 RxJava 的使用,只是我的答案是 Handler,他也就没有再追问下去了。在早期 Android 开发的荒芜时代,Handler 的确承担了项目中大部分的线程切换工作,通常包括子线程更新 UI 和消息传递。不光在我们自己的应用中,在整个 Android 体系中,Handler 消息机制也是极其重要的,不亚于 Binder 的地位。 ActivityThread.java 中的内部类 H 就是一个 Handler,它内部定义了几十种消息类型来处理一些系统事件。

Handler 的重要性毋庸置疑,今天就通过 AOSP 源码来深入学习 Handler。相关类的源码包含注释均已上传到我的 Github 仓库 android_9.0.0_r45 :

Handler.java

Looper.java

Message.java

MessageQueue.java

Handler

Handler 用来发送和处理线程对应的消息队列 MessageQueue 中存储的 Message。每个 Handler 实例对应一个线程以及该线程的消息队列。当你创建一个新的 Handler,它会绑定创建它的线程和消息队列,然后它会向消息队列发送 Message 或者 Runnable,并且在它们离开消息队列时执行。

Handler 有两个主要用途:

  1. 规划 Message 或者 Runnable 在未来的某个时间点执行
  2. 在另一个线程上执行代码

以上翻译自官方注释。说白了,Handler 只是安卓提供给开发者用来发送和处理事件的,而消息如何存储,消息如何循环取出,这些逻辑则交给 MessageQueueLooper 来处理,使用者并不需要关心。但要真正了解 Handler 消息机制,认真读一遍源码就必不可少了。

构造函数

Handler 的构造函数大致上可以分为两类,先来看第一类:

  1. public Handler() {
  2. this(null, false);
  3. }
  4. public Handler(Callback callback) {
  5. this(callback, false);
  6. }
  7. public Handler(Callback callback, boolean async) {
  8. // 如果是匿名类、内部类、本地类,且没有使用 static 修饰符,提示可能导致内存泄漏
  9. if (FIND_POTENTIAL_LEAKS) {
  10. final Class<? extends Handler> klass = getClass();
  11. if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
  12. (klass.getModifiers() & Modifier.STATIC) == 0) {
  13. Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
  14. klass.getCanonicalName());
  15. }
  16. }
  17. // 从当前线程的 ThreadLocal获取 Looper
  18. mLooper = Looper.myLooper();
  19. if (mLooper == null) { // 创建 Handler 之前一定要先创建 Looper。主线程已经自动为我们创建。
  20. throw new RuntimeException(
  21. "Can't create handler inside thread " + Thread.currentThread()
  22. + " that has not called Looper.prepare()");
  23. }
  24. mQueue = mLooper.mQueue; // Looper 持有一个 MessageQueue
  25. mCallback = callback; // handleMessage 回调
  26. mAsynchronous = async; // 是否异步处理
  27. }

这一类构造函数最终调用的都是两个参数的方法,参数中不传递 Looper,所以要显式检查是否已经创建 Looper。创建 Handler 之前一定要先创建 Looper,否则会直接抛出异常。在主线程中 Looper 已经自动创建好,无需我们手动创建,在 ActivityThread.javamain() 方法中可以看到。Looper 持有一个消息队列 MessageQueue,并赋值给 Handler 中的 mQueue 变量。Callback 是一个接口,定义如下:

  1. public interface Callback {
  2. public boolean handleMessage(Message msg);
  3. }

通过构造器参数传入 CallBack 也是 Handler 处理消息的一种实现方式。

再回头看一下在上面的构造函数中是如何获取当前线程的 Looper 的?

  1. mLooper = Looper.myLooper(); // 获取当前线程的 Looper

这里先记着,回头看到 Looper 源码时再详细解析。

看过 Handler 的第一类构造函数,第二类其实就很简单了,只是多了 Looper 参数而已:

  1. public Handler(Looper looper) {
  2. this(looper, null, false);
  3. }
  4. public Handler(Looper looper, Callback callback) {
  5. this(looper, callback, false);
  6. }
  7. public Handler(Looper looper, Callback callback, boolean async) {
  8. mLooper = looper;
  9. mQueue = looper.mQueue;
  10. mCallback = callback;
  11. mAsynchronous = async;
  12. }

直接赋值即可。

除此之外还有几个标记为 @hide 的构造函数就不作说明了。

发送消息

发送消息大家最熟悉的方法就是 sendMessage(Message msg) 了,可能有人不知道其实还有 post(Runnable r) 方法。虽然方法名称不一样,但最后调用的都是同一个方法。

  1. sendMessage(Message msg)
  2. sendEmptyMessage(int what)
  3. sendEmptyMessageDelayed(int what, long delayMillis)
  4. sendEmptyMessageAtTime(int what, long uptimeMillis)
  5. sendMessageAtTime(Message msg, long uptimeMillis)

几乎所有的 sendXXX() 最后调用的都是 sendMessageAtTime() 方法。

  1. post(Runnable r)
  2. postAtTime(Runnable r, long uptimeMillis)
  3. postAtTime(Runnable r, Object token, long uptimeMillis)
  4. postDelayed(Runnable r, long delayMillis)
  5. postDelayed(Runnable r, Object token, long delayMillis)

所有的 postXXX() 方法都是调用 getPostMessage() 将 参数中的 Runnable 包装成 Message,再调用对应的 sendXXX() 方法。看一下 getPostMessage() 的代码:

  1. private static Message getPostMessage(Runnable r) {
  2. Message m = Message.obtain();
  3. m.callback = r;
  4. return m;
  5. }
  6. private static Message getPostMessage(Runnable r, Object token) {
  7. Message m = Message.obtain();
  8. m.obj = token;
  9. m.callback = r;
  10. return m;
  11. }

主要是把参数中的 Runnable 赋给 Message 的 callback 属性。

殊途同归,发送消息的重任最后都落在了 sendMessageAtTime() 身上。

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2. MessageQueue queue = mQueue;
  3. if (queue == null) {
  4. RuntimeException e = new RuntimeException(
  5. this + " sendMessageAtTime() called with no mQueue");
  6. Log.w("Looper", e.getMessage(), e);
  7. return false;
  8. }
  9. return enqueueMessage(queue, msg, uptimeMillis);
  10. }
  11. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  12. msg.target = this;
  13. if (mAsynchronous) {
  14. msg.setAsynchronous(true);
  15. }
  16. return queue.enqueueMessage(msg, uptimeMillis); // 调用 Messagequeue 的 enqueueMessage() 方法
  17. }

Handler 就是一个撒手掌柜,发送消息的任务转手又交给了 MessageQueue 来处理。

再额外提一点,enqueueMessage() 方法中的参数 uptimeMillis 并不是我们传统意义上的时间戳,而是调用 SystemClock.updateMillis() 获取的,它表示自开机以来的毫秒数。

MessageQueue

enqueueMessage()

Message 的入队工作实际上是由 MessageQueue 通过 enqueueMessage() 函数来完成的。

  1. boolean enqueueMessage(Message msg, long when) {
  2. if (msg.target == null) { // msg 必须有 target
  3. throw new IllegalArgumentException("Message must have a target.");
  4. }
  5. if (msg.isInUse()) { // msg 不能正在被使用
  6. throw new IllegalStateException(msg + " This message is already in use.");
  7. }
  8. synchronized (this) {
  9. if (mQuitting) { // 正在退出,回收消息并直接返回
  10. IllegalStateException e = new IllegalStateException(
  11. msg.target + " sending message to a Handler on a dead thread");
  12. Log.w(TAG, e.getMessage(), e);
  13. msg.recycle();
  14. return false;
  15. }
  16. msg.markInUse();
  17. msg.when = when;
  18. Message p = mMessages;
  19. boolean needWake;
  20. if (p == null || when == 0 || when < p.when) {
  21. // New head, wake up the event queue if blocked.
  22. // 插入消息队列头部,需要唤醒队列
  23. msg.next = p;
  24. mMessages = msg;
  25. needWake = mBlocked;
  26. } else {
  27. // Inserted within the middle of the queue. Usually we don't have to wake
  28. // up the event queue unless there is a barrier at the head of the queue
  29. // and the message is the earliest asynchronous message in the queue.
  30. needWake = mBlocked && p.target == null && msg.isAsynchronous();
  31. Message prev;
  32. for (;;) {
  33. prev = p;
  34. p = p.next;
  35. if (p == null || when < p.when) { // 按消息的触发时间顺序插入队列
  36. break;
  37. }
  38. if (needWake && p.isAsynchronous()) {
  39. needWake = false;
  40. }
  41. }
  42. msg.next = p; // invariant: p == prev.next
  43. prev.next = msg;
  44. }
  45. // We can assume mPtr != 0 because mQuitting is false.
  46. if (needWake) {
  47. nativeWake(mPtr);
  48. }
  49. }
  50. return true;
  51. }

从源码中可以看出来,MessageQueue 是用链表结构来存储消息的,消息是按触发时间的顺序来插入的。

enqueueMessage() 方法是用来存消息的,既然存了,肯定就得取,这靠的是 next() 方法。

next()

  1. Message next() {
  2. // Return here if the message loop has already quit and been disposed.
  3. // This can happen if the application tries to restart a looper after quit
  4. // which is not supported.
  5. final long ptr = mPtr;
  6. if (ptr == 0) {
  7. return null;
  8. }
  9. int pendingIdleHandlerCount = -1; // -1 only during first iteration
  10. int nextPollTimeoutMillis = 0;
  11. for (;;) {
  12. if (nextPollTimeoutMillis != 0) {
  13. Binder.flushPendingCommands();
  14. }
  15. // 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。
  16. // 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。
  17. // 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。
  18. // 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
  19. nativePollOnce(ptr, nextPollTimeoutMillis);
  20. synchronized (this) {
  21. // Try to retrieve the next message. Return if found.
  22. final long now = SystemClock.uptimeMillis();
  23. Message prevMsg = null;
  24. Message msg = mMessages;
  25. if (msg != null && msg.target == null) {
  26. // Stalled by a barrier. Find the next asynchronous message in the queue.
  27. // msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
  28. // 如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),
  29. // 所有同步消息都将忽略(平常发送的一般都是同步消息)
  30. do {
  31. prevMsg = msg;
  32. msg = msg.next;
  33. } while (msg != null && !msg.isAsynchronous());
  34. }
  35. if (msg != null) {
  36. if (now < msg.when) {
  37. // 消息触发时间未到,设置下一次轮询的超时时间
  38. // Next message is not ready. Set a timeout to wake up when it is ready.
  39. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  40. } else {
  41. // Got a message.
  42. // 得到 Message
  43. mBlocked = false;
  44. if (prevMsg != null) {
  45. prevMsg.next = msg.next;
  46. } else {
  47. mMessages = msg.next;
  48. }
  49. msg.next = null;
  50. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  51. msg.markInUse(); // 标记 FLAG_IN_USE
  52. return msg;
  53. }
  54. } else {
  55. // No more messages.
  56. // 没有消息,会一直阻塞,直到被唤醒
  57. nextPollTimeoutMillis = -1;
  58. }
  59. // Process the quit message now that all pending messages have been handled.
  60. if (mQuitting) {
  61. dispose();
  62. return null;
  63. }
  64. // If first time idle, then get the number of idlers to run.
  65. // Idle handles only run if the queue is empty or if the first message
  66. // in the queue (possibly a barrier) is due to be handled in the future.
  67. // Idle handle 仅当队列为空或者队列中的第一个消息将要执行时才会运行
  68. if (pendingIdleHandlerCount < 0
  69. && (mMessages == null || now < mMessages.when)) {
  70. pendingIdleHandlerCount = mIdleHandlers.size();
  71. }
  72. if (pendingIdleHandlerCount <= 0) {
  73. // No idle handlers to run. Loop and wait some more.
  74. // 没有 idle handler 需要运行,继续循环
  75. mBlocked = true;
  76. continue;
  77. }
  78. if (mPendingIdleHandlers == null) {
  79. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  80. }
  81. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  82. }
  83. // Run the idle handlers.
  84. // We only ever reach this code block during the first iteration.
  85. // 只有第一次循环时才会执行下面的代码块
  86. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  87. final IdleHandler idler = mPendingIdleHandlers[i];
  88. mPendingIdleHandlers[i] = null; // release the reference to the handler
  89. boolean keep = false;
  90. try {
  91. keep = idler.queueIdle();
  92. } catch (Throwable t) {
  93. Log.wtf(TAG, "IdleHandler threw exception", t);
  94. }
  95. if (!keep) {
  96. synchronized (this) {
  97. mIdleHandlers.remove(idler);
  98. }
  99. }
  100. }
  101. // Reset the idle handler count to 0 so we do not run them again.
  102. // 将 pendingIdleHandlerCount 置零保证不再运行
  103. pendingIdleHandlerCount = 0;
  104. // While calling an idle handler, a new message could have been delivered
  105. // so go back and look again for a pending message without waiting.
  106. nextPollTimeoutMillis = 0;
  107. }
  108. }

next() 方法是一个死循环,但是当没有消息的时候会阻塞,避免过度消耗 CPU。nextPollTimeoutMillis 大于 0 时表示等待下一条消息需要阻塞的时间。等于 -1 时表示没有消息了,一直阻塞到被唤醒。

这里的阻塞主要靠 native 函数 nativePollOnce() 来完成。其具体原理我并不了解,想深入学习的同学可以参考 Gityuan 的相关文 Android消息机制2-Handler(Native层)

MessageQueue 提供了消息入队和出队的方法,但它自己并不是自动取消息。那么,谁来把消息取出来并执行呢?这就要靠 Looper 了。

Looper

创建 Handler 之前必须先创建 Looper,而主线程已经为我们自动创建了 Looper,无需再手动创建,见 ActivityThread.javamain() 方法:

  1. public static void main(String[] args) {
  2. ...
  3. Looper.prepareMainLooper(); // 创建主线程 Looper
  4. ...
  5. }

prepareMainLooper()

  1. public static void prepareMainLooper() {
  2. prepare(false);
  3. synchronized (Looper.class) {
  4. if (sMainLooper != null) {
  5. throw new IllegalStateException("The main Looper has already been prepared.");
  6. }
  7. sMainLooper = myLooper();
  8. }
  9. }

sMainLooper 只能被初始化一次,也就是说 prepareMainLooper() 只能调用一次,否则将直接抛出异常。

prepare()

  1. public static void prepare() {
  2. prepare(true);
  3. }
  4. private static void prepare(boolean quitAllowed) {
  5. // 每个线程只能执行一次 prepare(),否则会直接抛出异常
  6. if (sThreadLocal.get() != null) {
  7. throw new RuntimeException("Only one Looper may be created per thread");
  8. }
  9. // 将 Looper 存入 ThreadLocal
  10. sThreadLocal.set(new Looper(quitAllowed));
  11. }

主线程中调用的是 prepare(false),说明主线程 Looper 是不允许退出的。因为主线程需要源源不断的处理各种事件,一旦退出,系统也就瘫痪了。而我们在子线程调用 prepare() 来初始化 Looper时,默认调动的是 prepare(true),子线程 Looper 是允许退出的。

每个线程的 Looper 是通过 ThreadLocal 来存储的,保证其线程私有。

再回到文章开头介绍的 Handler 的构造函数中 mLooper 变量的初始化:

  1. mLooper = Looper.myLooper();
  1. public static @Nullable Looper myLooper() {
  2. return sThreadLocal.get();
  3. }

也是通过当前线程的 ThreadLocal 来获取的。

构造函数

  1. private Looper(boolean quitAllowed) {
  2. mQueue = new MessageQueue(quitAllowed); // 创建 MessageQueue
  3. mThread = Thread.currentThread(); // 当前线程
  4. }

再对照 Handler 的构造函数:

  1. public Handler(Looper looper, Callback callback, boolean async) {
  2. mLooper = looper;
  3. mQueue = looper.mQueue;
  4. mCallback = callback;
  5. mAsynchronous = async;
  6. }

其中的关系就很清晰了。

  • Looper 持有 MessageQueue 对象的引用
  • Handler 持有 Looper 对象的引用以及 Looper 对象的 MessageQueue 的引用

loop()

看到这里,消息队列还没有真正的运转起来。我们先来看一个子线程使用 Handler 的标准写法:

  1. class LooperThread extends Thread {
  2. public Handler mHandler;
  3. public void run() {
  4. Looper.prepare();
  5. mHandler = new Handler() {
  6. public void handleMessage(Message msg) {
  7. // process incoming messages here
  8. }
  9. };
  10. Looper.loop();
  11. }
  12. }

让消息队列转起来的核心就是 Looper.loop()

  1. public static void loop() {
  2. final Looper me = myLooper(); // 从 ThreadLocal 中获取当前线程的 Looper
  3. if (me == null) {
  4. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5. }
  6. final MessageQueue queue = me.mQueue; // 获取当前线程的消息队列
  7. ... // 省略部分代码
  8. for (;;) { // 循环取出消息,没有消息的时候可能会阻塞
  9. Message msg = queue.next(); // might block
  10. if (msg == null) {
  11. // No message indicates that the message queue is quitting.
  12. return;
  13. }
  14. ... // 省略部分代码
  15. try {
  16. msg.target.dispatchMessage(msg); // 通过 Handler 分发 Message
  17. dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
  18. } finally {
  19. if (traceTag != 0) {
  20. Trace.traceEnd(traceTag);
  21. }
  22. }
  23. ... // 省略部分代码
  24. msg.recycleUnchecked(); // 将消息放入消息池,以便重复利用
  25. }
  26. }

简单说就是一个死循环不停的从 MessageQueue 中取消息,取到消息就通过 Handler 来进行分发,分发之后回收消息进入消息池,以便重复利用。

从消息队列中取消息调用的是 MessageQueue.next() 方法,之前已经分析过。在没有消息的时候可能会阻塞,避免死循环消耗 CPU。

取出消息之后进行分发调用的是 msg.target.dispatchMessage(msg)msg.target 是 Handler 对象,最后再来看看 Handler 是如何分发消息的。

  1. public void dispatchMessage(Message msg) {
  2. if (msg.callback != null) { // callback 是 Runnable 类型,通过 post 方法发送
  3. handleCallback(msg);
  4. } else {
  5. if (mCallback != null) { // Handler 的 mCallback参数 不为空时,进入此分支
  6. if (mCallback.handleMessage(msg)) {
  7. return;
  8. }
  9. }
  10. handleMessage(msg); // Handler 子类实现的 handleMessage 逻辑
  11. }
  12. }
  13. private static void handleCallback(Message message) {
  14. message.callback.run();
  15. }
  • Message 的 callback 属性不为空时,说明消息是通过 postXXX() 发送的,直接执行 Runnable 即可。
  • Handler 的 mCallback 属性不为空,说明构造函数中传入了 Callback 实现,调用 mCallback.handleMessage(msg) 来处理消息
  • 以上条件均不满足,只可能是 Handler 子类重写了 handleMessage() 方法。这好像也是我们最常用的一种形式。

Message

之所以把 Message 放在最后说,因为我觉得对整个消息机制有了一个完整的深入认识之后,再来了解 Message 会更加深刻。首先来看一下它有哪些重要属性:

  1. int what :消息标识
  2. int arg1 : 可携带的 int
  3. int arg2 : 可携带的 int
  4. Object obj : 可携带内容
  5. long when : 超时时间
  6. Handler target : 处理消息的 Handler
  7. Runnable callback : 通过 post() 发送的消息会有此参数

Message 有 public 修饰的构造函数,但是一般不建议直接通过构造函数来构建 Message,而是通过 Message.obtain() 来获取消息。

obtain()

  1. public static Message obtain() {
  2. synchronized (sPoolSync) {
  3. if (sPool != null) {
  4. Message m = sPool;
  5. sPool = m.next;
  6. m.next = null;
  7. m.flags = 0; // clear in-use flag
  8. sPoolSize--;
  9. return m;
  10. }
  11. }
  12. return new Message();
  13. }

sPool 是消息缓存池,链表结构,其最大容量 MAX_POOL_SIZE 为 50。obtain() 方法会直接从消息池中取消息,循环利用,节约资源。当消息池为空时,再去新建消息。

recycleUnchecked()

还记得 Looper.loop() 方法中最后会调用 msg.recycleUnchecked() 方法吗?这个方法会回收已经分发处理的消息,并放入缓存池中。

  1. void recycleUnchecked() {
  2. // Mark the message as in use while it remains in the recycled object pool.
  3. // Clear out all other details.
  4. flags = FLAG_IN_USE;
  5. what = 0;
  6. arg1 = 0;
  7. arg2 = 0;
  8. obj = null;
  9. replyTo = null;
  10. sendingUid = -1;
  11. when = 0;
  12. target = null;
  13. callback = null;
  14. data = null;
  15. synchronized (sPoolSync) {
  16. if (sPoolSize < MAX_POOL_SIZE) {
  17. next = sPool;
  18. sPool = this;
  19. sPoolSize++;
  20. }
  21. }
  22. }

总结

说到这里,Handler 消息机制就全部分析完了,相信大家也对整个机制了然于心了。

  • Handler 被用来发送消息,但并不是真正的自己去发送。它持有 MessageQueue 对象的引用,通过 MessageQueue 来将消息入队。
  • Handler 也持有 Looper 对象的引用,通过 Looper.loop() 方法让消息队列循环起来。
  • Looper 持有 MessageQueue 对象应用,在 loop() 方法中会调用 MessageQueue 的 next() 方法来不停的取消息。
  • loop() 方法中取出来的消息最后还是会调用 Handler 的 dispatchMessage() 方法来进行分发和处理。

最后,关于 Handler 一直有一个很有意思的面试题:

Looper.loop() 是死循环为什么不会卡死主线程 ?

看起来问的好像有点道理,实则不然。你仔细思考一下,loop() 方法的死循环和卡死主线程有任何直接关联吗?其实并没有。

回想一下我们经常在测试代码时候写的 main() 函数:

  1. public static void main(){
  2. System.out.println("Hello World");
  3. }

姑且就把这里当做主线程,它里面没有死循环,执行完就直接结束了,没有任何卡顿。但是问题是它就直接结束了啊。在一个 Android 应用的主线程上,你希望它直接就结束了吗?那肯定是不行的。所以这个死循环是必要的,保证程序可以一直运行下去。Android 是基于事件体系的,包括最基本的 Activity 的生命周期都是由事件触发的。主线程 Handler 必须保持永远可以相应消息和事件,程序才能正常运行。

另一方面,这并不是一个时时刻刻都在循环的死循环,当没有消息的时候,loop() 方法阻塞,并不会消耗大量 CPU 资源。

关于 Handler 就说到这里了。还记得文章说过线程的 Looper 对象是保存在 ThreadLocal 中的吗?下一篇文章就来说说 ThreadLocal 是如何保存 线程局部变量 的。

文章首发微信公众号: 秉心说 , 专注 Java 、 Android 原创知识分享,LeetCode 题解。

更多最新原创文章,扫码关注我吧!

深入理解 Handler 消息机制的更多相关文章

  1. 【Android面试查漏补缺】之Handler详解,带你全面理解Handler消息机制

    在安卓面试中,关于 Handler 的问题是必备的,但是这些关于 Handler 的知识点你都知道吗? 一.题目层次 Handler 的基本原理 子线程中怎么使用 Handler MessageQue ...

  2. Android全面解析之由浅及深Handler消息机制

    前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Handler的博客可谓是俯拾皆是,而这也是一个老生常谈的话题,可见的他非常基础,也非常重要.但很多的博客,却很少有从入门开始介绍,这在我一开始学习的时候 ...

  3. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  4. Android(java)学习笔记202:Handler消息机制的原理和实现

     联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系   1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...

  5. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

  6. Android(java)学习笔记145:Handler消息机制的原理和实现

     联合学习 Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系   1. 首先我们通过一个实例案例来引出一个异常: (1)布局文件activity_m ...

  7. Android消息传递之Handler消息机制

    前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...

  8. Handler消息机制与Binder IPC机制完全解析

    1.Handler消息机制 序列 文章 0 Android消息机制-Handler(framework篇) 1 Android消息机制-Handler(native篇) 2 Android消息机制-H ...

  9. Handler消息机制实现更新主UI

    如下实现的是简单的更新主UI的方法,用Handler消息机制 将textview的内容每隔一秒实现num++ /* * handler消息机制 * asynctask异步任务 *  * httpcli ...

随机推荐

  1. HTML结构 语义化思想

    总体思想:用正确的标签做正确的事情! 根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好地解析. 主要体现: 1. 对用户而言, ...

  2. web设计_9_CSS常用布局,响应式

    一个完整的页面和其中的组件该如何具备灵活性. 怎么样利用CSS来实现无论屏幕.窗口以及字体的大小如何变化,都可以自由扩展和收缩的分栏式页面. 要决定使用流动布局.弹性布局还是固定宽度的布局,得由项目的 ...

  3. Win常用软件

    本节只适合windows系统 VScode 下载 安装 双击安装 打开目录方式 右键文件夹->使用VSCode打开 命令行打开 code folder [dzlua@win10:~]$ ls a ...

  4. Python版:Selenium2.0之WebDriver学习总结_实例1

    Python版:Selenium2.0之WebDriver学习总结_实例1  快来加入群[python爬虫交流群](群号570070796),发现精彩内容. 实属转载:本人看的原文地址 :http:/ ...

  5. 【iOS】App Transport Security

    iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输.这也意味着所有的HTTP协议都强制使用了HTTPS ...

  6. 以太坊solidity智能合约-生成随机数

    Solidity随机数生成 在以太坊的只能合约中,没有提供像其他面向对象编程一样的生成随机数的工具类或方法.其实,所谓的随机数也是伪随机的,没有哪一种语言能够真正的生成随机数. 对于solidity来 ...

  7. Linux常用的命令及使用方法

    1.请用命令查出ifconfig命令程序的绝对路径 [root@localhost ~]# which ifconfig(ifconfig是linux中用于显示或配置网络设备(网络接口卡)的命令) / ...

  8. Java的自动装箱/拆箱

    概述 自JDK1.5开始, 引入了自动装箱/拆箱这一语法糖, 它使程序员的代码变得更加简洁, 不再需要进行显式转换.基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法将原始类 ...

  9. 【POJ - 3258】River Hopscotch(二分)

    River Hopscotch 直接中文 Descriptions 每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一块岩石跳到另一块岩石.这项激动人心的活动在一条长长的笔直河道中进行,在起点 ...

  10. c#小灶——输出语句

    前面我我们学习了如何在控制台输出一句话,今天我们学习一下更详细的输出方式. Console.WriteLine();和Console.Write(); 我们来看一下下面几行代码, using Syst ...