Android个人笔记之Android的消息机制

概述

Handler在开发中最常用的就是拿来更新UI了。Handler的运行需要MessageQueue和Looper的支撑。Handler创建的时候会采用当前线程的Looper构造消息循环系统。但线程是默认没有Looper的,需要我们手动创建。除非是主线程,也就是UI线程,它就是ActivityThread。ActivityThread被建立的时候系统就会初始化Looper,所以主线程可以默认使用Handler。Handler创建完毕后,通过Handler的post方法将一个Runnable对象投递到Looper中处理,或者用handler的send方法发送消息,其实post方法最终也是通过send方法调用。

ThreadLocal

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,所以也只能在指定线程中获取相、应的数据,其他线程则没有办法。平常用到ThreadLocal的地方不多,但有时也挺好用。如果某些数据是以线程为作用域并且不同线程有不同数据副本时,就可以使用ThreaLocal。比如Looper。不同线程有不同的Looper,通过ThreaLocal就可以轻松实现Looper在不同线程中的存取。
我们演示一下ThreadLocal的用法。

private ThreadLocal<boolean> mBooleanThreadLocal = new ThreadLocal<boolean>();

接着在主线程,子线程1,子线程2中分别设置和获取它的值。

mBooleanThreadLocal.set(true);
Log.d("TAG","(Thread main)mBooleanThreadLocal ="+mBooleanThreadLocal.get());

new Thread(){
@override
public void run(){
mBooleanThreadLocal.set(false);
Log.d("TAG","(Thread1)mBooleanThreadLocal ="+mBooleanThreadLocal.get());
  }
}.start();

new Thread(){
@override
public void run(){
mBooleanThreadLocal.set(false);
Log.d("TAG","(Thread2)mBooleanThreadLocal ="+mBooleanThreadLocal.get());
  }
}.start();

上面代码输出为

(Thread main)mBooleanThreadLocal =true;
(Thread1)mBooleanThreadLocal =false;
(Thread2)mBooleanThreadLocal =null;

看完你就知道ThreadLocal的神奇之处。同一个ThreadLocal对象得到的值却不同。其实是因为不同线程调用ThreadLocal的get方法,ThreadLocal内部会从各自线程取出一个数组,再从数组中根据当前ThreadLocal的索引查找对应的value值。显然不同线程的数组是不同的。所以ThreadLocal在不同线程中维护一套数据的副本是互不干扰的。

MessageQueue

MessageQueue主要包括两个操作,插入和读取,对应的方法为enqueueMessage和next。enqueueMessage的作用就是往消息队列中插入一条消息。next的作用是取出一则消息读取,并从消息队列中移除。MessageQueue虽然叫做消息队列,实际上却是用单链表的数据结构,因为执行插入和删除效率比较高。

Looper

Looper创建时MessageQueue会跟着创建。并把当前线程的对象保存起来。
在线程中使用Looper的方式如下,注意这里的Handler并不能更新UI。

new Thread(){
@override
public void run(){
Looper.prepare();//创建Looper对象
Handler handler = new Handler();
Looper.loop();//Looper开始工作
  }
}.start();

Looper还提供了退出的方法,quit和quitSafely。调用quit方法会直接退出Looper,而quitSafely只是设定一个退出标记,然后等消息队列中的已有消息全部处理完毕后才退出。Looper退出后,Handler发送的消息会失败。如果在子线程中手动创建Looper,那么所有事情完毕后应调用quit方法终止消息循环。而如果退出Looper后,线程也就被终止了。
Looper.loop()是一个死循环,只有MessageQueue的next方法返回了null才能跳出循环。当Looper的quit方法调用时Looper会调用MeassageQueue的quit方法来通知消息队列退出,此时next方法就会返回null。

Handler的工作原理

先看一下发送消息过程的源码

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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  msg.target = this;    
  if (mAsynchronous) {        
    msg.setAsynchronous(true);    
  }    
  return queue.enqueueMessage(msg, uptimeMillis);
}

可以发现,Handler发送消息的过程只是向消息过程中插入一则消息。 MessageQueue的next方法会返回这条消息给Looper,Looper收到后开始处理,最终由Looper交给Handler处理。,这时Handler的dispatchMessage方法被调用。

public void dispatchMessage(Message msg) { 
  if (msg.callback != null) {        
    handleCallback(msg);    
  } else {  
    if (mCallback != null) {            
      if (mCallback.handleMessage(msg)) {                
        return;            
      }        
    }
    //处理sendMessage();方法发送的消息        
    handleMessage(msg);    
  }
}

看上面的代码,Message的callback是一个Runnable对象,就是post方法传递过来的Runnable对象。handleCallback的方法很简单,就是执行run方法。

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

Callback是一个接口。定义为

public interface Callback { 
  public boolean handleMessage(Message msg);
}

Callback的意义在于可以创建Handler的实例而不用派生Handler的子类。使用方式如下

Handler handler= new Hanlder(Callback);

平常我们一般都是创建Handler的子类并重写handleMessage方法。Callback为我们提供了另一种方式。

我们借助一张图片来理解一下Hanlder消息处理的流程,相信不难理解。

Handler消息处理流程

以上总结大部分来自《Android开发艺术探索》,想要理解更仔细请翻阅原书。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容