为什么使用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之前需要先调用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):


——数据消息可以携带这些信息。通过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包装成messageprivate static Message getPostMessage(Runnable r) {    Message m = Message.obtain();    m.callback = r;    return m;}

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

总结

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

更多相关文章

  1. Android(安卓)App启动过程
  2. Accessibility辅助功能的使用
  3. windows下配置安卓开发环境
  4. android大分辨率图片的缩放处理和图片添加水印+文字,水印图片对角
  5. 【Android】查看程序每个方法所花费的时间
  6. Android(安卓)常用设计模式(二) -- 单例模式(详解)
  7. Android(安卓)多进程通信(2) - Binder、Messenger
  8. Android如何分析和研究Monkey Log文件
  9. Android:自定义View(一)

随机推荐

  1. 【Arcgis android】 离线编辑实现及一些
  2. Android(安卓)App 防止 后台服务 被杀掉
  3. Android中,把XML文件转换成Object对象的方
  4. android ndk编译getevent
  5. Android(安卓)模糊搜索rawquery bind or
  6. Android(安卓)LCD(三):Samsung LCD接口篇
  7. 关于设置线性布局及其他布局的宽高
  8. Porting WiFi drivers to Android
  9. android SQLite操作
  10. Android(安卓)实现扫雷小游戏实例代码