Handler消息通信机制完全解析篇

本文有借鉴过网络上优秀的文章,加上自己的总结。

为什么要使用Handler?

为了保证Android的UI操作是线程安全的,Android规定只允许UI线程修改UI组件。
但在实际开发中,必然会遇到多个线程并发操作UI组件的时候,这将导致UI操作的线程不安全。
问题在于,如何同时满足:

  1. 保证线程安全
  2. 使多个线程并发操作UI组件
    Handler消息机制可以解决这个问题。

是否熟悉以下相关概念?YES的话本段可略过!

  • 主线程
    定义:程序第一次启动时,Android会同时启动一条主线程(MainThread)
    作用:主线程主要负责处理与UI相关的事件,所以主线程又叫UI线程
  • Message
    定义:Handler接收和处理的消息对象,可以理解为封装了某些数据的对象
    使用:后台线程处理完数据后需要更新UI,可发送一条包含更新信息的Message给UI线程
  • Message Queue
    定义:消息队列
    作用:用来存放通过Handler发送的消息,按照先进先出的顺序排列
  • Handler
    定义:Handler是Message的主要处理者
    作用:
    1.负责将Message添加到消息队列中
    2.处理Looper分派过来的Message
  • Looper
    定义:循环器,不断的取出MessageQueue中的消息并传递给Handler
    作用:循环取出MessageQueue的Message,将取出的Message交付给相应的Handler
    PS:每个线程只能拥有一个Looper,但一个Looper可以和多个线程的Handler绑定,也就是说多个线程可以往一个Looper所持有的MessageQueue中发送消息,这给我们提供了线程之间通信的可能。

Handler工作流程

  • 异步通信的准备

1.Looper对象的创建和实例化
Looper.prepare()
Looper.loop()

2.MessageQueue队列的创建
Looper.prepare()->new Looper()
一个线程只会有一个Looper实例,同时一个Looper实例也只有会创建一个MessageQueue。

3.Handler实例化
Handler是和线程绑定在一起的,初始化Handler的时候一般通过指定Looper对象从而绑定相应线程,即给Handler指定Looper对象相当于绑定到了Looper对象所在的线程中。Handler的消息处理回调会在那个线程中执行。

实例化Handler的方法:
1) 通过Looper.mylooper()得到当前线程的Looper对象,或通过Loop.getMainLooper()获得当前进程的主线程的Looper对象。
2)不指定Looper对象,这个Handler绑定到创建这个线程的线程上,消息处理回调也就在在创建线程中执行。
当Handler初始化时,可通过构造方法自动关联Looper和相应的MessageQueue。

  • 消息发送
    Handler将消息发送到消息队列中

  • 消息循环
    Looper执行Looper.loop()进入消息循环,在循环过程中不断从该MessageQueue取出消息,并将取出的消息派发给创建该消息的Handler...

  • 消息处理
    通过Handler的dispatchMessage(msg)方法,即回调handleMessage(msg)处理消息。如果时Handler的post方法发送的消息,则会在对应的run()方法中处理回调。

源码分析

Looper.prepare()是在做什么?

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //一个线程只能持有一个Looper实例,sThreadLocal保存线程持有的looper对象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

        //sThreadLocal是一个ThreadLocal变量,用于在一个线程中存储变量,这里Looper变量就存放在ThreadLocal里
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper的构造方法在做什么?

    private Looper(boolean quitAllowed) {
        //创建Looper时,会自动创建一个与之匹配的MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper.loop()方法作用是什么?

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //myLooper()方法是返回sThreadLocal存储的Looper实例
        final Looper me = myLooper();

        //me==null就会抛出异常,说明looper对象没有被创建,
        //也就是说loop方法必须在prepare方法之后运行,消息循环必须要先在线程中创建Looper实例
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //获取Looper实例中的消息队列mQueue
        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 (;;) {
            //next()方法用于取出消息队列中的消息,如果取出的消息为空,则线程阻塞
            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.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();
        }
    }

综上,可以看出Looper的作用是:
1.实例化Looper对象本身(prepare()方法),创建与之对应的MessageQueue(looper()构造方法)
2.loop()方法不断从MessageQueue中取消息,派发给Handler,然后调用相应Handler的dispatchMessage()方法进行消息处理。

那么,Handler是如何和Looper绑定且从MessageQueue中获取消息执行的呢?
来看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());
            }
        }
      
        //Looper.myLooper()获得了当前线程保存的Looper实例
        mLooper = Looper.myLooper();

        //如果没有looper实例就会抛出异常,这说明一个没有创建looper的线程中是无法创建一个Handler对象的;
        //子线程中创建一个Handler时需要创建Looper,且开启循环才能使用这个Handler
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

        //获取looper实例的MessageQueue,保证handler实例与looper实例的MessageQueue关联
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

以上,当Handler构造函数初始化时,自动关联looper和对应的MessageQueue。

Handler向MessageQueue发送消息的代码sendMessage执行后,是发生了什么?

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

    //sendMessage最后的最后调用到了enqueueMessage方法
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // msg.target = this,也就是把当前handler作为msg的target属性
        // 在Looper的loop()方法中会取出msg,然后执行msg.target.dispatchMessage(msg)去处理消息
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }

        //handler发出的消息最终会保存到消息队列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Handler的post()和sendMessage()有什么不同?

    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    private static Message getPostMessage(Runnable r) {
        //使用obtain方法创建一个Message对象,因为Message内部维护了一个Message池用于Message复用,避免使用new重新分配内存
        Message m = Message.obtain();
        //将创建的Runnable对象作为callback属性,赋值给message
        m.callback = r;
        //返回一个message对象
        return m;
    }

