Handler使用的那些事儿

Posted 一介闲休

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler使用的那些事儿相关的知识,希望对你有一定的参考价值。

     对任何有开发经验的童鞋,对Handler肯定是如雷贯耳,使用也必定非常熟练,当现在我还是想对其来个梳理,总结下常见的疑问:

 1.常听人说mHandler.obtainMessage(what,obj).sendToTarget()要比Message msg = new Message;msg.what= 1;msg.obj = obj;mHandler.sendMessage(msg)要简洁,要好?

    对这样的传说,究竟有多少人认真审视过?它是不是真的好?好又好在哪里?是简洁还是性能?对这一路的疑惑,我们慢慢道来,这里面涉及到了Handler也涉及到了Message,下面从源码来解惑,先来看看Handler类源码中几个重要方法:

//构造函数
public Handler() 
        if (FIND_POTENTIAL_LEAKS) 
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) 
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            
        
//获取当前线程的私有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;
        mCallback = null;
    <pre name="code" class="html">//获取Message对象
public final Message obtainMessage(int what, Object obj)
    
        return Message.obtain(this, what, obj);
    
//发送消息
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    
        if (delayMillis < 0) 
            delayMillis = 0;
        
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    

    //发送消息的实际执行方法,就是将该消息加入到消息队列中
    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) 
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);
        
        else 
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        
        return sent;
    
//派送消息并处理,这也就是之所以我们实现了handleMessage后,就可以得到处理
public void dispatchMessage(Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    
 

看完Handler后,再来看下Messager类几个重要方法:

public static Message obtain(Handler h, int what, Object obj) //对应handler中的obtainMessage
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    <pre name="code" class="html">//获取消息,如果消息池为null,就创建消息,如果不为null,则从消息池里面取消息
public static Message obtain() 
        synchronized (sPoolSync) 
            if (sPool != null) 
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            
        
        return new Message();
    
//回收消息,如果消息池没有满则加入消息池中,满的话,则舍弃
public void recycle() clearForRecycle(); synchronized (sPoolSync) if (sPoolSize < MAX_POOL_SIZE) next = sPool; sPool = this; sPoolSize++;

 

看完Handler和Messager类后,就可以回到问题了,当采用obtainMessage获取消息时,当消息池中没有消息就和new Message方式开销都是一样的,都是通过创建Message来获取消息,如果消息池不为null,则前者可以直接取消息而不用new了,节省了开销和空间,因而传说不欺人也。

2.在线程中创建Handler,如果没有调用prepare(),为何一直会报错,调用了prepare()后,不报错了但是消息没有处理?为何调用prepare和loop后的activity内部线程,在onDestory中没有调用该线程的quit方法会导致内存泄露?

废话先别说,直接上Looper类源码:

Looper构造函数:
private Looper() 
        mQueue = new MessageQueue();//创建了个消息队列
        mRun = true;
        mThread = Thread.currentThread(); //该looper所在线程属于当前线程
    


    public static void prepare() 
        if (sThreadLocal.get() != null) 
            throw new RuntimeException("Only one Looper may be created per thread");
        
        sThreadLocal.set(new Looper()); //作用就是为该线程创建一个隶属于自己的Looper
    

    public static Looper myLooper() 
        return sThreadLocal.get();//取出线程自己所属的Looper
    

    public static void loop() 
        Looper me = myLooper();
        if (me == null) 
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        
        MessageQueue queue = me.mQueue;
        
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
     //此处就是个循环,一直从消息队列中取消息进程处理,处理完成后,就调用recycle回收msg,结合Message类,就可以知道回收掉的msg,就会重新放入消息池中。
        while (true) 
            Message msg = queue.next(); // might block
            if (msg != null) 
                if (msg.target == null) 
                    // No target is a magic identifier for the quit message.
                    return;
                

                long wallStart = 0;
                long threadStart = 0;

                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) 
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                	   //msg.target就是handler,调用dispatchMessage就是分发消息,再通过handlerMessage进行处理
                msg.target.dispatchMessage(msg);

                if (logging != null) 
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;

                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) 
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    
                

                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) 
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                
                //处理完成后,消息回收,放入消息池中
                msg.recycle();
            
        
    
//结束消息循环,该线程所做任务也就完成了
Looper.quit():
 public void quit() 
        Message msg = Message.obtain();
        // NOTE: By enqueueing directly into the message queue, the
        // message is left with a null target.  This is how we know it is
        // a quit message.
        mQueue.enqueueMessage(msg, 0);
    

看完源码后,我们应该有这样一个思路,在线程中创建Handler,如果没有调用Looper.prepare(),则会报错,原因很简单,因为在Handler的构造函数中会去取该线程私有的looper,而线程的私有looper,是在Looper.prepare中通过sThreadLocal设置进去的,而没有调用prepare,在初始化Handler时取出线程的私有looper则为null,因此就会报“Can't create handler inside thread that has not called Looper.prepare()”异常。 

当调用Looper.prepare()后在handler初始化时就可以获取到looper,也具备自己的消息队列了,因而也就不会报错,但没有调用Looper.loop(),则不会执行handler发出的消息,looper.loop()方法通过源码我们可以知道,它的工作就是一直从线程私有消息队列中取消息,并调用handler的dispatchMessage方法去分派消息,最后处理完成后,就将该消息所占用的空间回收值消息池中,如果没有调用Looper.loop(),则表示没有从消息队列中取消息进行处理的意向,因而也不会执行你所实现的handleMessage。


当我们在线程中调用了Looper.prepare()和Looper.loop(),如果此时线程是activity的内部线程或匿名线程,如果没有该线程的quit方法,则会造成内存泄露,其原因就是调用Looper.loop()后,其内部就是一个死循环,如果不调用quit,则会一直存活,加之该activity的隐含this也存活在该线程中,因而在activity销毁后,没有调用quit方法,会导致该线程还是一直在运行,造成activity隐含的this没有得到释放,进而导致泄露。

说到此处,总结下Thread,Looper,Handler,MessageQueue,Message的关系:

当我们创建个线程后,调用了Looper.prepare(),此时就会为线程创建一份私有的looper和消息队列MessageQueue,如果此时在创建Handler,就表示该Handler存放消息的位置就是该线程的MessageQueue,调用Looper.loop后,表示会一直从MessageQueue中取消息取处理,并执行Handler中实现的handleMessage方法,处理完成msg后,则会回收msg所占空间值Message的消息池中。




以上是关于Handler使用的那些事儿的主要内容,如果未能解决你的问题,请参考以下文章

Android+Handler+Thread 那些事儿

Android 子线程更新UI那些事儿

关于同步与异步的那些事儿

注解的那些事儿| 注解的使用

关于PBR贴图那些事儿

TCP那些事儿