Handler、Message、MessageQueue、Looper协作简析

为什么使用Handler

在Java里面线程间通信的方式有好些方式,在Android里我们常用的方式之一是通过Handler,做过点Android开发的都知道,在Android里对界面上的一些操作都需要放在主线程或者说UI线程中,其实不在主线程中也能操作,当然有特殊要求,具体请自行百度。总之,通过Handler我们能比较方便的在子线程和主线程间通信。

相关类

android.os.Handler
发送消息和处理消息都需要通过它。它会与一个Looper相关联。

android.os.Looper
通常我们叫轮询器,负责消息分发的。一个Looper只能和一个线程关联。

android.os.MessageQueue
消息队列,放消息的,每个Looper和线程最多只会有一个消息队列。

android.os.Message
它可以存放一些数据并传递给相应消费线程。

Handler的简单示例

public class MainActivity extends AppCompatActivity {

    private TextView mTvTip;
    private Button mBtnDownLoad;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // what为0表示下载完成
                case 0:
                    mTvTip.setText("苍老师的电影下载好啦,还不快去鉴赏。");
                    break;
            }
        }
    };

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

        mTvTip = (TextView) findViewById(R.id.tv_tip);
        mBtnDownLoad = (Button) findViewById(R.id.btn_download);

        mBtnDownLoad.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        // 睡眠5秒钟模拟下载过程
                        SystemClock.sleep(1000 * 5);

                        Message msg = mHandler.obtainMessage();
                        // 下载完成,将msg的what值设定为0
                        msg.what = 0;
                        msg.sendToTarget();
                    }
                }).start();
            }
        });
    }
}

代码很简单,界面就一个TextView和一个Button,给按钮设定了点击监听,点击的时候起了个线程去下载老师的艺术片儿,下载完后在界面上用文字通知咱们去鉴赏。

Handler的模式

从上面我们的示例代码可以看出,子线程将一个msg传递到主线程去处理,就相当于一个生产者消费者模式,子线程生产,主线程消费。如下图所示:

1、插入,生产者线程(对于我们上面的示例就是子线程)通过消费者线程(对于我们上面的示例就是UI线程)上的Handler将msg插入到消息队列里。

2、检索,运行在消费者线程的Looper会按顺序检索MessageQueue里的消息。

3、分发,消费者线程上的Handler负责处理msg,一个线程可以有多个Handler来处理msg,Looper会将相应的msg分发给对应的Handler来处理。

子线程使用Handler

之前我们的例子是在UI线程里实例化Handler的,所以UI线程可以接收到消息,那如何发送消息到子线程呢?我们试试在子线程里实例化Handler:

public class MainActivity extends AppCompatActivity {

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

        new DownloadThread().start();
    }

    private class DownloadThread extends Thread {

        public Handler mHandler;

        @Override
        public void run() {
            mHandler = new Handler();
        }
    }
}

按之前的猜想,直接在子线程里实例化Handler,结果报错如下:

子线程创建Handler报错

错误显示在实例化Handler之前需要先调用Looper.prepare()。我们来看一下Looper.prepare()的部分源码:

public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
        // 检查线程局部变量是否已经保存了一个Looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 创建一个Looper对象并放到当前线程局部变量中
        sThreadLocal.set(new Looper(quitAllowed));
}

通过源码我们能发现prepare()方法就是将一个Looper对象放到当前线程的局部变量中,从它抛出的异常信息我们也能验证了之前说的一个线程只能有一个Looper对象。这和我们在实例化Handler对象需要先调用prepare()有什么关系呢?不难猜出是实例化Handler的时候需要用到当前线程的Looper对象,我们暂且相信是这样的,后面验证。

我们修改一下代码再试试:

public class MainActivity extends AppCompatActivity {

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

        mBtnDownload = (Button) findViewById(R.id.btn_download);

        final DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();

        mBtnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Message msg = downloadThread.mHandler.obtainMessage();
                msg.what = 0;
                msg.sendToTarget();
            }
        });
    }

    private class DownloadThread extends Thread {

        public Handler mHandler;

        @Override
        public void run() {
            // 先实例化一个Looper放到当前线程的局部变量
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case 0:
                            Log.e("fiend", "接收到UI线程发过来的信息了");
                            break;
                    }
                }
            };
        }
    }
}

