当前位置: 移动技术网 > 移动技术>移动开发>Android > 全面总结Android中线程的异步处理方式

全面总结Android中线程的异步处理方式

2019年07月24日  | 移动技术网移动技术  | 我要评论

一、概述
handler 、 looper 、message 这三者都与android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
说了这一堆,那么和handler 、 looper 、message有啥关系?其实looper负责的就是创建一个messagequeue,然后进入一个无限循环体不断从该messagequeue中读取消息,而消息的创建者就是一个或多个handler 。

二、源码解析
1、looper
对于looper主要是prepare()和loop()两个方法。
首先看prepare()方法

public static final void prepare() { 
  if (sthreadlocal.get() != null) { 
   throw new runtimeexception("only one looper may be created per thread"); 
  } 
  sthreadlocal.set(new looper(true)); 
} 

sthreadlocal是一个threadlocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个looper的实例放入了threadlocal,并且2-4行判断了sthreadlocal是否为null,否则抛出异常。这也就说明了looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个looper实例~相信有些哥们一定遇到这个错误。
下面看looper的构造方法:

private looper(boolean quitallowed) { 
  mqueue = new messagequeue(quitallowed); 
  mrun = true; 
  mthread = thread.currentthread(); 
} 

在构造方法中,创建了一个messagequeue(消息队列)。
然后我们看loop()方法:

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

第2行:

public static looper mylooper() {
return sthreadlocal.get();
}

方法直接返回了sthreadlocal存储的looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。
第6行:拿到该looper实例中的mqueue(消息队列)
13到45行:就进入了我们所说的无限循环。
14行:取出一条消息,如果没有消息则阻塞。
27行:使用调用 msg.target.dispatchmessage(msg);把消息交给msg的target的dispatchmessage方法去处理。msg的target是什么呢?其实就是handler对象,下面会进行分析。
44行:释放消息占据的资源。

looper主要作用:
(1)与当前线程绑定,保证一个线程只会有一个looper实例,同时一个looper实例也只有一个messagequeue。
(2)loop()方法,不断从messagequeue中去取消息,交给消息的target属性的dispatchmessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(messagequeue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:handler登场了。

2、handler
使用handler之前,我们都是初始化一个实例,比如用于更新ui线程,我们会在声明的时候直接初始化,或者在oncreate中初始化handler实例。所以我们首先看handler的构造方法,看其如何与messagequeue联系上的,它在子线程中发送的消息(一般发送消息都在非ui线程)怎么发送到messagequeue中的。

public handler() { 
  this(null, false); 
} 
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; 
 } 

14行:通过looper.mylooper()获取了当前线程保存的looper实例,然后在19行又获取了这个looper实例中保存的messagequeue(消息队列),这样就保证了handler的实例与我们looper实例中messagequeue关联上了。

handler 常用方法:

(1)post(runnable)

(2)postattime(runnable,long)

(3)postdelayed(runnable long)

(4)sendemptymessage(int)

(5)sendmessage(message)

(6)sendmessageattime(message,long)

(7)sendmessagedelayed(message,long)

以上post类方法允许你排列一个runnable对象到主线程队列中, sendmessage类方法, 允许你安排一个带数据的message对象到队列中,等待更新.

一般运行逻辑:
点击button --- > 启动一条新线程,用来处理数据 ---- >数据处理完毕,通过handler返回 ----- > handler里面接收返回的数据,进行ui更新等处理。


然后看我们最常用的sendmessage方法

public final boolean sendmessage(message msg) 
 { 
  return sendmessagedelayed(msg, 0); 
 } 

public final boolean sendemptymessagedelayed(int what, long delaymillis) { 
  message msg = message.obtain(); 
  msg.what = what; 
  return sendmessagedelayed(msg, delaymillis); 
 } 

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

辗转反则最后调用了sendmessageattime,在此方法内部有直接获取messagequeue然后调用了enqueuemessage方法,我们再来看看此方法:

private boolean enqueuemessage(messagequeue queue, message msg, long uptimemillis) { 
  msg.target = this; 
  if (masynchronous) { 
   msg.setasynchronous(true); 
  } 
  return queue.enqueuemessage(msg, uptimemillis); 
 } 

enqueuemessage中首先为meg.target赋值为this,【如果大家还记得looper的loop方法会取出每个msg然后交给msg,target.dispatchmessage(msg)去处理消息】,也就是把当前的handler作为msg的target属性。最终会调用queue的enqueuemessage的方法,也就是说handler发出的消息,最终会保存到消息队列中去。

