关闭

android Handler的机制详解

标签: Handler的机制详解Message机制MessageQueue机制Looper机制handler实现原理
465人阅读 评论(0) 收藏 举报
分类:

先看一个小实现,这样是最常见的Handler消息接收的实现方式:

主线程中:
        mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            ...
        } 
而子线程的实现中:
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();  
		        mHandler = new Handler() {  
		        @Override  
		        public void handleMessage(Message msg) {  
		            ...
		        }  
		        Looper.loop();  
            }
        }).start();

个人更建议下面这种实现方式,只有Handler的初始化的时候有一点区别,我认为可读性更强,原因后面我也会详细讲述的:

        new Thread(new Runnable() {
            @Override
            public void run() {
                	Looper.prepare();  
		        mHandler = new Handler(Looper.myLooper()) {  
		        @Override  
		        public void handleMessage(Message msg) {  
		            ...
		        }  
		        Looper.loop();  
            }
        }).start();


肯定有读者有疑问了,为什么在主线程的实现和在子线程的实现不太一样呢?这个啊,说来话长,也会在后面我将慢慢道来。

了解Handler的机制前,我们得先了解Message,MessageQueue,Looper。

Message

1,这里Message是包含消息的类,实现了Parcelable接口,可用来作为跨进称传输的信息载体。

2,注意Message有一个字段next也是Message,很明显Message肯定是用来表示一个单链表的其中一个节点。他也可以表示一个单链表队列的头节点,后面要讲的MessageQueue是继承的Message,MessageQueue就是作为一个队列的头结点,通过next一层一层把下一个Message节点取出来实现遍历。

注意:虽然MessageQueue大家习惯叫做消息队列,它内部结构同Message,并不是真正的队列,而是单链表的数据结构来存储消息列表。

关于Message我们一定要了解obtain()和recycle()这两个很重要的方法

obtain():

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

sPool也是一个Message类,表示的是一个单链表队列的头结点,该队列是用来存储所有需要回收的message的地址。每次的存储都是在recycle方法中设置。

每次我们调用obtain()来获取一个Message的时候,会先从sPool队列中拿出头节点返回,并把头结点从sPool中删除。若sPool为null,意思是还没有设置过任何一次Message地址作为可回收地址,这就要新初始化一个Message。设置地址可回收的好处自然也是不言而喻的,在有大量消息请求的时候,可节省的内存空间是非常可观的。

总之呢,Message.obtain()就是一个初始化Message的过程。

另外呢,Handler在调用obtainMessage()的时候,最后也是调用了Message.obtain()来初始化一个Message,并返回实例的。


recycle()正是解释了如何把一个废弃的Message地址做为可回收地址。

  public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
每次message.recycle()后,都会把该message添加到sPool的之前作为头结点。不过啊,虽然说recycle是个很好的功能,但是呢,我们平时开发并不需要主动去设置message的recycle去回收利用内存空间,因为Looper.loop()检测messageQueue的时候,每从queue中取出一个message,虽然从queue队列中删掉了这个message节点,但是并没有在处理完该节点后完全放弃他,而是把他recycle了,资源回收利用的效果。



MessageQueue

MessageQueue可以理解为对Message队列的一个管理类,它拥有一个mMessages。虽然这看来像一个数组,然而并不是,它是一个Message类,不是List。

原因是因为这个队列并不是一个数组,是一个单链表的结构,每一个Message都有一个next的Message对象,用来指向下一个Message的地址。

而mMessages只是代表第一个message的地址,这样通过mMessages就可以把所有message都一个一个取出来。

MessageQueue主要的两个方法,一个是入队enqueueMessage();一个是next(),next()返回下一条message,也会完成该message出队的工作。

入队enqueueMessage():

boolean enqueueMessage(Message msg, long when) {
       
        synchronized (this) {
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }
        }
        return true;
    }
这里,msg.next=p; prev.next=msg;是个典型的入队过程,跟下面的next是个相反的操作.


next():

Message next() {

        for (;;) {
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } 
               ...
        }
    }
上述代码中