代码很简单,点击按钮的时候向子线程发送消息,如果子线程收到消息会在Logcat里打印出一条log,但是点击按钮后并没有看到打印的log,这,这就很尴尬了。。。

通过我们写的代码,能够知道已经向子线程发送消息了,为毛子线程没反应呢,通过上面的生产者消费者模式的图我们可以知道,消息的检索是由Looper来做的,是不是Looper哪里出了问题,导致它没有检索到我们发送过来的消息?我们的代码只是调用了Looper.prepare()方法,而这个方法只是创建了一个Looper对象放到当前线程的局部变量中,是不是Looper还有个开关来控制它开始检索消息?我们来看看Looper的源码先,找了一阵后我们在Looper源码里发现了这么个方法:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        ...// 省略部分代码

        for (;;) {
            ...// 省略部分代码

            msg.target.dispatchMessage(msg);

            ...// 省略部分代码
        }
    }

从loop()方法的注释我们就能知道这个方法是开启消息轮询的,主要是有个for的死循环一直调用target来分发消息,从Message的源码可以知道target就是Handler对象。我们再次修改一下代码:

private class DownloadThread extends Thread {

        public Handler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case 0:
                            Log.e("fiend", "接收到UI线程发过来的信息了");
                            break;
                    }
                }
            };
            // 增加调用这个方法来开始消息轮询
            Looper.loop();
        }
    }

在实例化Handler后我们调用了Looper.loop()方法来开始消息的轮询,结果输出如下:

子线程收到消息

终于正常收到UI线程发过来的消息了。

停止Looper

上面我讲了子线程如何接收UI线程发送过来的消息。但是这里有个问题。如何结束这个子线程?

就像我们上面的下载线程,当苍老师的艺术片下载完了后,其实我们就已经不需要这个子线程了,但是从之前Looper的源码里我们知道它是个死循环在轮询消息,这样就导致我们的子线程无法正常结束了,这,这就有点浪费资源了。。。

我们继续查看Looper的源码,看一下是否有相关代码是关于如何退出死循环的。发现了这两个方法:

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

从方法的名字我们能知道的是一个是退出,一个是安全退出,通过它们的注释我们也能知道,它们就是退出Looper的方法,至于quit()方法是一开始就有的方法,但可能不安全,quitSafely()方法则是API 18(Android 4.3)后才有的方法,它能安全退出Looper.

这样我们就能在子线程做完所有工作后调用Looper.myLooper().quit()方法,这样子线程就能正常结束了。

Handler对象实例化过程

前面我们留有一个疑问,为什么实例化Handler的时候需要先实例化一个Looper才行,我们来一探究竟。

Handler部分源码如下:

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    ...// 省略部分代码

    // 获取当前线程的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    ...// 省略部分代码
}

从上面的构造方法中我们看到之前在没调用Looper.prepare()就实例化Handler所抛出的异常信息,这也解释了为什么要先实例化Looper后再实例化Handler

到这里我们应该会有个疑问,刚开始我们从子线程发消息到主线程的例子中,我们实例化Handler的时候并没有调用Looper.prepare()啊,为毛没有报错?这,这就有点尴尬了…

UI线程的Looper

继续查看Looper的源码,发现了这么一个方法:

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
}

从名字大概就能猜到,这是创建主Looper的方法,而且抛出的异常信息也能证明这一点。我们来看看是在哪里调用的这个方法:

通过查找调用的地方发现,在程序入口main()方法里调用这个方法,所以这就解释了为什么我们在UI线程实例化Handler的时候没调用Looper.prepare()方法也没有报错,因为早就已经在入口处创建了UI线程的Looper了,而且还是调用的Looper.prepareMainLooper()方法,当然在这个方法里我们也看到它,它还是调用的prepare(false)创建的。

注意,我们在子线程创建Looper的时候是直接调用的prepare()方法,并没有带参数,我们来看一下它的代码:

public static void prepare() {
    prepare(true);
}