现在已经很清楚了looper会调用prepare()和loop()方法,在当前执行的线程中保存一个looper实例,这个实例会保存一个messagequeue对象,然后当前线程进入一个无限循环中去,不断从messagequeue中读取handler发来的消息。然后再回调创建这个消息的handler中的dispathmessage方法,下面我们赶快去看一看这个方法:

public void dispatchmessage(message msg) { 
  if (msg.callback != null) { 
   handlecallback(msg); 
  } else { 
   if (mcallback != null) { 
    if (mcallback.handlemessage(msg)) { 
     return; 
    } 
   } 
   handlemessage(msg); 
  } 
 } 

可以看到,第10行,调用了handlemessage方法,下面我们去看这个方法:

/** 
 * subclasses must implement this to receive messages. 
 */ 
 public void handlemessage(message msg) { 
 } 
 

可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handlemessage方法,然后根据msg.what进行消息处理。
例如:

private handler mhandler = new handler() 
 { 
  public void handlemessage(android.os.message msg) 
  { 
   switch (msg.what) 
   { 
   case value: 
     
    break; 
 
   default: 
    break; 
   } 
  }; 
 }; 

到此,这个流程已经解释完毕,让我们首先总结一下
(1)首先looper.prepare()在本线程中保存一个looper实例,然后该实例中保存一个messagequeue对象;因为looper.prepare()在一个线程中只能调用一次,所以messagequeue在一个线程中只会存在一个。
(2)looper.loop()会让当前线程进入一个无限循环,不端从messagequeue的实例中读取消息,然后回调msg.target.dispatchmessage(msg)方法。
(3)handler的构造方法,会首先得到当前线程中保存的looper实例,进而与looper实例中的messagequeue想关联。
(4)handler的sendmessage方法,会给msg的target赋值为handler自身,然后加入messagequeue中。
(5)在构造handler实例时,我们会重写handlemessage方法,也就是msg.target.dispatchmessage(msg)最终调用的方法。
好了,总结完成,大家可能还会问,那么在activity中,我们并没有显示的调用looper.prepare()和looper.loop()方法,为啥handler可以成功创建呢,这是因为在activity的启动代码中,已经在当前ui线程调用了looper.prepare()和looper.loop()方法。

3、handler post
今天有人问我,你说handler的post方法创建的线程和ui线程有什么关系?
其实这个问题也是出现这篇博客的原因之一;这里需要说明,有时候为了方便,我们会直接写如下代码:

mhandler.post(new runnable() 
  { 
   @override 
   public void run() 
   { 
    log.e("tag", thread.currentthread().getname()); 
    mtxt.settext("yoxi"); 
   } 
  }); 

然后run方法中可以写更新ui的代码,其实这个runnable并没有创建什么线程,而是发送了一条消息,下面看源码:

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

可以看到,在getpostmessage中,得到了一个message对象,然后将我们创建的runable对象作为callback属性,赋值给了此message.
注:产生一个message对象,可以new  ,也可以使用message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为message内部维护了一个message池用于message的复用,避免使用new 重新分配内存。

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

最终和handler.sendmessage一样,调用了sendmessageattime,然后调用了enqueuemessage方法,给msg.target赋值为handler,最终加入messagqueue.
可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
其实上面已经贴过代码,就是dispatchmessage方法:

public void dispatchmessage(message msg) { 
  if (msg.callback != null) { 
   handlecallback(msg); 
  } else { 
   if (mcallback != null) { 
    if (mcallback.handlemessage(msg)) { 
     return; 
    } 
   } 
   handlemessage(msg); 
  } 
 } 

 
第2行,如果不为null,则执行callback回调,也就是我们的runnable对象。

好了,关于looper , handler , message 这三者关系上面已经叙述的非常清楚了。
最后来张图解:

2016419151736389.png (867×404)

希望图片可以更好的帮助大家的记忆~~

三、补充
其实handler不仅可以更新ui,你完全可以在一个子线程中去创建一个handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建handler实例的线程中运行。

eg:

new thread() 
  { 
   private handler handler; 
   public void run() 
   { 
 
    looper.prepare(); 
     
    handler = new handler() 
    { 
     public void handlemessage(android.os.message msg) 
     { 
      log.e("tag",thread.currentthread().getname()); 
     }; 
    }

  }

android不仅给我们提供了异步消息处理机制让我们更好的完成ui的更新,其实也为我们提供了异步消息处理机制代码的参考~~不仅能够知道原理,最好还可以将此设计用到其他的非android项目中去~~

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网