继续向下调用:

    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的post()方法和sendmessage()方法一样,最终都调用了sendMessageAtTime,然后调用了enqueueMessage方法,将msg.target赋值为handler,然后将Handler加入到MessageQueue中。

But,在使用post方法时,将创建的Runnable对象作为callback属性赋值给了message,那么该如何执行handler的回调方法呢?请看如下代码:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        //如果msg.callback不为null,则执行handleCallback回调,也就是我们的Runnable里的回调
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    //Runnable的run()方法中执行回调函数
    private static void handleCallback(Message message) {
        message.callback.run();
    }

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

可以看到dispatchMessage()方法中调用了handleMessage()方法,但是是一个空方法,在创建Handler时通过复写handleMessage()方法来实现我们需要的消息处理方式,根据msg.what标识进行区分处理。

为什么在UI线程中使用Handler时不需要创建Looper?

当一个Android应用程序启动时,会创建一个主线程ActivityThread,在ActivityThread中有一个静态的main()方法,也是应用程序的入口点。

 public static void main(String[] args) {
        ...
        //  通过prepareMainLooper方法为主线程创建一个looper
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        // 开启消息循环
        Looper.loop();
        ...
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        //prepare()方法用于创建一个looper对象
        //主线程的消息循环是不允许退出的
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

举几个例子来加强理解

子线程和主线程通信,使用sendMessage()更新UI

/**
 * Created by Kathy on 17-2-15.
 * 子线程与主线程通信
 */

public class MainActivity extends Activity {

    private static final int INT_ONE = 1;

    //在主线程里创建一个mHandler实例
    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case INT_ONE:
                    String text = (String) msg.obj;
                    Toast.makeText(MainActivity.this, text,Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
            return false;
        }
    });


    class ThreadOne extends Thread{
        @Override
        public void run() {
            //创建需要发送的消息,注意使用的是主线程的mHandler,所以也在主线程的handleMessage()中回调
            Message msg = Message.obtain();
            msg.what = INT_ONE; //标识消息
            msg.obj = "One"; //存放消息
            mHandler.sendMessage(msg);
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //启动子线程
        new ThreadOne().start();
    }
}

启动这个Activity,执行子线程,在子线程中通过UI线程的mHandler发送消息,会发现最终会执行到handleMessage()方法,弹出Toast。

使用Handler.post()更新UI

/**
 * Created by Kathy on 17-2-16.
 * 使用Handler.post()通信
 */

public class MainActivityTwo extends Activity {

    private Handler mHandler;

    //使用mHandler.post()方法只需要在run()方法中写回调内容即可
    class ThreadTwo extends Thread {
        @Override
        public void run() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivityTwo.this, "Two", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 实例化Handler,无需指定looper,mHandler自动绑定当前线程(UI线程)的Looper和MessageQueue
        mHandler = new Handler();

        new ThreadTwo().start();
    }
}

主线程如何向子线程中发送消息的呢?

/**
 * Created by Kathy on 17-2-16.
 */

public class MainActivityThree extends Activity {

    private Handler mChildHandlerOne;
    private Handler mChildHandlerTwo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("Kathy", "mainLooper = " + getMainLooper());

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                //子线程中实例化mChildHandlerOne
                mChildHandlerOne = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        Log.d("Kathy", "mChildHandlerOne received Message!");

                        // 在此线程中,用mChildHandlerTwo发送消息
                        // 消息被加入到mChildHandlerTwo的消息队列中,进而会执行到mChildHandlerTwo的回调方法
                        // 这就完成了子线程向子线程之前发消息的可能
                        Message msg2 = Message.obtain();
                        msg2.what = 1;
                        mChildHandlerTwo.sendMessage(msg2);
                        return false;
                    }
                });
                Log.d("Kathy", "mChildHandlerOne = " + mChildHandlerOne.getLooper());
                Looper.loop();

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                //子线程中实例化mChildHandlerTwo
                mChildHandlerTwo = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        Log.d("Kathy", "mChildHandlerTwo received Message!");
                        return false;
                    }
                });
                Log.d("Kathy", "mChildHandlerTwo = " + mChildHandlerTwo.getLooper());
                Looper.loop();
            }
        }).start();

        // 点击按钮,如果两个子线程中的handler都被实例化后,在主线程中,用mChildHandlerOne发送消息
        // 消息会加入到mChildHandlerOne的消息队列中,进而会执行到mChildHandlerOne的回调方法
        // 这就完成了在主线程中向子线程发送消息的可能
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mChildHandlerOne && null != mChildHandlerTwo) {
                    Message msg1 = Message.obtain();
                    msg1.what = 1;
                    mChildHandlerOne.sendMessage(msg1);
                }
            }
        });
    }
}

Log输出如下:

02-17 15:41:14.998 22131-22131/com.demo.kathy.demo D/Kathy: mainLooper = Looper (main, tid 1) {56801a8}
02-17 15:41:15.007 22131-22170/com.demo.kathy.demo D/Kathy: mChildHandlerOne = Looper (Thread-5565, tid 5565) {dba14c1}
02-17 15:41:15.009 22131-22171/com.demo.kathy.demo D/Kathy: mChildHandlerTwo = Looper (Thread-5566, tid 5566) {8d3d466}
02-17 15:41:28.490 22131-22170/com.demo.kathy.demo D/Kathy: mChildHandlerOne received Message!
02-17 15:41:28.490 22131-22171/com.demo.kathy.demo D/Kathy: mChildHandlerTwo received Message!

若有错误,请及时指出,感谢阅读!

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

推荐阅读更多精彩内容