原来方法里也是带参数调用的,但是UI线程带的参数是false,而别的线程带的参数是true,从形参的命名我们就能知道是说能否退出Looper的意思,之前我们说了,可能通过调用Looper.myLooper().quit()方法退出Looper,所以这里我们能够知道,UI线程的Looper是不允许退出的。

MessageQueue

说了这么久,好像还没看到MessageQueue在哪里实例化的,我们来看看,在Looper实例化的时候代码是这样的:

// 方法一
public static void prepare() {
    prepare(true);
}

// 方法二
private static void prepare(boolean quitAllowed) {
    // 检查线程局部变量是否已经保存了一个Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 创建一个Looper对象并放到当前线程局部变量中
    sThreadLocal.set(new Looper(quitAllowed));
}

// 方法三
private Looper(boolean quitAllowed) {
    // 实例化MesssageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

方法一方法二在之前已经解释过了,我们看方法二最后一行代码,实例化了一个Looper对象放到当前线程的局部变量中,方法三是Looper的构造方法,在构造方法里我们看到了MessageQueue的实例化代码。之前我们说了一个线程只能有一个Looper,而MessageQueue是在Looper的构造方法中实例化的,所在一个线程同样只能有一个MessageQueue

Message

最后我们来看看Message,我们想要传递的内容就是放在Message里,然后通过MessageQueue最终传递到消费者线程去处理。Message里可以放数据(data)或者任务(task),每个Message只能放一种内容,不能同时存在数据和任务。

数据消息(Data message):

Handler、Message、MessageQueue、Looper协作简析_第1张图片
Handler、Message、MessageQueue、Looper协作简析_第2张图片

——数据消息可以携带这些信息。通过handleMessage()处理。

任务消息(Task message):
——任务消息就是传递一个java.lang.Runnable对象到消费者线程执行。

实例化Message有几种方法:

  • 直接调用构造方法
Message m = new Message();
  • 调用工厂方法
Message m = Message.obtain();
Message m = Message.obtain(Handler h);
Message m = Message.obtain(Handler h, int what);
Message m = Message.obtain(Handler h, int what, Object o);
Message m = Message.obtain(Handler h, int what, int arg1, int arg2);
Message m = Message.obtain(Handler h, int what, int arg1, int arg2,Object o);
Message m = Message.obtain(Handler h, Runnable task);
Message m = Message.obtain(Message originalMsg);

调用工厂方法好点,因为它会从Message池里获取,不需要new一个新的,池里没有才会new,所以性能稍微好点吧。

消息都是通过Handler插入到消息队列的,数据消息是通过前缀为send的方法插入的,任务消息是通过前缀为post的方法插入的。

  • 插入数据对象到消息队列
boolean sendMessage(Message msg)
boolean sendMessageAtFrontOfQueue(Message msg)
boolean sendMessageAtTime(Message msg, long uptimeMillis)
boolean sendMessageDelayed(Message msg, long delayMillis)
  • 插入简单数据对象到消息队列
boolean sendEmptyMessage(int what)
boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
boolean sendEmptyMessageDelayed(int what, long delayMillis)
  • 插入任务到消息队列
boolean post(Runnable r)
boolean postAtFrontOfQueue(Runnable r)
boolean postAtTime(Runnable r, Object token, long uptimeMillis)
boolean postAtTime(Runnable r, long uptimeMillis)
boolean postDelayed(Runnable r, long delayMillis)

MessageQueue里只能放Message对象,那是如何传递Runnable的呢?我们来看一下源码:

// handler调用post方法发送任务
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}

// 将Runnable包装成message
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

通过发送任务的方法源码我们知道,其实最终加入消息队列的还是消息对象,只是将任务进行了包装。

总结

基本将HandlerMessageMessageQueueLooper协作运转的过程简单走了一遍,但是还是有些东西没讲,如消息的生命周期、消息队列的空闲时间、自定义callback、移除消息、消息处理过程跟踪等。但是这个简析基本理清了他们之间的关系以及工作原理,在使用上的话应该不会存在大问题了。

你可能感兴趣的:(android,Android开发,handler)