美文网首页
Handler看这一篇就够了

Handler看这一篇就够了

作者: 劲火星空 | 来源:发表于2019-07-22 20:57 被阅读0次

    Handler使用

    首先来熟悉一下Handler的四种使用方式,如果比较熟悉可以直接跳过:

    1. 通过sendMessage消息机制来发送

    sendEmptyMessage(int);//发送一个空的消息
    sendMessage(Message);//发送消息,消息中可以携带参数
    sendMessageAtTime(Message, long);//未来某一时间点发送消息
    sendMessageDelayed(Message, long);//延时Nms发送消息

    接收数据

    mHandler = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           switch (msg.what) {
               case 0:
                   String data = (String) msg.obj;
                   textView.setText("接收数据:" + data);
                   break;
               default:
                   break;
           }
       }
    };
    

    发送数据

     new Thread(new Runnable() {
         @Override
         public void run() {
             Message msg = new Message();
             msg.what = 0;
             msg.obj = "测试数据";
             mHandler.sendMessage(msg);
         }
     }).start();
    
    1. 使用Post方式;

    post(Runnable);//提交计划任务马上执行
    postAtTime(Runnable, long);//提交计划任务在未来的时间点执行
    postDelayed(Runnable, long);//提交计划任务延时Nms执行

    new Thread(new Runnable() {
        @Override
        public void run() {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    tvContent.setText("接收数据:" + str);
                }
            }, 3000);
        }
    }).start();
    

    其实传递Runnable也是最终转换成message通过sendMessage来发送,将Runnable赋给message的callback,
    这个callback在Handler取消息的时候会先进行判断,如果不为空就直接调用了,如果为空才会调用handMessage方法

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
    1. 使用runOnUiThread方式;

    runOnUiThread其实是调用的post

    new Thread(new Runnable() {
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tvContent.setText("接收数据:" + str);
                }
            });
        }
    }).start();
    
    1. 通过View的Post方式;

    也是调用的Handler的post方法

    new Thread(new Runnable() {
        @Override
        public void run() {
            tvContent.post(new Runnable() {
                @Override
                public void run() {
                    tvContent.setText("接收数据:" + str);
                }
            });
        }
    }).start();
    

    Handler组成

    Handler的四大成员:

    发送方式 功能介绍
    Message 主要功能是进行消息的封装,同时可以指定消息的操作形式
    Looper 消息循环泵,用来为一个线程跑一个消息循环,每一个线程最多只可以拥有一个
    MessageQueue 就是一个消息队列,存放消息的地方每一个线程最多只可以拥有一个
    Handler 消息的处理者,handler 负责将需要传递的信息封装成Message,发送给Looper,继而由Looper将Message放入MessageQueue中。当Looper对象看到MessageQueue中含有Message,就将其广播出去。该handler 对象收到该消息后,调用相应的handler 对象的handleMessage()方法对其进行处理

    需要注意的点是每个线程只有一个Looper和一个MessageQueue,一个线程中可以有多个Handler,每个Handler会处理各自的消息,互相之间没有影响,也就是多个Handler的发送和接收之间值多对多的关系。

    Handler原理分析

    首先来看下主线程和子线程创建Handler的不同

    1. 主线程
    mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    
    1. 子线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                }
            };
            Looper.loop();
        }
    }).start();
    

    发现主线程直接建立即可,而子线程需要使用Looper.prepare()和Looper.loop方法,这个为啥?带着疑问,接着往下看。

    从Looper.prepare()开始
    当Looper.prepare()被调用时,发生了什么?

    public static void prepare() {
            prepare(true);  //最终其实执行的是私有方法prepare(boolean quitAllowed)中的逻辑
        }
    
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {   //先尝试获取是否已经存在一个Looper在当前线程中,如果有就抛个异常。
            //这就是为什么我们不能在一个Thread中调用两次Looper.prepare()的原因。
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));  //首次调用的话,就创建一个新的Looper。
        }
        
        //Looper的私有构造函数
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);   //创建新的MessageQueue,稍后在来扒它。
            mThread = Thread.currentThread();         //把当前的线程赋值给mThread。
        }
     }
    

    可以看到首次调用prepare的时候会创建一个Looper实例放进去,创建Looper实例的时候会创建一个与之关联的MessageQueue,那么这个sThreadLoacal是什么呢? sThreadLocal是个静态的ThreadLocal<Looper> 实例(在Android中ThreadLocal固定为Looper)。那么,Looper.prepare()既然是个静态方法,Looper是如何确定现在应该和哪一个线程建立绑定关系的呢?
    来看看ThreadLocal的get()、set()方法。

    public void set(T value) {
            Thread t = Thread.currentThread();  //同样先获取到当前的线程
            ThreadLocalMap map = getMap(t);     //获取线程的ThreadLocalMap
            if (map != null)
                map.set(this, value);           //储存键值对
            else
                createMap(t, value);
    }    
    
    public T get() {
            Thread t = Thread.currentThread();   //重点啊!获取到了当前运行的线程。
            ThreadLocalMap map = getMap(t);      //取出当前线程的ThreadLocalMap。这个东西是个重点,前面已经提到过。忘了的同学在前面再看看。
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);  
                //可以看出,每条线程的ThreadLocalMap中都有一个<ThreadLocal,Looper>键值对。绑定关系就是通过这个键值对建立的。
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
    }
    

    重点来了:每个线程有一个与之关联的Looper,这个Looper是用ThreadLoacaMap来存储的,ThreadLoacaMap存储的就是ThreadLocal对象,而Android中的ThreadLocal就是Looper,所以存的就是Looper,并且ThreadLocalMap是在每线程中都私有的,这就保证的并发时的线程安全,这是典型的以空间来换时间,而我们熟悉的synchronized是以时间换空间。

    创建Handler
    平时我们都使用new Handler()来在一个线程中创建Handler实例,但是它是如何知道自己应该处理那个线程的任务呢

    public Handler() {
            this(null, false); 
    }
        
    public Handler(Callback callback, boolean async) {      //可以看到,最终调用了这个方法。
            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());
                }
            }
    
            mLooper = Looper.myLooper();                    //重点啊!在这里Handler和当前Thread的Looper绑定了。Looper.myLooper()就是从ThreadLocale中取出当前线程的Looper。
            if (mLooper == null) {
                //如果子线程中new Handler()之前没有调用Looper.prepare(),那么当前线程的Looper就还没创建。就会抛出这个异常。
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;  //赋值Looper的MessageQueue给Handler。
            mCallback = callback;
            mAsynchronous = async;
    }
    

    上面代码中有mLooper = Looper.myLooper(),这就是Handler和Looper进行了绑定,然后Handler就可以给Looper发消息了,我们调用Handler的sendMessage方法也就是往Looper的MessageQueue里边发消息

    Looper.loop()
    我们都知道,在Handler创建之后,还需要调用一下Looper.loop(),这样才能循环起来,那Looper是怎样把消息准确的送到Handler中处理

    public static void loop() {
            final Looper me = myLooper();   //这个方法前面已经提到过了,就是获取到当前线程中的Looper对象。
            if (me == null) { 
                //没有Looper.prepare()是要报错的!
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;       //获取到Looper的MessageQueue成员变量,这是在Looper创建的时候new的。
    
            //这是个Native方法,作用就是检测一下当前线程是否属于当前进程。并且会持续跟踪其真实的身份。
            //在IPC机制中,这个方法用来清除IPCThreadState的pid和uid信息。并且返回一个身份,便于使用restoreCallingIdentity()来恢复。
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {  //重点(敲黑板)!这里是个死循环,一直等待抽取消息、发送消息。
                Message msg = queue.next(); //  从MessageQueue中抽取一条消息。至于怎么取的,我们稍后再看。
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                final long traceTag = me.mTraceTag;   //取得MessageQueue的跟踪标记
                if (traceTag != 0) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));  //开始跟踪本线程的MessageQueue中的当前消息,是Native的方法。
                }
                try {
                    msg.target.dispatchMessage(msg);   //尝试分派消息到和Message绑定的Handler中
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);      //这个和Trace.traceBegin()配套使用。
                    }
                }
    
                final long newIdent = Binder.clearCallingIdentity();   //what?又调用这个Native方法了。这里主要是为了再次验证,线程所在的进程是否发生改变。
                msg.recycleUnchecked();   //回收释放消息。
            }
        }
    

    从上面的分析可以知道,当调用了Looper.loop()之后,线程就就会被一个for(;;)死循环阻塞,每次等待MessageQueue的next()方法取出一条Message才开始往下继续执行。然后通过Message获取到相应的Handler (就是target成员变量),Handler再通过dispatchMessage()方法,把Message派发到handleMessage()中处理。

    MessageQueue
    MessageQueue是一个用单链的数据结构来维护消息列表,现在又产生一个疑问,MessageQueue的next()方法是如何阻塞住线程的呢?

    Message next() {
            //检查loop是否已经为退出状态。mPrt是Native层的MessageQueue的地址。通过这个地址可以和Native层的MessageQueue互动。
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
            int pendingIdleHandlerCount = -1;
            int nextPollTimeoutMillis = 0;      //时间标记,当且仅当第一次获取消息时才为0。因为它在死循环外面啊!
    
            for (;;) {
               //这是一个Native的方法。
               nativePollOnce(ptr, nextPollTimeoutMillis);
               synchronized (this) {       //锁住MessageQueue
                    //获取当前的系统时间,用于后面和msg.when进行比较。
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;        //获得当前MessageQueue中的第一条消息
                    if (msg != null && msg.target == null) {
                    
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {  //这个判断的意义在于只有到了Message应该被发送的时刻才去发送,否则继续循环。
                            //计算下一条消息的时间。注意最大就是Integer.MAX_VALUE。
                            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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();   //转换消息标记为使用过的
                            return msg;         //返回一条消息给Looper。
                        }
                    } else {
                        // 如果取到的Message为null,将时间标记设置为-1。
                        nextPollTimeoutMillis = -1;
                    }
            }
        }
    

    可以看到。MessageQueue在取消息(调用next())时,会进入一个死循环,直到取出一条Message返回。这就是为什么Looper.loop()会在queue.next()处等待的原因
    上面方法中出现了一个nativePollOnce(ptr, nextPollTimeoutMillis);函数的调用。线程会被阻塞在这个地方,实际上是阻塞在了底层的Looper的epoll_wait()这个地方等待唤醒呢。nativeWake(),通过这个来进行唤醒唤醒,这个方法是在enqueueMessage中调用的
    下面看下enqueueMessage的代码:

    boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {   //没Handler调用是会抛异常的啊
                throw new IllegalArgumentException("Message must have a target.");
            }
            synchronized (this) {       //锁住MessageQueue再往里添加消息。
                msg.markInUse();        //切换Message的使用状态为未使用。
                msg.when = when;        //我们设置的延迟发送的时间。
                //经过下面的逻辑,Message将会被“储存”在MessageQueue中。实际上,Message在MessageQueue中的储存方式,
                //是使用Message.next逐个向后指向的单链表结构来储存的。比如:A.next = B, B.next = C...
                Message p = mMessages;  //尝试获取当前Message
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                    // 如果为null,说明是第一条。
                    msg.next = p;   
                    mMessages = msg;    //设置当前的Message为传入的Message,也就是作为第一条。
                    needWake = mBlocked;
                } else {
                
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    //不满足作为第一条Message的条件时,通过下面的逐步变换,将它放在最后面。这样便把Message“储存”到MessageQueue中了。
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; 
                    prev.next = msg;
                }
    
              
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    为什么主线程创建Handler不用Looper
    因为主线程在ActivityThread,也就是主线程初始化的时候通过Looper.prepareMainLooper()建立好了对应的Looper
    注意ActivityThread并没有继承Thread,它的Handler是继承Handler的私有内部类H.class。在H.class的handleMessage()中,它接收并执行主线程中的各种生命周期状态消息。UI的16ms的绘制也是通过Handler来实现的。也就是说,主线程中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之间进行的。

    ThreadLocal说明

    这个在上面已经说过了,因为是非常的重要,所以在这里再强调一遍。Android中ThreadLocal的表现形式就是Looper,我们在Looper.prepare()中会创建一个Looper实例,并将其存入ThreadLocalMap中,而ThreadLoacalMap是属于特定线程的,这样不仅保证的线程安全,同时也将Looper和对应的线程关联起来了,Looper创建的时候也会创建一个对应的MessageQueue

    HandlerThread

    先来看一个HandlerThread使用的例子:

    HandlerThread thread = new HandlerThread("MyHandlerThread");
    thread.start();
    mHandler = new Handler(thread.getLooper());
    mHandler.post(new Runnable(){...});
    

    再看下HandlerThread的源码

    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
    
        public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
        protected void onLooperPrepared() {
        }
    
        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
    }
    

    它的代码比较短,我们主要来看一下它的run()方法,我们发现它和普通Thread不同之处在于它在run()方法内创建了一个消息队列,然后来通过Handler的消息的方式来通知HandlerThread执行下一个具体的任务。由于HandlerThread的run()方法内Looper是个无限循环,所以当我们不需要使用HandlerThread的时候可以通过qiut()的方法来终止。

    public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
    }
    

    quit()实际上就是让run()内的Looper停止循环

    removemessages

    这个是用来移除消息队列中对应what类型的数据,当有postMessageDelay消息进入倒计时的时候可以使其停止,但是如果设置的时间达到了,那么也是没有作用的
    先来看下源码,下面是Handler的调用

    // Handler.java
    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }
    

    他会接着调用MessageQueue里边的removeMessages

    // MessageQueue.java
    void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }
    
        synchronized (this) {
             Message p = mMessages;
    
            // 原代码注释,Remove all messages at front.
            // 根据上面传入的参数,p!=null成立,p.target == h成立,object == null 成立
            // 即此处可等同为p.what == what
            // 此处代码即找到第一个p.what == what的消息并将其移除
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }
    
            // Remove all messages after front.
            // 此处即为移除找到第一个后的Message
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    // 同上,此处也可以等同为p.what == what
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
    

    由源码中可以看出,MessageQueue将Message.what与函数传入的what相同的Message从队列中移除

    一个线程多个Handler

    一个线程可以建立多个Handler,但是一个线程只有一个Looper,也只有一个MessageQueue,多个Handler发送的消息会都放到一个MessageQueue中进行遍历。重点:同一个Handler只能处理自己发送给Looper的那些Message,多个Handler之间是互不干扰的,也就是多对多的关系。移除相同类型消息的时候,其他Handler也不会受到影响

    尊重作者,尊重原创,参考文章:
    Handler实例:https://www.cnblogs.com/whoislcj/p/5590615.html
    Handler原理:https://www.jianshu.com/p/8862bd2b6a29
    HandlerThread:https://www.jianshu.com/p/5b6c71a7e8d7
    多对多关系:https://blog.csdn.net/u011573355/article/details/50735604

    相关文章

      网友评论

          本文标题:Handler看这一篇就够了

          本文链接:https://www.haomeiwen.com/subject/iweglctx.html