handler消息机制详解

来源:互联网 发布:手机网络翻墙怎么回来 编辑:程序博客网 时间:2024/06/06 02:07

在日常开发中我们经常用到hanlder来更新ui界面或者传递消息,他主要用于线程之间的通信,之前一直就这么用,对于他的深层原理并没有深究,在周五的项目中又再次用到了,现在在这里来深入分析下。

运行原理:

在android4.0之后,为了保证正主线程的流畅性,不能再主线程访问网络或者加载耗时间的数据,对于这写操作统一放到了子线程中进行,而在主线程中主要进行ui界面的更新,这时候我们就需要借助于handler消息机制在子线程与主线程之间相互通信这个过程比较复杂,主要涉及到四大要素:

  • Message(消息)
  • MessageQueue(消息队列)
  • Looper(消息循环)
  • Handler(消息发送和处理)
这四大要素的交互原理是:子线程访问网络获取到message,通过主线程的handler发送消息到主线程的消息队列MessageQueue,主线程的looper轮询器会一直轮询,当发现这个消息时,就把他取出来,交给handlerMessage方法处理,更新UI界面。

Looper:

looper是一个轮询器,它也存在于主线程和子线程中,当handler初始化好的时候,就和当前所在的线程的handler绑定。他的作用的不断地循环,当发现消息队列里面有数据的时候就会立即从消息队列里面取出来,当没有消息时候就会暂停下来。需要注意的是handler的创建必须借助looper,在主线程中,系统人默认会创建一个looper轮询器,而在子线程中,需要手动创建。

        new Thread(new Runnable() {            @Override            public void run() {                Looper.prepare();                Handler handler = new Handler();                Looper.loop();            }        }).start();

Looper中还有一个特殊的概念,那就是ThreadLocal,每一个线程,都会单独对应的一个looper,这个looper通过ThreadLocal来创建,保证每个线程只创建一个looper,looper初始化后就会调用looper.loop创建一个MessageQueue,这个方法在UI线程初始化的时候就会完成,我们不需要手动创建。ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。

MessageQueue:
消息队列它是用来存放消息的一个队列结构,当子线程或者主线程通过handler发送消息时候,他都会把消息按照先后顺序存放起来,读取的时候是按照消息的先后顺序进行读取,在读取完成之后伴随着着消息的移除。存放、读取、移除三者是同步进行的,先存放,后读取,再移除,这个读取是靠looper不断地循环读取的。接下来看看message的创建,和一些常用的方法:

1、message的创建
通过阅读源码发现,message他实现了Parcelable接口,创建有两种方式一种是直接 new Message();另外一种是使用Message的静态方法obtain来获取,这两种方法的区别是第一种直接通过创建对象来获取message,这种方法不推荐,第二种是从message的消息池里面取得。 它类似一个线程池,创建了一个Message池,如果有闲置的Message就直接返回,不然就新建一个,用完以后,返回消息池,这种方法大大减少了当有大量Message对象而产生的垃圾回收问题吗,并且他还提供了大量形式的构造方法供我们来使用。
Message.obtain()的消息池上限是10个。

    public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }

从以上源码中我们可以看出obtain方法的内部结构是一个消息池。

2、Message的常用方法;

(1)what:int类型的数据,用于指定消息的类型,标记消息从哪里来。

(2)arg0,arg1:两个int型值,一般用来传递简单的整型数据。

(3)obj:object类型的数据,用于传递复杂的数据。

(4)data:Bundle型,这个传递较多种数据的时候需要用到。

Handler:

handler它是一个在线程之间通讯的桥梁,充当了子线程与主线程之间的沟通的媒介。他不但存在于主线程,并且还存在于子线程,也就是说每个线程都存在handler,当我们创建handler时候,可以在其构造方法中指定他的线程,也可以不指定,当不指定,即构造方法为空的时候他默认是存在于当前线程。

1、 HandlerThread详解 
       HandlerThread本质上还是一个thread,他继承自thread,内部封装了looper。HandlerThread与handler的区别是:handler与activity在同个线程中,而HandlerThread与activity在不同线程中。HandlerThread将loop转到子线程中处理,降低了主线程的压力,使主界面更流畅。使用步骤如下:

(1)创建一个HandlerThread,即创建了一个包含Looper的线程。

HandlerThread handlerThread = new HandlerThread("sohu.com");

handlerThread.start(); //创建HandlerThread后一定要记得start(),获取HandlerThread的Looper

(2)Looper looper = handlerThread.getLooper();创建Handler,通过Looper初始化

(3)Handler handler = new Handler(looper);

通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。

如果想让HandlerThread退出,则需要调用handlerThread.quit();。

2、handler中更新ui界面的方法

(1)sendMessage发送

    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            if (msg.what == 1) {                Toast.makeText(getApplicationContext(), string, Toast.LENGTH_LONG).show();            }        }    };

(2)使用post发送消息(延迟消息)

    private Handler handler = new Handler();    Runnable runnable = new Runnable() {        @Override        public void run() {            Toast.makeText(getApplicationContext(),string, Toast.LENGTH_LONG).show();        }    };    handler.post(runnable);
(3)使用runOnUiThread更新
    new Thread(){        @Override        public void run() {            //在这里进行复杂的数据操作            // …………………………………………………………………………            runOnUiThread(new Runnable() {                @Override                public void run() {                    //在这里进行更新UI的操作                    Toast.makeText(getApplicationContext(),string, Toast.LENGTH_LONG).show();                }            });        }    }.start();

以上三种方法均是使用主线程的handler向主线程发消息,那么在线程如何获取handler,并向主线程发消息呢?在上文提到在创建handler时候需要借助于looper,主线程默认有点,子线程是默认没有的,需要我没自己创建,因此必须创建,否则会报handler没有初始化的错误,下面来看看代码:

    Looper.prepare();    Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            Toast.makeText(context, "发送消息成功!", Toast.LENGTH_SHORT).show();        }    };    handler.sendEmptyMessage(1); //一定要写这个标记,要不然收不到    Looper.loop();

注意:handler使用不当可能会导致内存泄漏,所以在handler使用结束的时候需要在移除消息。handler.removeCallbacksAndMessages(null);即可移除消息。

0 0