1,在获取到msg=msg.next,并最后把msg返回
2,prevMsg.next=msg.next,这就是删除了msg所在节点。按道理这个时候应该回收msg这段内存的,但是这里并没有释放掉这段内存,会造成内存泄漏。但是这是android源码呀,怎么会犯这么低级的错误呢。这是因为msg被处理后,最后被设置为可回收空间了。所以,待会儿请注意看Looper中的loop()中有解释这一现象。


Looper

looper的主要作用

1,prepare()初始化messageQueue用来存储message队列

2,loop()持续检测messageQueue队列中是否有新的message需要处理。在handler初始化的时候会绑定looper,每次loop()后发现有新的message需要处理都会发给handler的handleMessage()处理。

prepare():

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

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
    }
Looper在prepare()时主要做了3件事:

1,初始化一个MessageQueue,这是后面用来作为一个链表来存储Message队列的。

2,设置当前线程给mThread

3,把当前Looper初始化,并通过sThreadLocal.set()设置给了sThreadLocal。后面会通过sThreadLocal.get()来获取当前looper。这个实现其实就是在myLooper()方法中:

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据,这里就是用来存储looper的。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

不知道你有没有注意到,Looper的构造方法在这里是private来修饰的,换言之,你不能在其他类中以一般构造方法的形式初始化Looper,你只能通过Looper.prepare()来初始化一个looper。这里一个线程中绑定一个looper,一个线程中也只能使用一次prepare()。如果你企图使用或者不小心使用了2次prepare,就会抛一个RuntimeException("Only one Looper may be created per thread")。

只要你在一个线程中使用过Looper.prepare(),looper就一直绑定着这个线程,系统也未提供给开发者主动销毁looper的操作,也就是说系统本身设计上也是不建议你主动去销毁looper,应该是没有这个必要,所以一般looper是伴随着线程的生命周期存在和销毁。那么这个looper在这个线程中就是全局存在的,你随时可以通过Looper.myLooper()来获取,并且调用其一些可用的操作。


loop():

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            msg.target.dispatchMessage(msg);
           ...
            msg.recycle();
        }
    }

不知道你有没有注意到loop这里是一个无限循环(只有在调用了Looper.quit()才可退出循环)。每一次的循环都在做以下几个事情:

1,从MessageQueue中获取新的Message

2,调用msg.target.dispatchMessage,而dispatchMessage()中就是调用了我们熟悉的Handler::handleMessage():

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

3,msg.recycle()正是把这个已经处理过的msg从内存中置null,但是并未收回该内存空间,而是把这段已分配的内存作为可重复使用的空间。上面Message的recycle()已经详细讲述过,这里不再累述。


Handler:

handler的主要作用:

1,发送消息sendMessage,往mLooper的messageQueue队列中添加一条消息。Looper的loop()会检测到新的消息,然后

2,然后到这里了,handleMessage(Message msg)接收并处理消息


Handler使用的内部流程:

1,Looper.prepare():初始化一个Looper在LocalThread本地线程,后面可以通过Looper.myLooper()获取该looper,其实也是从LocalThread中获取的。

