handler机制原理

1、Handler原理

Handler使用方式 因发送消息到消息队列的方式不同而不同,共分为2大类:使用 Handler.sendMessage()、使用Handler.post()

Handler中四个重要的类:

处理器 类(Handler)快递员(属于某个快递公司的职员)

消息队列 类(MessageQueue)快递分拣中心(分拣快递的传送带)

循环器 类(Looper)快递公司(具有处理包裹去向的管理中心)

消息 类(Message)包裹(可以放置很多东西的箱子)

大概原理

Handler是常被开发者拿来更新UI的一种消息处理机制,它的运行机制需要底层的Looper和MessageQueue的支撑

       一个Android应用程序被创建时就会创建一个进程,该进程用应用的包名作为进程名。该进程会启动主线程ActivityThread,也叫做UI主线程,但有时需要做些耗时操作,为了不能够去阻塞UI主线程的正常运行,我们将它放在子线程中进行操作,操作完成后需要绘制UI,但Android子线程不能直接操作UI线程的,所以通过Handler来进行通信


Handler

Handler有这样三个特点:

a.允许你去发送并处理一条与Runnable对象和MessageQueue相关联的消息。

b.每一个Handler实例都与一个单独的线程和该线程的MessageQueue相关。

c.当你创建一个新的处理程序时,它将绑定到正在创建的线程的线程/消息队列——从那个点开始,它将向该消息队列传递消息和runnables,并在它们从消息队列中释放时执行它们。

实际上,根据我的理解,handler就是我们在各线程间处理发送消息数据的一种机制,实现线程间切换的一种方式。

Handler有两个主要用途:

(1)将消息和runnables作为将来的某个点执行。

(2)在不同的线程上执行要执行的操作。


一、Message(消息)

Message.class位于android.os.包中。Message的构造函数为无参构造方法,且只有一个构造方法;

public Message() { }

除了构造方法可以创建实例对象外,还可以通过内部的静态方法来创建:

static Message obtain()

static Message obtain(Message orig)

static Message obtain(Handler h)

static Message obtain(Handler h, Runnable callback)

static Message obtain(Handler h, int what)

static Message obtain(Handler h, int what, Object obj)

static Message obtain(Handler h, int what, int arg1, int arg2)

static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)


以上几个静态的方法里面都会首先调用第一个方法来创建一个Message对象,我们来看看源码(API 28)


public static final Object sPoolSync = new Object();    //同步锁对象

    private static Message sPool;                          //全局池消息实例


/**

    * 从全局池返回一个新的消息实例,允许我们在许多情况下避免分配新对象。

    */

    public static Message obtain() {

        synchronized (sPoolSync) {

            if (sPool != null) {

                Message m = sPool;

                //......

                return m;

            }

        }

        return new Message();

    }



如果当前全局池的Message实例不为空,则返回第一个消息实例。所以,大多数情况下,使用obtain()来获得一个Message对象,可以避免消耗更多的内存资源。

对于其他 static obtain( ) 的重载方法,通过源码,可以发现,都是进行赋值操作,没有太多的可讨性。唯一得注意一下的是 obtain(Handler h, Runnable callback)这个静态方法:

/*package*/ Handler target;

/*package*/ Runnable callback;

public static Message obtain(Handler h, Runnable callback) {

        Message m = obtain();

        m.target = h;

        m.callback = callback;

        return m;

    }

可以看到,也是赋值操作,target是保护级别的成员变量,即只有同包名空间可以访问,此变量意义重大。除了target,还有一个callback,这个callback也是配合着Handler来发挥作用的。后面讲到Handler会解释到,请稍安勿躁。

此时,大家先要记住的就几点:

Message有8个静态方法可以创建Message实例

Message有两个重要的成员变量,分别为target 和callback,一个是Handler,一个是Runnable。

Message有4个公开变量what、arg1、arg2、obj 可以存储消息进行传递

Message还有一个包间成员变量next,它是Message类型,后面会使用到,知道有这个next就行    

以上就是Message的基本秘密了,很简单,没有什么复杂的东西(作为一个包裹箱,就是这么简单,能装一些东西,然后附带一些关键信息)。


二、Handler(处理机)

Handler.class也位于android.os包中。Handler英文意思为:处理者,管理者,处理机。它在消息传递过程中扮演着重要的角色,是消息的主要处理者,说白了,就是收消息,最终处理消息(它就像一个快递员,收快递,然后领快递单,派送快递)。

1、Handler的构造方法(API 28)

Handler()

Handler(Callback callback)

Handler(boolean async)

Handler(Callback callback, boolean async)

Handler(Looper looper)

Handler(Looper looper, Callback callback)

Handler(Looper looper, Callback callback, boolean async)

通过源码可以发现,上面的构造方法都是上面一个个往下调用的,第一个调用第二个,第二个调用第三个…所以,我们首先把目光放在最后一个方法上:

public Handler(Looper looper, Callback callback, boolean async) {

        mLooper = looper;

        mQueue = looper.mQueue;

        mCallback = callback;

        mAsynchronous = async;

    }

这是一个赋值的构造方法。再看另外一个构造方法:

public Handler(Callback callback, boolean async) {

        //......

        mLooper = Looper.myLooper();  //返回与当前线程关联的Looper对象,在后面Looper会讲到

        if (mLooper == null) {

            throw new RuntimeException(

                "Can't create handler inside thread " + Thread.currentThread()

                        + " that has not called Looper.prepare()");

        }

        mQueue = mLooper.mQueue;  //返回Looper对象的消息队列,在后面MessageQueue会讲到

        mCallback = callback;  //接口回调

        mAsynchronous = async; //是否异步

    }


public interface Callback {

