Android Handler消息机制原理及总结

Android中的消息机制主要就是指Handler的消息机制,Handler相信大家已经非常熟悉了,它可以将一个任务切换到Handler所在的线程中去执行,开发中,当我们在子线程做了一些操作后需要更新UI,由于Android不允许在子线程中访问UI控件,所以我们一般都会使用handler来实现。

Handler的机制需要MessageQueue、Looper和Message的支持。他们在消息机制中各扮演了不同的角色

Handler:负责消息的发送和接收处理
MessageQueue:消息队列,一个消息存储单位,经常需要进行增减,内部使用的是单链表的结构
Looper:消息循环。会不停地从MessageQueue中取消息,如果有新消息就会立刻处理,否则就一直阻塞在那里
Message:消息载体

下面通过一段简单的代码来分析整个执行过程。

 private TextView textView;
 private Handler handler = new Handler(){
     @Override
     public void handleMessage(Message msg) {
          super.handleMessage(msg);
          textView.setText("消息处理.....");
     }
 };
@Override
protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.mytv);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟耗时操作
                SystemClock.sleep(3000);
                handler.sendMessage(new Message());
            }
        }).start();

    }

就是在子线程中做了一些耗时操作后,通过Handler发送消息去更新UI

Hanlder是怎么接受到消息的呢?

Looper

Looper几个主要的方法

  • Looper.prepare():为当前线程创建一个Looper
  • Looper.loop():开启消息循环
  • Looper.getMainLoop():可以在任何地方获取到主线程的
  • Looper.quit():直接退出Looper
  • quitSafely():设置一个标记,把消息队列的所有消息处理完后才会退出
//创建一个新的Looper并放到当前线程的ThreadLocal中去
 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));
    }

再看看Looper的构造方法,发现,Looper中保存有一个MessageQueue

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

loop()该方法是一个死循环,会不断从MessageQueue中去取消息,当获取到消息后,将交由Handler去处理

public static void loop() {
    //myLooper方法会从ThreadLocal中获取到当前线程的Looper
    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;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
        //不断的去MessageQueue中取消息
        Message msg = queue.next();
        if (msg == null) {
            return;
        }    
       Printer logging = me.mLogging; 
       if (logging != null) {
          logging.println(">>>>> Dispatching to " + msg.target + " " +                msg.callback + ": " + msg.what);    } 
      //调用发送该消息的Handler的dispatchMessage方法处理该消息
      //这里的msg.target == Handler,是在Handler发送消息的时候进行赋值的
       msg.target.dispatchMessage(msg); 
       if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);    } 
       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();
}

Looper大概就是这么一回事,每条线程绑定一个Looper(子线程需要自己调用Loop.prepare()),保证每条线程都只有一个Looper实例
随后调用Looper.loop开启消息循环,进入堵塞状态,等待消息的到来

MessageQueue

相对来说就很简单来,就两个操作,插入和读取。可以看成一个消息容器

Handler

先来看看他的构造方法

```java
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());        }   
 } 
  //获取当前线程中的Looper
   mLooper = Looper.myLooper();
    if (mLooper == null) { 
    //如果当前线程没有Looper就会抛出异常。
       throw new RuntimeException(            "Can't create handler inside thread that has not called Looper.prepare()"); 
   } 
   //获取Looper中的MessageQueue,可以看出,Looper中为每个线程维护了一个Message
   mQueue = mLooper.mQueue; 
   mCallback = callback;
   mAsynchronous = async;
}

接来下看看Handler发送消息的方法,追踪senndmessage到最底层的方法就是enqueueMessage。

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

主要做了两个操作
msg.target = this;将发送的Message的target指向当前Handler。
ueue.enqueueMessage(msg, uptimeMillis);往当前线程的MessageQueue插入一条消息

前面在Looper.looper()方法中我们看到msg.target.dispatchMessage(msg); 获取到消息后,调用Handler的dispatchMessage方法进行消息处理。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);//这里是Message的Callback,也就是开启我们在postDelayed传入的Runnable
    } else {
      //mCallback:构造器传入的实现
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                   return;
            }
        }
      //handler自己的handler,一般由派生之类来实现
        handleMessage(msg);
    }
}

可以看到Handler最终将调用handlerMessage处理消息,当然这里的handlerMessage处理是有优先级的

下面我们总结下流程
流程总结:
1,首先调用Looper.prepare()方法,会创建一个Looper实例,该实例包含一个MessageQueue,并将该实例保存在当前线程中Threadlocal
2,调用Looper.loop()开始消息循环,不断地向MessageQueue中读取消息,并调用msg.target.dispatchMessage(msg);来处理消息
3,构建Handler的时候,会先获取到当前Handler所在线程的Looper并得到其中的 MessageQueue
4,使用Handler发送消息的时候,会将一个Message到保存当前线程Looper中的MessageQueue
5,当Looper.loop()获取到消息的时候,调用msg.target.dispatchMessage(msg)来处理消息,其实Message.target = handler。也就是调用Handler的dispatchMessage来处理
6,Handler的dispatchMessage最终回去调用handlerMessage方法。到这里就知道,其实Handler的handler在哪条线程执行,取决于构建Handler时所使用的是哪条线程保存的Looper,因为handlerMessage其实是Looper去调用的。

下面通过一张图来表述下具体流程


这里写图片描述

前面说到,Handler必须有Looper才能执行,或者会就抛出异常,当前我们在日常使用中,直接在Activity中定义Handler就可以用啦,并没有说调用Looper.prepare来进行初始化操作。

其实UI线程默认已经做了这些操作了。对于我们来说是隐形的

大家都知道Android的程序入口是ActivityThread类,他有个main方法,我们看看他做了什么

 public static void main(String[] args) {
        //省略。。。。

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到main方法主要就是消息循环,当main方法结束了,你的程序也就退出了

可能有人会问?UI线程已经做了Looper.loop()了?loop不是一个死循环堵塞状态吗?为什么我们程序还能跑起来。

既然main方法只是做了消息循环,那么我们Activity的生命周期的方法是怎么执行的呢?
在ActivityThread中有个Handler,Activity的生命周期方法在里面均有case,也就说是我们的代码其实就是在这个循环里面去执行的,自然不会阻塞了

当前我还有个疑惑。程序是在哪给ActivityThread的Handler发消息的呢?这个我暂时还不清楚。。还得继续学习啊。

UI线程已经帮我们做了这些工作了,但是如果我们自己new Thread的话,需要在子线程中使用Handler的话,还是要自己来实现的。
对此,Android也为我们提供了一个HandlerThread类,方便我们快速的实现需求。
HandlerThread继承了Thread

public class HandlerThread extends Thread

代码相当简单,
看看run方法

 @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

如果对前面的Handler机制理解了话,这里是不是一目了然了呢?

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

推荐阅读更多精彩内容