Android多线程(三)——消息机制Handler的使用与源码解析

简介

Handler是Android中的一种消息机制。handler的应用很广,平时我们自己的继承Thread、实现Runnable接口实现异步通信时都会使用到Handler,很多异步框架如AsyncTask,handlerThread等内部也都使用了Handler。

使用

因为Android不能在子线程中更新UI,当我们子线程执行异步任务时需要更新UI,这就可以使用在主线程中new Handler实例,在子线程中获取handler,实例使用sendMessage或者post方法通知主线程去更新UI。
简单demo展示

class HandlerTestActivity : AppCompatActivity() {

    lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hander_test)

        btnHandler.setOnClickListener {
            var thread = Thread {
                Thread.sleep(3000)
                var msg = Message.obtain()
                msg.what = 1
                handler.sendMessage(msg)
            }.start()
        }

        handler = Handler() {
            if (it.what == 1) {
                tvHandler.setText("已点击按钮")
            }
            flase
        }
    }
}

这里用的时handler的sendMessage方法,也可以使用post方法。使用post方法就不用单独在Handler后边写相应的UI操作了,直接写在Runable里面,还可以用postDelayed进行延迟效应的操作。
而Handler在实例化的时候需要实现Handler.callback接口里的handleMessage方法,这里写的是主线程接受到工作线程发过来消息的UI更新操作。

  	handler = Handler()
 	btnHandler.setOnClickListener {
	   Thread {
	            handler.postDelayed(Runnable {
	                tvHandler.setText("已点击按钮")
	            },1000)
	        }.start()
   }

源码分析

Handler

handler的工作过程是与Looper和messageQueue禁密相连的。每个线程可以对应多个Handler,但是每个线程只有一个Looper和一个MessageQueue。 MessageQueue是线程消息的中转站,Looper是消息的管理者,当handler发送消息到消息队列后,消息队列中的消息会依次被取出,再分发到对应handler,让Handler再执行所在的线程的回调(handleMessage)。
在这里插入图片描述

  1. Handler共有7个构造函数,由 Looper/Callback/async三个参数的有无自我组合,其中有3个含async的方法通过@hide标签被隐藏为无法使用,初始化mLooper/mQueue/mCallback/mAsynchronous。
  • 使用构造函数创建的Handler下处理程序是同步的,即mAsynchronous=false,而使用静态方法createAsync创建的handler会传入async = true。
  • mLooper和mQueue只需创建一次,通过Looper的静态方法mylooper()直接获取,然后通过looper对象获取到对应的queue。
  • Handler的Callback里面又一个handleMessage方法,在这里作为一个参数,我们要传入就必须实现回调里面的方法,这里写的也就是处理消息后的动作,如主线程更新UI。
  • FIND_POTENTIAL_LEAKS是静态常量默认false,当要继承Handler写自己的Handler时,可以把它设为true,用于检测内存泄漏。
//同步方式
	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();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
   //异步方式
     @NonNull
    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }
  1. Message实例化obtainMessage有5个方法,最终指向Message的静态obtain方法,实例化一条message,但是这里自动传入handler将message.target = handler,我们直接使用message.obtain没有绑定Handler。但是这并不影响消息的传递。
  2. 创建的是异步的Handler。此时当传递消息到消息队列执行enqueueMessage时会进入msg.setAsynchronous(true)。异步消息消息结合同步屏障可以实现立即执行的效果(优先级最高),但是现在messageQueue的同步屏障方法在api28中被@hide,已经不推荐使用,并且在api28 之后无论是反射还是JNI方式也无法使用@hide标记的方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
 public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
  1. sendMessage——>sendMessageDelayed——>sendMessageAtTime——>enqueueMessagea。
    这是sendMessage的调用顺序,我们可以通过前三种形式发送即时/延迟/定时消息,他们的实现过程也是向前递进的sendMessage延迟0秒,sendMessageDelayed在当前时刻➕延迟时间的时刻去执行。handler的enqueueMessage最后调用queue的enqueueMessage方法。发送消息完成。在Handler的enqueueMessage方法中指定message的target为当前Handler。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (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;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  1. handler除了直接sendMessage还可以使用post方式,post方式会传入Runnable参数,并赋值给message.callback,在把这个message通过对应的send方式发送出去,postDelay、postAtTime同上。
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;
    }
  1. dispatchMessage就是最终处理消息,消息最终又回到了Handler,如果消息是通过post方式传来的,那么就会通过handleCallback方式去执行那个Runnable,会去mCallback.handleMessage(msg)执行初始化传过来的回调,返回的结果也是自己设定的,这个参数就是Handler初始化传进来的重写的handle方法的返回值,如果是false会接着执行handleMessage,需要重写。
 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();
    }

