Android Loop机制中Looper与handler详细分析

作者:Super-B 时间:2023-01-13 04:40:56 

Looper是什么

用于为线程运行消息循环的类。默认情况下,线程没有与之关联的消息循环。要创建一个,在要运行循环的线程中调用 prepare(),然后调用loop()让它处理消息,直到循环停止为止。与消息循环的大多数交互是通过 Handler类进行的。

意思大概就是让线程有处理消息的能力,并且这种能力是无限循环的,直到被停止为止。

简单使用

public Handler handler;
public void looperThread(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            handler = new Handler(Looper.myLooper(),new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());
                    return false;
                }
            });
            Looper.loop();
        }
    }).start();
}
@Override
public void onClick(View view) {
    Message message = Message.obtain();
    message.obj = "点击事件消息时间戳:"+System.currentTimeMillis()%10000;
    handler.sendMessage(message);
}

创建一个具有消息循环的线程,该线程中创建一个和该looper绑定的handler对象,然后点击事件中不断的去发送消息给looper循环,看下最后的效果如下:

18:17:45.459 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5458
18:17:45.690 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5690
18:17:45.887 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5886
...省略
18:18:40.010 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:9
18:18:40.840 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:839
18:18:41.559 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:1558

可以看到我一直点击,一直有消息可以被处理,那么说明我创建的线程是一直运行的,并没有结束。那么looper具体是怎么实现的这样的功能的呢?

从源码了解loop原理

在分析源码之前,先看下整体的类图关系:

Android Loop机制中Looper与handler详细分析

loop分析

我们从Looper.prepare();这句代码开始分析:

Looper.prepare();
public final class Looper {
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   private static Looper sMainLooper;  // guarded by Looper.class
   final MessageQueue mQueue;
   final Thread mThread;
   ...省略
   public static void prepare() {
       prepare(true);
   }
 ...省略

可以看到调用了prepare()方法后,接着调用了有参函数prepare:

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

sThreadLocal的泛型参数是Looper,那么知道Looper保存在了线程所持有的map容器中,首先就是判断sThreadLocal.get()是否为空,这个方法在上一章说过,是根据当前线程来获取的,如果这个prepare方法在ui线程中调用那么返回的就是ui线程中的Looper,如果调用的是子线程中,那么返回的就是子线程的Looper了,如果不为空,抛出异常,意思就是一个线程只能持有一个Looper对象;如果为空的话,那么调用sThreadLocal的set方法将创建的Looper对象存放到对应线程的map容器中。

接着调用了loop函数:

Looper.loop();
public static void loop() {
       final Looper me = myLooper();
       ...省略
       final MessageQueue queue = me.mQueue;
       for (;;) {
           Message msg = queue.next(); // might block
           if (msg == null) {
               return;
           }  
     ...省略
           try {
               msg.target.dispatchMessage(msg);
           } finally {
           ...省略
           }
...省略
           msg.recycleUnchecked();
       }
   }

大概是这样的,其中去掉了一些和业务无关的代码。

myLooper()

第一步调用myLooper()方法:

final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
public static @Nullable Looper myLooper() {
   return sThreadLocal.get();
}

获取当前线程的sThreadLocal中的Looper对象。从Looper对象获取队列。

第二步开始for循环,Message msg = queue.next(); // might block 在循环中不断的从queue中取Message消息,

获取msg判断是否为空,空的话直接返回,不为空的话,调用msg的Target的dispatchMessage方法。最后msg使用完毕之后就回收msg对象。

首先来看下

Message msg = queue.next(); // might block

next()

调用的是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;
               }
...省略
           }
           ...省略
       }
   }

首先调用nativePollOnce(ptr, nextPollTimeoutMillis); 这个方法是调用的native方法,意思就是阻塞当前线程,在延迟nextPollTimeoutMillis时长后唤醒当前线程。

接着调用:

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

其中的判断是msg.target == null这个条件,这个条件说明当前的msg是没有设置Target的,msg的Target一般是handler,如果这里是空的话,那么这个msg就是同步屏障消息,用于拦截同步消息的,让异步消息有优先处理权。如果当前是同步屏障的话,那么while循环,一直向后遍历msg节点,条件是这个msg非空和非异步消息,所以这里能够跳出循环的情况就是msg到了尾部为空了,要么就是向后遍历发现了异步消息。接着往下看:

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

分为两种情况:

(1)如果msg为空的话,先设置延迟时长nextPollTimeoutMillis = -1;接着这趟for循环结束,回到起点的位置,又开始执行nativePollOnce(ptr, nextPollTimeoutMillis);延迟时间是-1那么线程就会阻塞下去,直到被唤醒,不会执行for循环了(msg在进入队列的时候会去唤醒线程的,所以这里不会一直阻塞的)。

(2)如果msg不为空的话,假设消息设置的时间点大于现在的时间点,那么设置nextPollTimeoutMillis 为时间差和整数最大值中的最小值。这样的话,线程在下次循环中的开头就会阻塞到可以执行该消息的when时间节点再次运行(线程在阻塞的时候不会去轮转cpu时间片所以可以节约cpu资源,同样的,如果阻塞期间有消息进来可以马上运行,那么还是会被唤醒的);假设消息设置的时间点小于现在的时间点,那么从msg消息链中把该消息摘取出来,msg标记为使用中,将msg返回。

思考:队列中头部msg是同步屏障的话,那么优先从前往后去查找异步消息进行处理,所以在同步屏障消息之后的同步消息不会被执行,直到被移除为止。队列头部是普通的消息的时候,是根据when时间节点来判断,是直接返回msg,还是等待when-now时间差在去循环一遍查找头结点msg。

handler.dispatchMessage

handler = new Handler(Looper.myLooper(),new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());
              return false;
          }
      });

handler在创建的参数是Looper和Callback,接着再来看下dispatchMessage是如何实现的:

public void dispatchMessage(Message msg) {
   if (msg.callback != null) {
       handleCallback(msg);
   } else {
       if (mCallback != null) {
           if (mCallback.handleMessage(msg)) {
               return;
           }
       }
       handleMessage(msg);
   }
}
private static void handleCallback(Message message) {
   message.callback.run();
}

如果msg存在callback的话,直接调用callbakc的run方法,这里不存在我们传递msg没有设置callback,那么走下面的那个逻辑,我们给handler设置了mCallback,那么就直接回调handler的mCallback.handleMessage的方法:

@Override
   public boolean handleMessage(Message msg) {
       Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());
       return false;
   }

这样也就出现了我们开头demo中的打印消息了。

handler分析

我们通过上面的next方法分析了如何从队列中获取消息,那么我们还没有分析消息是如何入队的,接下来我们来分析下handler的几个关键的问题,(1)handler的消息一个分为几种;(2)handler发送消息到哪去了。

我们从handler的构造函数入手:

handler = new Handler(Looper.myLooper(),new Handler.Callback() {
              @Override
              public boolean handleMessage(Message msg) {
                  Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());
                  return false;
              }
          });
public Handler(Looper looper, Callback callback) {
   this(looper, callback, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
   mLooper = looper;
   mQueue = looper.mQueue;
   mCallback = callback;
   mAsynchronous = async;
}

我们可以看到,handler一共持有四个关键变量,Looper循环(和looper关联,handler发送的消息只会发到这个队列中),mQueue 持有Looper的队列,mCallback 用于处理消息的回调函数,mAsynchronous 标志这个handler发送的消息是同步的还是异步的。

我们再来看一下消息是怎么发送的:

Message message = Message.obtain();
message.obj = "点击事件消息时间戳:"+System.currentTimeMillis()%10000;
handler.sendMessage(message);

首先从Message中获取一个message,这个Message其实里面保存着msg的链表,遍历链表,返回的是回收的msg,其中flags整数变量标志着msg是否正在使用中,是否是异步消息等等状态。

handler.sendMessage(message);

然后使用handler去发送一个msg对象、接着进去看下:

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

msg初始状态下是同步消息,sendMessage方法发送出去的消息delayMillis 延迟时间是0;

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

在入队列之前,将msg的Target设置为当前handler,然后根据handler是否是异步的,设置msg是否是异步的,然后调用队列的入队函数,将消息入队。

这里先回答第二个问题,如何入队的:

消息入队

boolean enqueueMessage(Message msg, long when) {
       synchronized (this) {
           msg.markInUse();
           msg.when = when;
           Message p = mMessages;
           boolean needWake;
           if (p == null || when == 0 || when < p.when) {
               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;
   }

首先判断当前入队msg的when时间是否比队列中的头结点的when时间节点靠前,靠前的话,就将入队的msg加入到队列的头部,并且调用nativeWake(mPtr);方法唤醒looper所在的线程,那么next()开始执行了,可以马上遍历队列,消耗msg消息。如果当前消息msg的时间节点when大于头部节点,首先设置needWake标志, 是否需要唤醒分为:如果队列头部是同步屏障,并且入队消息msg是异步消息,那么就需要唤醒线程,其他情况不需要唤醒;接着执行for循环,循环里面寻找队列中第一个节点时间是大于msg消息的时间节点的(这意味着队列中消息是按照时间节点排序的),循环结束后,将入队的msg插入到队列中,最后根据需要是否唤醒线程。

同步屏障

同步屏障功能是让队列中的同步消息暂时不执行,直到同步屏障被移除,异步消息可以不受影响的被执行,相当于排队买票的队列中头部有个人一直卡着不走,只有vip的人才能正常在窗口中买票,其他普通人买不了票,如果那个头部卡着的那个人不走的话。这个同步屏障非常有用,用于优先执行某些任务。

同步屏障我们使用的比较少,但是安卓frame层代码有使用这个同步屏障的功能,例如ViewRootImp中:

ViewRootImp中:
   void scheduleTraversals() {
  ...省略
           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           mChoreographer.postCallback(
                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...省略
   }
Choreographer中:
  private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long 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);
           }
       }
   }