2,初始化Handler():在你指定的线程中初始化一个Handler,这里把当前线程的looper赋给了Handler的mLooper,这里会把刚才初始化的Looper的消息队列属性mQueue绑定给Handler。

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

    public Handler(Callback callback, boolean async) {
...
        mLooper = Looper.myLooper();
...
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

因为looper在当前线程中是全局的,所以这也就解释了为什么Hanlder在初始化的时候命名没有指定looper,当时他们却用的同一个looper。如果你觉得这种代码实现方案可读性不好,Hanlder也提供了另外一种可读性更好的构造函数:

    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
把looper作为输入参数来构建Handler,如下

        new Thread(new Runnable() {
            @Override
            public void run() {
                <span style="white-space:pre">	</span>Looper.prepare();  
		        mHandler = new Handler(Looper.myLooper()) {  
		        @Override  
		        public void handleMessage(Message msg) {  
		            ...
		        }  
		        Looper.loop();  
            }
        }).start();
本质上,两种实现方式是完全一样的。这种看起来可读性更好一些。这也回应了一开始我为什么会建议使用这种方案的原因。


3,Handler::sendMessage():每次sendMessage()就会调用mQueue.enqueueMessage(msg),把新的消息加入到消息队列mQueue。

4,Looper::loop():loop()中在处理mQueue的消息,每次处理一条message并调用handleMessage(msg)返回到Handler处理,并删除已处理msg节点。而且loop()是一个无限循环的遍历mQueue的过程,只有调用Looper.quit()才会停止。

5,handleMessage(Message msg):这里是每次有接收到消息后的处理逻辑。

6,loop()一直在查找mQueue有没有消息,所以,步骤3和步骤5一定是成对出现的,一一对应。


网上有很多文章说Handler有两个功能,处理Message和Runnable,因为Handler提供了sendMessage(Message msg)和post(Runnable r)。关于这个观点,我也不多说,反正不能这么理解。

sendMessage(msg)相信大家应该很熟悉了,sendMessage被触发后,最后会通过handleMessage(msg)回调过来,这里不多讲了。

而post(Runnable r):

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
这很明显,还是个sendMessage()。只是回调过来有一点区别,这里你应该注意到了message的callback是不为null的吧。那么请看回调:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
在callback==null的时候,是回到了我们常见了handleMessage(msg)进行回调。

但是这里callback!=null呀,这里就是触发了callback.run()。

handler.post(new Runnable(){

@Override
public void run() {
//do something
});
post(Runnable r)不需要做区分,每条消息都在自己的run()里面进行接收并处理。这里对比起sendMessage()发送消息,到handleMessage()接收并处理消息显得看起来好像更简洁一些。其实不然,这种处理会导致程序中你要实现很多很多个run(),这显然并不好看。而handleMessage()来处理消息,并通过message.what来去消息进行区分,这样实现看起来就只有一个。

也就是看个人习惯了,个人还是推荐sendMessage()发送消息,handleMessage()处理消息。


注意:

1,Handler的应用场景

There are two main uses for a Handler: 

(1) to schedule messages and  runnables to be executed as some point in the future; and 

(2) to enqueue an action to be performed on a different thread than your own.

1),通过sendMessageDelayed()设计一个定时器,比如说:时间的表盘的开发。

    mHandler.sendMessageDelayed(msg,1000);
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            sendMessageDelayed(msg,1000);
        }
    };

2),Handler在子线程中处理耗时操作,耗时操作处理完了后拿到数据,通过sendMessage(msg)把数据绑定到msg,并把消息发送出去,之后会在handleMessage(msg)处理,一般会在这里更新UI。

本质上来说,Handler并不是专门用来更新UI的,它只是常被开发者用来更新UI。

        mHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //...处理msg
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作,比如说网络数据请求
                ...
                mHandler.sendMessage(msg);
            }
        }).start();

如果是处理第二种情况,需要在handleMessage()中刷新UI,那么就必须保证Handler的初始化是在主线程的,我上面这种就是正确的把Handler的初始化放在了主线程。

这也就解释了为什么不能直接在run里面直接通过函数的调用去刷新UI呢。如果直接这么调用,那么刷新UI还是在子线程,那么就会报错:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.


问题:

1,为什么子线程不被允许访问UI?

答:这是因为android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。准确的说,是使用的是宿主窗口surface的UI是不能在子线程被访问,但是SurfaceView,TextureView等有自己独立surface的UI还是可以在子线程被访问,甚至是官方推荐你去子线程去处理。

2,那为什么不对UI控件加锁保证线程安全呢?

答:加锁会让UI访问变得复杂,而且效率会降低。

3,一个线程只需要开启一个looper,和一个MessageQueue。也就是说在同一个线程中,只需要执行一次prepare(),和loop()。那为什么主线程不需要呢?

答:

首先,拓展一下知识。很多人不知道,我们主线程其实是有一个Looper的,叫做sMainLooper,可以通过Looper.getMainLooper()获取。所以如果你在主线程中企图使用调用Looper.prepare()初始化一个looper就是错误的操作。mainLooper是在ActivityThread启动的:

public final class ActivityThread {
    ......
    public static final void main(String[] args) {
    ......
    Looper.prepareMainLooper();
    ......
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    ......
    Looper.loop();
    ......
    thread.detach();
    ......
    }
}


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:33804次
    • 积分:569
    • 等级:
    • 排名:千里之外
    • 原创:41篇
    • 转载:1篇
    • 译文:0篇
    • 评论:9条
    文章分类
    最新评论