Looper

  1. looper初始化

 	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);
        mThread = Thread.currentThread();
    }
    
     public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  • 在我们通常使用的looper的初始化方法子线程默认looper可以退出,prepareMainLooper是初始化主线程的looper不允许退出,这里传入了给quitAllow传入ture之后,那么使用Looper的quit就可以结束loop循环,再来消息就不会进行任何处理了。
  • 因为looper的构造函数是私有的,实例化looper必须经过prepare方法,looper.prepare在实例化looper的时候使用了ThreadLocal的set方法,这样就保证了一个线程只有这一个looper对象的存在了。
  • looper在实例化时先实例化了一个MessageQueue, 后绑定当前的thread,这就保证了这个线程里只有一个lopper和一个messageQueue。
  1. looper开启循环
 public static void loop() {
      public static void loop() {
        
        ...// 仅贴出关键代码

        // 1. 获取当前Looper的消息队列
            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;
            // 获取Looper实例中的消息队列对象(MessageQueue)

        // 2. 消息循环(通过for循环)
            for (;;) {
            
            // 2.1 从消息队列中取出消息
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            // next():取出消息队列里的消息
            // 若取出的消息为空,则线程阻塞
        
            // 2.2 派发消息到对应的Handler
            msg.target.dispatchMessage(msg);
            // 把消息Message派发给消息对象msg的target属性
            // target属性实际是1个handler对象
        
        // 3. 释放消息占据的资源
        msg.recycle();
        }
}
  
  • loop方法里面又一个for的死循环,每循环一次,就获取下一条消息,直接调用与消息绑定的Handler,用这个Handler去dispatchMessage(),到此就实现了一条消息的分发,接着会把这条已经分发的消息销毁释放资源。然后直至将消息队列的消息都分发完成。
  • 到消息队列的消息为空是,线程阻塞,直到有下一条消息的到来。需要注意的是在主线程在执行looper.loop 之前用过thread.attach(false)建立Binder通道 (创建新线程),不会循环卡死。
    链接

当消息队列为空时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

Message

Message为了进行序列化实现了Parcelable接口,可以在不同进程间传递。

  1. Message的初始化方法如下,先是选择性初始化标识符what,两个int类型的参数和一个任意对象,再是单链表尾插。
 public static Message obtain() {
		//加锁保证线程安全
        synchronized (sPoolSync) {
        //sPool是上一条message,在初始化当前msg时,会把上一条msg的next指向当前msg。
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
        //每增加一条消息,poolSize就-1
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
  1. 消息回收,如果Handler初始化的是async=true,也就是是异步Handler,那么这里回收就会出抛出异常。否则使用recycleUnchecked进行回收msg。
 public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

3.其他参数说明

  • target:这是msg锁绑定的Handler,可以在初始化或者send的时候进行绑定,每条消息都会被绑定,已便最终分发到对应的Handler。
  • when:消息发送出去的时刻,是long类型的时间戳。
  • data:Bundle类型的参数,用于存储一些额外数据。
  • callback:存储一些异步动作,在事件处理是可直接执行callback里的操作。

MessageQueue

MessageQueue是一个用final修饰的类不可被扩展,它里面主要是对msg的管理。

  1. enqueueMessage()
  boolean enqueueMessage(Message msg, long when) {
  //msg必须要target,否则无法传递分发,抛出异常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
  //消息不可以重复使用
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

 //当执行了looper.quit,looper结束循环,还没有发送出去的消息直接回收
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
 //标记消息被使用·,及消息发送时间
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
     //消息队列的消息为空,直接插入
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
         //消息队列不为空,就通过msg发送时间排序,
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
//p为空说明到了队列尾部,或者找到了刚好比当前msg.when大的时间戳,进行插入
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • 需要注意的是messge在初始化的时候有一个初始化顺序先后的队列的,但是在消息被handler post后会执行enqueueMessage,他会重新对message按照时间先后顺序排列进行排列。
  1. 插入同步屏障postSyncBarrier方法是被hide了,他是在消息队列中插入一条没有target的消息,将后边同步消息都堵塞住,但是异步消息不受影响。当移除同步屏障后,才可以正常接受同步消息。
  2. message.next() // 该参数用于确定消息队列中是否还有消息, 从而决定消息队列应处于出队消息状态 或等待状态
Message next() {
      
       int nextPollTimeoutMillis = 0;

       for (;;) {
           if (nextPollTimeoutMillis != 0) {
               Binder.flushPendingCommands();
           }

       // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 
       nativePollOnce(ptr, nextPollTimeoutMillis);

       synchronized (this) {
    
           final long now = SystemClock.uptimeMillis();
           Message prevMsg = null;
           Message msg = mMessages;

           // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
           if (msg != null) {
               if (now < msg.when) {
                   nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
               } else {
                   // 取出了消息
                   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;
               }
           } else {

               // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
               // 下次循环时,消息队列则处于等待状态
               nextPollTimeoutMillis = -1;
           }

           ......
       }
          .....
      }
}

4.quit,looper的quit方法最终调用的还是mesageQueue的quit方法,分为安全和非安全两种,安全的会清空消息队列在quit,非安全的就直接quit。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值