向队列中发送一个同步屏障getQueue().postSyncBarrier();看下源码如何实现的:

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

同步屏障的时间节点是当前时间,还可以知道同步屏障消息的Target是空的,成员变量arg1保存的是同步屏障的自增值。接下来就是找到队列中第一个时间节点比自己大的节点位置,然后插入到队列中,所以屏障也是按照时间来排列的,没有特殊待遇。

接着使用handler向Looper中发送了一个异步消息:

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
  msg.arg1 = callbackType;
  msg.setAsynchronous(true);
  mHandler.sendMessageAtTime(msg, dueTime);

可以看到异步消息需要设置msg.setAsynchronous(true);

执行ui的任务使用异步消息去执行,为啥要用异步,因为在5.0以上的安卓系统中已经开始使用了垂直同步技术了,所以重绘页面的操作需要按照屏幕刷新率来执行,假如一个16ms里面有多次重绘请求,最终也只会抛弃掉,只保留一个重绘消息,所以,为了保证重绘操作能够在收到同步信号的时间节点马上执行,必须使用同步屏障,这样前面排队的同步消息暂时不执行,优先执行我们的重绘界面的异步消息,这样可以保证我们的界面尽量能够及时刷新,避免丢帧。、

再来看下handler.post()方法:

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

可以看到,其实也是封装了一个msg对象,将callback传递给它,我们在dispatchMessge函数中也知道,如果msg如果有自己的callback 就会调用这个回调处理消息,不会使用handler自己的callback 来处理消息。

来源:https://blog.csdn.net/u012345683/article/details/108297844

标签:Android,handler,Looper
0
投稿

猜你喜欢

  • java对象序列化与反序列化的默认格式和json格式使用示例

    2021-12-07 18:22:23
  • 完美解决android M上锁屏情况下,禁止pc通过MTP访问手机存储单元

    2023-01-23 07:55:37
  • Android 悬浮窗权限各机型各系统适配大全(总结)

    2022-04-27 10:09:53
  • Android中实现根据资源名获取资源ID

    2023-06-20 04:18:30
  • java 实现文件夹的拷贝实例代码

    2023-01-04 10:19:28
  • Java BigDecimal使用方法详解

    2022-03-28 03:45:23
  • 详解Android studio中正确引入so文件的方法

    2022-06-17 23:21:32
  • Java泛型的类型擦除示例详解

    2023-07-02 13:38:17
  • java 使用foreach遍历集合元素的实例

    2022-11-17 09:24:58
  • 一文详解C++模板和泛型编程

    2022-12-28 09:40:46
  • java基础的详细了解第七天

    2023-02-01 12:06:26
  • c#语言使用Unity粒子系统制作手雷爆炸

    2021-10-11 11:13:46
  • 清除aspx页面缓存的程序实现方法

    2021-10-28 06:16:57
  • Android开发之SQLite的使用方法

    2022-11-12 05:26:38
  • SpringBoot动态更新yml文件

    2022-02-23 00:34:23
  • Java文件断点续传实现原理解析

    2022-08-21 02:05:39
  • Java实战入门之双色球彩票小游戏

    2023-05-12 04:07:13
  • Android开发中Bitmap高效加载使用详解

    2021-06-05 01:32:15
  • c# 进程和线程的区别与联系

    2023-05-04 23:18:36
  • SpringMVC结构简介及常用注解汇总

    2023-10-25 09:16:59
  • asp之家 软件编程 m.aspxhome.com