您当前的位置: 首页 >  android

科技D人生

暂无认证

  • 0浏览

    0关注

    1550博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

科技D人生 发布时间:2016-09-12 09:38:45 ,浏览量:0

什么是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 mIntegerThreadLocal =
    new ThreadLocal();

然后创建两个子线程,并在不同的线程中为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("            
关注
打赏
1662604032
查看更多评论
0.1357s