在Android种常用的线程间通信方式之一是通过Handler,做过android开发的都知道,在Android种对ui的操作都要放在主线程或者说是ui此案成中,当然不在主线程也能操作,具体在应用开始onResume 之前,这时还未检查是否是ui线程。具体涉及到android源码,本例不深入。

相关类

android.os.Handler 发送消息和处理消息都需要通过它,它会与一个Looper相关联。 android.os.Looper 通常说是轮询器,负责消息分发的,一个Looper只能与一个线程关联

android.os.MessageQueue 消息队列,存放message,每个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();
            }
        });
    }
![Handler.png](http://upload-images.jianshu.io/upload_images/1394860-de75c82fca47a09b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

}

Handler 的模式

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

Handler.png 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();
        }
    }
}

结果报错,java.lang.runtimeException:can’t create handler inside thread that has not called Looper.prepare()

错误显示在实例化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;
                    }
                }
            };
// 增加调用这个方法来开始消息轮询
            Looper.loop();
        }
    }
}

停止Looper

上面我讲了子线程如何接收UI线程发送过来的消息。但是这里有个问题。如何结束这个子线程? 我们继续查看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才行,我们来一探究竟。

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

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的方法,而且抛出的异常信息也能证明这一点。我们来看看是在哪里调用的这个方法:

activity_handler.png 通过查找调用的地方发现,在程序入口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只能放一种内容,不能同时存在数据和任务。

总结

基本将Handler、Message、MessageQueue、Looper协作运转的过程简单走了一遍,但是还是有些东西没讲,如消息的生命周期、消息队列的空闲时间、自定义callback、移除消息、消息处理过程跟踪等。但是这个简析基本理清了他们之间的关系以及工作原理,在使用上的话应该不会存在大问题了。 ⤧  Next post View事件分发简要分析 ⤧  Previous post 回调方法与顺序