• Android学习总结(3)——Handler深入详解


    什么是Handler

    Handler是Android消息机制的上层接口,它为我们封装了许多底层的细节,让我们能够很方便的使用底层的消息机制。Handler的最常见应用场景之一便是通过Handler在子线程中间接更新UI。Handler的作用主要有两个:一是发送消息;二是处理消息,它的运作需要底层Looper和MessageQueue的支撑。

    MessageQueue即消息队列,它的底层用单链表实现;Looper则负责在一个循环中不断从MessageQueue中取消息,若取到了就交由Handler进行处理,否则便一直等待。关于Looper需要注意的一点是除了主线程之外的其他线程中默认是不存在Looper的。主线程中之所以存在,是因为在ActivityThread被创建时会完成初始化Looper的工作。

    为什么使用Handler

    总的来说, Handler的作用是将一个任务(从当前线程)切换到指定的线程中去执行 。我们知道Android只允许主线程去更新用户界面,而主线程需要时刻保持较高的响应性,因此我们要把一些耗时任务交给工作者线程去执行。那么问题来了,如果工作者线程执行完任务后想要更新UI该怎么破?我们希望的是主线程能够接收到工作者线程的通知,并且能根据工作者线程执行任务的结果对用户界面进行相应的更新。Handler就能让我们很方便的做到这些。Handler的工作过程大致如下图所示:

    我们针对上图做下简单解释(详细的分析请见后文):首先我们在主线程中创建Handler对象(使用主线程的Looper)并定义handleMessage方法,这个Handler对象默认会关联主线程中的Looper。通过在工作者线程中使用该Handler对象发送消息,相应的消息处理工作(即handleMessage方法)会在主线程中运行, 这样就成功地将更新UI任务从工作者线程切换到了主线程。

    Handler的工作原理分析

    总的来说,Handler对象在被创建时会使用当前线程的Looper来构建底层的消息循环系统(使用默认构造器的情况下),若当前线程不存在Looper,则会报错。Handler对象创建成功后,就可以通过Handler的send或post方法发送消息了。调用send/post方法发送消息时,实际上会调用MessageQueue的enqueueMessage方法将该消息加入到MessageQueue中。之后Looper发现有新消息会取出,并把它交给Handler处理。下面我们通过分析相关源码来详细介绍这一过程。在这之前我们需要先了解一下ThreadLocal的工作原理。

    ThreadLocal的内部工作机制

    ThreadLocal是一个线程内部的数据存储类。通过使用ThreadLocal,能够让同一个数据对象在不同的线程中存在多个副本,而这些副本互不影响。Looper的实现中便使用到了ThreadLocal。通过使用ThreadLocal,每个线程都有自己的Looper,它们是同一个数据对象的不同副本,并且不会相互影响。下面我们现在探索下ThreadLocal的工作原理,为分析Looper的工作原理做好铺垫。

    ThreadLocal使用示例

    作为ThreadLocal的一个简单示例,我们先创建一个ThreadLocal对象:

    private ThreadLocal<Integer> mIntegerThreadLocal =
        new ThreadLocal<Integer>();

    然后创建两个子线程,并在不同的线程中为ThreadLocal对象设置不同的值:

    1 mIntegerThreadLocal.set(0);
    2 Log.d(TAG, "In Main Thread, mIntegerThreadLocal = " + mIntegerThreadLocal.get()); 
    3  
    4 new Thread("Thread 1") { 
    5   @Override 
    6   public void run(){ 
    7     mIntegerThreadLocal.set(1); 
    8     Log.d(TAG, "In Thread 1, mIntegerThreadLocal = " + mIntegerThreadLocal.get()); 
    9   }
    10 }.start();
    11 
    12 new Thread("Thread 2") {
    13   @Override
    14   public void run() {
    15     Log.d(TAG, "In Thread 2, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
    16   }
    17 }.start();

    在以上代码中,我们在主线程中设置mIntegerThreadLocal的值为0,在Thread 1中该设置为1,而在Thread 2中未设置。我们看一下日志输出:

    通过日志输出我们可以看到,主线程与Thread 1的值确实分别为我们为他设置的,而Thread 2中由于我们没有给它赋值,所以就为null。我们虽然在不同的线程中访问同一个数据对象,却可以获取不同的值。那么ThreadLocal是如何做到这一点的呢?下面我们通过源码来寻找答案。

    ThreadLocal的工作原理

    我们首先要知道,Thread类内部有一个专门用来存储线程对象ThreadLocal数据的实例域,它的声明如下:

    ThreadLocal.Values localValues;

    这样一来,每个线程中就可以维护ThreadLocal对象的一个副本,而且这些副本不会互相干扰,ThreadLocal的get方法只要到localValues中去取数据就好了,set方法也只需操作本线程的localValues。我们来看一下set方法的源码:

    1 public void set(T value) {
    2   Thread currentThread = Thread.currentThread();
    3   Values values = values(currentThread);
    4   if (values == null) {
    5     values = initializeValues(currentThread);
    6   }
    7   values.put(this, value);
    8 }

    第3行通过values方法获取到当前线程的localValues并存入values变量中,接下来在第4行进行判断,若localValues为null,则调用initializeValues方法进行初始化,然后会调用put方法将value存进去。实际上,localValues内部有一个名为table的Object数组,ThreadLocal的值就存在这个数组中。

    了解了set方法的大致逻辑后,我们再来看一下get方法都做了些什么:

    1 public T get() { 
    2   // Optimized for the fast path. 
    3   Thread currentThread = Thread.currentThread(); 
    4   Values values = values(currentThread); 
    5   if (values != null) { 
    6     Object[] table = values.table; 
    7     int index = hash & values.mask; 
    8     if (this.reference == table[index]) { 
    9       return (T) table[index + 1];
    10    }
    11  } else {
    12    values = initializeValues(currentThread);
    13  }
    14 
    15  return (T) values.getAfterMiss(this);
    16 }

    第4行中,获取localValues。第5行若判断为null,则表示未进行设置(比如上面例子中的线程2),就会返回默认值;若判断非空就先获取table数组,然后再计算出index,根据index返回ThreadLocal的值。

    经过以上对get和set方法的源码的分析,我们了解到了这两个方法实际上对不同的线程对象会分别操作它们内部的localValues,所以能够实现多个ThreadLocal数据对象的副本之间的互不干扰。了解了ThreadLocal的实现原理,下面我们来探索下Looper是怎么借助ThreadLocal来实现的。

    Looper的内部工作机制

    在介绍Looper的工作机制之前,我们先来简单的介绍下MessageQueue。MessageQueue对消息队列进行了封装,在它的内部使用单链表来保存消息。MessageQueue主要支持以下两个操作:

    • enqueueMessage:向消息队列中插入一个消息。
    • next:从消息队列中取出一个消息(会从队列中删除该消息)。next方法内有一个无限循环,若消息队列为空,它会阻塞在这直到取到消息。

    大致了解了MessageQueue后,让我们一起来探索Looper的内部工作机制,看看它是如何漂亮的完成将任务切换到另一个线程这个工作的。我们首先来看一下Looper的构造方法:

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

    我们可以看到Looper的构造方法中创建了一个MessageQueue对象。之前我们提到过Handler只有在存在Looper的线程中才能创建,而我们看到Looper的构造方法是private的,那么我们怎么为一个线程创建Looper呢?答案是使用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));
    }

    我们可以看到prepare方法内部调用了Looper的构造器来为当前线程初始化Looper,而且当前的线程的Looper已经初始化的情况下再调用prepare方法会抛出异常。

    创建了Looper后,我们就可以开始通过Looper.loop方法进入消息循环了( 注意,主线程中我们无需调用loop方法,因为ActivityThread的main方法中已经为我们调用了 )。我们来看一下这个方法的源代码:

    ublic 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();
    
     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
       Printer logging = me.mLogging;
       if (logging != null) {
         logging.println(">>>>> Dispatching to " + msg.target + " " +
             msg.callback + ": " + msg.what);
       }
    
       msg.target.dispatchMessage(msg);
    
       if (logging != null) {
         logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
       }
    
       // Make sure that during the course of dispatching the
       // identity of the thread wasn't corrupted.
       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();
     }
    }

    通过以上代码我们可以看到,在第13行会进入一个无限循环。接着在第14行,调用了MessageQueue的next方法,之前我们介绍过这个方法会一直阻塞直到从消息队列中取出一个消息。退出这个无限循环的唯一方法就是MessageQueue返回null。这可以通过调用Looper的quit方法来实现。当Looper的quit/quitSafely方法被调用时,会导致MessageQueue的quit/quitSafely方法被调用,这会导致消息队列被标记为“退出”状态,如此一来,MessageQueue的next方法就会返回null了。这告诉了我们,如果我们不调用Looper的quit方法,他就会在loop方法中的循环里一直运行下去。

    若在第14行中成功从MessageQueue中取得了一个消息,接下来就会对这个消息进行处理。第27行调用了msg.target的dispatchMessage方法,其中msg.target指的是发送这条消息的Handler对象,也就是说这里调用的是发送消息的Handler对象的dispatchMessage方法。注意,Handler的dispatchMessage方法实在创建该Handler时所使用的线程中执行的,这样一来,便成功地将任务切换到了Looper所在线程。接下来,我们以分析dispatchMessage方法的源码为切入点研究一下Handler的工作原理。

    Handler的内部工作机制

    首先,我们接着上一步,看一下dispatchMessage方法的源码:

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

    我们可以看到,这个方法中会首先判断msg.callback是否为null,若不为null则调用handleCallback方法。msg.callback是一个Runnable对象,实际上就代表着我们调用post方法放入MessageQueue中的Runnable对象。也就是说,若我们post了一个Runnable对象,就会调用handleCallback方法,这个方法的源码如下:

    private static void handleCallback(Message message) { 
      message.callback.run();
    }

    从以上代码我们可以看到,这个方法就是简单的调用了Runnable对象的run方法让它开始运行。

    回到dispatchMessage方法的代码,若msg.callback为null,就会判断mCallback是否为null,若不为null则调用mCallback的handleMessage方法,否则调用handleMessage方法。实际上这两个handleMessage方法都是我们创建Handler对象时定义的消息处理函数,只不过分别对应了两种不同的创建Handler对象的方式。调用mCallback的handleMessage方法表示我们创建Handler对象时传入了一个实现了Callback接口的的对象,而调用handleMessage方法表示我们创建Handler对象时继承了Handler类并重写了handleMessage方法。那么mCallback是什么呢?让我们先看一下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 that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue;
      mCallback = callback;
      mAsynchronous = async;
     }

    我们可以看到,mCallback被赋值为我们传入的第一个参数callback,callback即为实现了Callback接口的对象,Callback接口中只有一个方法,那就是handleMessage方法。

    主线程的消息循环

    Android中的主线程也就是我们上面提到过的ActivityThread。我们上面介绍过,ActivityThread的main方法中会通过Looper.loop方法开启循环,相关源码如下:

    public static void main(String[] args) { 
       ... 
      Looper.prepareMainLooper(); 
      ... 
      Looper.loop(); 
      throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    通过以上代码我们可以看到主线程在初始化时确实通过Looper.loop方法开启了消息循环。那么主线程使用了哪个Handler来与MessageQueue进行交互呢?实际上它使用了ActivityThread.H,它的定义如下:

    rivate class H extends Handler { 
     public static final int LAUNCH_ACTIVITY = 100; 
     public static final int PAUSE_ACTIVITY = 101; 
     public static final int PAUSE_ACTIVITY_FINISHING= 102; 
     public static final int STOP_ACTIVITY_SHOW = 103; 
     public static final int STOP_ACTIVITY_HIDE = 104; 
     public static final int SHOW_WINDOW = 105; 
     public static final int HIDE_WINDOW = 106; 
     public static final int RESUME_ACTIVITY = 107;
     public static final int SEND_RESULT = 108;
     public static final int DESTROY_ACTIVITY = 109;
     public static final int BIND_APPLICATION = 110;
     public static final int EXIT_APPLICATION = 111;
     public static final int NEW_INTENT = 112;
     public static final int RECEIVER = 113;
     public static final int CREATE_SERVICE = 114;
     public static final int SERVICE_ARGS = 115;
     public static final int STOP_SERVICE = 116;
    
     ...
    
     public void handleMessage(Message msg) {
       ...
      switch (msg.what) {
        case LAUNCH_ACTIVITY: {
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
          final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
          r.packageInfo = getPackageInfoNoCheck(
              r.activityInfo.applicationInfo, r.compatInfo);
          handleLaunchActivity(r, null);
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case RELAUNCH_ACTIVITY: {
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
          ActivityClientRecord r = (ActivityClientRecord)msg.obj;
          handleRelaunchActivity(r);
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case PAUSE_ACTIVITY:
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
          handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
              (msg.arg1&2) != 0);
          maybeSnapshot();
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          break;
    
          ...
      }
    
    }

    从以上源码中我们可以看到,主线程使用的Handler中定义了一系列常量,代表了发生了各种事件(比如启动Activity、暂停Activity、显示Window)时应发给的主线程的消息标识。实际上这些消息是H在ApplicationThread中发送过来的。具体过程如下:ActivityThread通过ApplicationThread与AMS(Activity Manager Service)进行进程间通信(IPC)。AMS完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会通过H发送消息到ActivityThread的MessageQueue中,之后H的handlerMessage方法便会根据发来的消息进行相应的处理。这样就完成了将任务从ApplicationThread切换到ActivityThread的工作。

  • 相关阅读:
    Android Studio中使用AAR包
    Unity Hub无法添加新模块解决办法
    WTM(基于Vue)项目发布记录
    企业微信自建应用开发
    企业微信接口上传临时素材
    cfw for ubuntu
    行锁,间隙锁,快照读,当前读的理解。
    spring容器之ApplicationContext
    select_poll_epoll
    百万级抽奖系统——redis的延时双删——数据库与缓存的数据一致性问题分析
  • 原文地址:https://www.cnblogs.com/zhanghaiyang/p/7212879.html
Copyright © 2020-2023  润新知