        public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的

    }

整个构造方法的过程中会确立以下几件事:

获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper()

如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue

可以设置Callback 来处理消息回调:mCallback = callback

Handler是消息的处理者,但是它并不是最终处理消息的那个大佬,它有且只能有一个上级个领导,就是Looper,Handler是将消息上报给Looper(领导),然后排队等待,等Looper(领导)处理完消息了,就会通知Handler去领取消息,给Handler分配任务,Handler拿到消息后在自行往下分发,Handler只能听命与Looper(领导)。

举个实际运用中的情景:

当你需要在子线程中更新主线程的UI时,你就会在当前的Activity下创建一个Handler对象,然后在它的handleMessage() 中更新UI。

private Handler mHandler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            //... 更新UI

        }

    };

在你创建这个mHandler 实例的时候,底层做了以下几件事情:

1、拿到mHandler所在线程的Looper,当前mHandler是在Activity中创建的,很明显,当前的线程就是主线程,所以 mHandler的成员变量mLooper = Looper.myLooper(),此处就已经将当前的主线程Looper赋值过去了。

2、紧接着,判断mLooper 是否为空,明显不为空,所以又会将主线程的消息队列赋值给mQueue。告诉Handler,你要是有消息,就送到这个消息队列中来,我(Looper)会一个个按顺序处理,处理完后我就会告诉你,你再处理。

由此我们可以得出结论:

1、Handler有且只能绑定一个线程的Looper;

2、Handler的消息是发送给Looper的消息队列MessageQueue,需要等待处理;

所以,如果你在子线程中声明了一个Handler,是不能直接更新UI的,需要调用Handler相关的构造方法,传入主线程的Looper,这样创建的Handler实例,你才能进行UI的更新操作。另外的,需要注意的是,子线程默认是没有开启专属的Looper,所以,在子线程中创建Handler之前,你必须先开启子线程的Looper,否则就会爆出异常,然后GG。从上面贴出的构造方法中的部分就可以知道:

if (mLooper == null) {

            throw new RuntimeException(

                "Can't create handler inside thread " + Thread.currentThread()

                        + " that has not called Looper.prepare()");

    }

以上就是创建Handler的过程,有了Handler实例了,怎样传递消息呢?

2、Handler sendMessage()相关的方法(API 28)

首先上一幅图来表明sendXXXMessageXXX()的相互调用关系:

可以看出,当我们调用Handler进行发送消息时,最终都会调用sendMessageAtTime()方法,最后调用enqueueMessage( ) 发送到消息队列。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

        MessageQueue queue = mQueue;  //获得当前的消息队列

        if (queue == null) {  //若是在创建Handler时没有指定Looper,就不会有对应的消息队列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);

    }


private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

        msg.target = this;  //这个target就是前面我们说到过的

        if (mAsynchronous) {

            msg.setAsynchronous(true);

        }

        return queue.enqueueMessage(msg, uptimeMillis);

    }

msg.target = this

在发送消息到消息队列之前,明确的指定了消息的target为当前的Handler,以便于在后面Looper分发消息时用到。

queue.enqueueMessage(msg, uptimeMillis)

然后调用了消息队列的enqueueMessage()方法,并传递了两个参数,一个Message,一个是long型的时间。

以上就是Handler的创建和发送消息的过程。

三、MessageQueue

前面我们知道,Handler发送消息会调用MessageQueue的enqueueMessage()方法,直接上源码(API 28):

boolean enqueueMessage(Message msg, long when) {

        if (msg.target == null) {  //判断msg的所属Handler

            throw new IllegalArgumentException("Message must have a target.");

        }

        //......

        synchronized (this) {  //因为是队列,有先后之分,所以用了同步机制

            //......

            msg.when = when;

            Message p = mMessages;  //对列中排在最后的那个Message

          //......

            if (p == null || when == 0 || when < p.when) {   

              //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队

                msg.next = p;  //此处的next就是前面我们在Message提到的,指向队列的下一个结点

                mMessages = msg;

                //......

            } else {

                //......

                Message prev;

                for (;;) {   

                    //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入

                    prev = p;

                    p = p.next;

                    if (p == null || when < p.when) {

                        break;

                    }

                    //......

                }

                msg.next = p;      // 置换插入

                prev.next = msg;  // 置换插入

            }

    //......

        }

        return true;

    }



以上就是消息队列插入消息的过程原理,通过单向链表的数据结构来存储消息。既然有了插入消息的方法供Handler插入消息,那么应该有对应的取出消息的方法,供Looper调用取出消息处理,它就是Message next()这个方法,代码就不贴了,自行前往查看,过程还是挺简单的。

四、Looper

Looper在Handler机制中扮演着关键的一环,他是循环处理消息的发动机,永不停息(永动鸡),它不断的从消息队列中取出的消息,处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环。

点击前往查看Looper在main()方法中的启动流程

下面直接上Looper关键方法loop( )的源码(API 28)

public static void loop() {

        final Looper me = myLooper();  //获得当前的Looper

        if (me == null) {

            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

        }


        final MessageQueue queue = me.mQueue;  //获取当前Looper的消息队列

        //......

        for (;;) {

            Message msg = queue.next();  //取出队头的消息

            if (msg == null) {

                // 如果消息为空,则跳过,继续执行下一个message

                return;

            }

            //......

            try {

                msg.target.dispatchMessage(msg);

                //......

            } finally {

              //......

            }

          //......

            msg.recycleUnchecked();  //回收可能正在使用的消息

        }

    }


由此可见,Looper的处理消息的循环还是挺简单的,就是拿出消息,然后分发,然后回收 … …

总结

Handler机制可以简述为:

Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。

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