handler

448 阅读16分钟

image.png

Handler是一个消息传递机制,作用是将子线程需要的UI操作,传递到UI线程执行,保证线程安全。

image.png

相关类介绍

  • Message:线程间通讯的数据单元。

MessageQueue:消息队列,特点先进先出。存储Handler发送过来的消息(Message)

  • Handler:是一个消息处理器,将消息传递给消息队列,然后再由对应线程从消息队列中逐个取出数据,并执行

  • Looper:消息循环器,循环取出消息(Message),将取出的消息分发给对应的处理者(Handler)。 特点:

  • 一个Handler只能拥有一个Looper。

  • 一个Looper可以绑定多个Handler

    public Handler(Callback callback, boolean async) {
       ...
		//关联Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
		//关联MessageQueue
        mQueue = mLooper.mQueue;
		...
    }

在Handler的构造方法中,会关联Looper和MessageQueue(Looper的构造函数中会创建自己的MessageQueue)

源码分析

Handler消息传输机制分析

默认情况,消息队列只有一个,就是主线程的消息队列。这个线程由ActivityThread.main方法中创建,ActivityThread的main方法是整个APP的入口,调用Looper.prepareMainLooper()创建,执行Looper.loop()来启动消息循环。

    public static void main(String[] args) {

       Looper.prepareMainLooper(); 
       // 1.创建主线程Looper
       ActivityThread thread = new ActivityThread(); 
       // 2. 创建主线程
       Looper.loop(); 
       // 3. 开启消息循环
}
	//设置和获取Looper,Looper保存在ThreadLocal中
    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,只能调用一次
    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));
    }

   //Looper的构造函数中会创建自己的MessageQueue
   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

	//获取Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

可以看到Looper存储在ThreadLocal,这就实现了线程安全。Looper可以对应多个Handler

/** 
  * 源码分析: Looper.loop()
  * 作用:消息循环,即从消息队列中获取消息、分发消息到Handler
  * 特别注意:
  *       a. 主线程的消息循环不允许退出,即无限循环
  *       b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
  */
  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.");
            }
            // myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常
            // 即loop()执行前必须执行prepare(),从而创建1个Looper实例
            
            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()是一个消息无限循环,不断从消息队列获取消息,将消息派发到对应的Handler

public final class Message implements Parcelable {

    Handler target;
    Runnable callback;
    Message next;
}

  /** dispatchMessage(msg)
  * 定义:属于处理者类(Handler)中的方法
  * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
  */
  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();
    }


   public void handleMessage(Message msg) {  
          ... // 创建Handler实例时复写
   } 

dispatchMessage派发消息最终会调用HandlerMessage或者run方法处理

  1. 当调用Handler.post时msg.callback != null,会走run方法
  2. 当Message.obtain方法里设置时msg.callback != null,会走run方法
  3. 当Handler初始化时传入Callbackh时mCallback != null,会走mCallback.handleMessage(msg)方法
  4. 当前面两者都没有,直接调用sendMessage时,会走handleMessage(msg) 方法

MessagePool

从消息池里拿一个消息,并表尾删除这个消息,消息池大小减一

消息池最大容量是50

recycleUnchecked()在loop的for每个循环的最后就调用,重置message,如果消息池没满,就添加到消息池里(表尾插入),否则就丢弃这个message。

消息池是一个链表,最大容量是50

使用线程池的原因是复用message,避免message重复销毁创建

MessageQueue

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

消息被处理的时间 = 当前时间+延迟的时间

消息被处理的时间 = 当前时间+延迟的时间?

至于这个地方为什么要用SystemClock.uptimeMillis() 而不用SystemClock. currentTimeMillis(),

System.currentTimeMillis() 这个方法得到的毫秒数为“1970年1月1号0时0分0秒 到 当前手机系统的时间”的差

SystemClock.uptimeMillis()用来计算自开机启动到目前的毫秒数。如果系统进入了深度睡眠状态(CPU停止运行、显示器息屏、等待外部输入设备)该时钟会停止计时。因此SystemClock.uptimeMillis()方法也成为了计算间隔的基本依据,比如Thread.sleep()、Object.wait()、System.nanoTime()以及Handler都是用SystemClock.uptimeMillis()方法。这个时钟是保证单调性,适用于计算不跨越设备的时间间隔。

MessageQueue中最重要的就是两个方法: 1.enqueueMessage向队列中插入消息 2.next 从队列中取出消息

Handler.sendMessageDelayed()方法最终会调用enqueueMessage方法进入MessageQueue的enqueueMessage方法中:

boolean enqueueMessage(Message msg, long when) {
        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;//将延迟时间封装到msg内部
            Message p = mMessages;//消息队列的第一个元素
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
               //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,
               //则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,
               //然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
          
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //如果此消息是延时的消息,则将其添加到队列中,
                //原理就是链表的添加新元素,按照when,
                //也就是延迟的时间来插入的,延迟的时间越长,越靠后,
                //这样就得到一条有序的延时消息链表,取出消息的时候,
                //延迟时间越小的,就被先获取了。
                //插入延时消息不需要唤醒Looper线程。
  
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {//查找适合插入的位置,拿到前后结点
                    prev = p;
                    p = p.next;
                    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;
    }

MessageQueue中enqueueMessage方法的目的有两个: 1.插入消息到消息队列 2.唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态)

MessageQueue的底层数据接口是单向链表,mMessages成员变量

取出消息的方法next():

Message next() {
    
        final long ptr = mPtr;
        if (ptr == 0) {
           //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,
           //mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,
           //阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
           //最终会使loop的forbreak出循环
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
           //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
           //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
           //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
           
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        // 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,
                        //进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞
                        //;
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //正常取出消息
                        //设置mBlocked = false代表目前没有阻塞
                        mBlocked = false;
                        if (prevMsg != null) {//prevMsg!=0的情况先不考虑
                            prevMsg.next = msg.next;
                        } else {////链表头部移动到链表第二个元素上
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //没有消息,会一直阻塞,直到被唤醒
                    nextPollTimeoutMillis = -1;
                }

                if (mQuitting) {
                    dispose();
                    return null;
                }
        }
    }


demo

public class HandlerActivity extends AppCompatActivity {
	@BindView(R.id.content_tv)
	TextView mContentTv;

	Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			if (msg.what == 1) {
				mContentTv.setText((CharSequence) msg.obj);
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_handler);
		ButterKnife.bind(this);
	}

	@OnClick({R.id.post, R.id.obtain, R.id.Handler_Callback_is_null, R.id.Handler_Callback_is_not_null})
	public void onClick(View view) {
		switch (view.getId()) {
			//当调用Handler.post时msg.callback != null,会走run方法
			case R.id.post:
				new Thread() {
					@Override
					public void run() {
						super.run();
						mHandler.post(new Runnable() {
							@Override
							public void run() {
								mContentTv.setText("post");
							}
						});
					}
				}.start();
				break;
			//当Message.obtain方法里设置时msg.callback != null,会走run方法
			case R.id.obtain:
				new Thread() {
					@Override
					public void run() {
						super.run();
						Message message = Message.obtain(mHandler, new Runnable() {
							@Override
							public void run() {
								mContentTv.setText("obtain");
							}
						});
						mHandler.sendMessage(message);
					}
				}.start();
				break;
			//当Handler初始化时传入Callback时mCallback != null,会走mCallback.handleMessage(msg)方法
			case R.id.Handler_Callback_is_null:
				new Thread() {
					@Override
					public void run() {
						super.run();
						Message msg = Message.obtain();
						msg.what = 1; // 消息标识
						msg.obj = "Handler_Callback_is_null"; // 消息内存存放
						mHandler.sendMessage(msg);
					}
				}.start();
				break;
			//当前面两者都没有,直接调用sendMessage时,会走handleMessage(msg) 方法
			case R.id.Handler_Callback_is_not_null:
				final Handler handler = new Handler(new Handler.Callback() {
					@Override
					public boolean handleMessage(Message msg) {
						if (msg.what == 2) {
							mContentTv.setText((CharSequence) msg.obj);
						}
						return true;

					}
				});

				new Thread() {
					@Override
					public void run() {
						super.run();
						Message msg = Message.obtain();
						msg.what = 2; // 消息标识
						msg.obj = "Handler_Callback_is_not_null"; // 消息内存存放
						handler.sendMessage(msg);
					}
				}.start();
				break;
		}
	}
}

为什么可以直接post呢,因为post(Runnable r)会将Runnable包装成Message对象,并将Runnable对象设置给Message对象的callback,最好将Message对象插入消息队列中去。

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

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

    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;
        }
		//将消息插入MessageQueue
        return enqueueMessage(queue, msg, uptimeMillis);
    }

Handler机制流程图

image.png

问题

1、为什么不new Message()而采用Message.obtain()?

因为Message内部维护了1个Message池,用于Message消息对象的复用,使用obtain()则是直接从池内获取,避免内存new Message重新分配内存。

    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();
    }
2、Handler可以在子线程创建吗?

通常Handler必须在主线程创建,因为Handler机制的主要作用是更新UI。当然也可以创建在子线程,那么就得自己手动去prepare和loop。

    new Thread(){
		@Override
		public void run() {
			super.run();
			//创建当前线程的Looper,并绑定到ThreadLocal中
			Looper.prepare();
			//创建Handler,关联Looper和MessageQueue
			Handler handler = new Handler();
			//启动消息循环
			Looper.loop();
		}
	}.start();
3、Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。 主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 blog.csdn.net/chewbee/art…

管道

管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。 当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。 当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

www.cnblogs.com/qiaoyanlin/…

epoll机制

IO多路复用

单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。 多路网络连接复用一个io线程。]

Socket 它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据

4、如何避免不当导致的内存泄漏

解决方案

  • 1.尽可能避免使用Handler做延迟操作。
  • 2.使用静态内部类继承Hanlder(静态内部类不会持有外部对象的引用),如果我们需要在Handler中 使用外部的Activity时,可以定义一个Activity弱引用(WeakReference)对象,弱引用在第二次GC回收时,可以被回收。
  • 3.在onDestory时,清除Handler消息队列中的消息removeCallbacksAndMessages(null)

问题根源: 非静态匿名内部类持有外部类引用

静态Handler内部类, 如果在handMessage方法里需要用到上下文, 进行弱化,在onDestory时,清除Handler消息队列中的消息removeCallbacksAndMessages(null)

public class NoLeakActivity extends AppCompatActivity {

    private NoLeakHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandler = new NoLeakHandler(this);

        Message message = Message.obtain();

        mHandler.sendMessageDelayed(message,10*60*1000);
    }

    private static class NoLeakHandler extends Handler{
        private WeakReference<NoLeakActivity> mActivity;

        public NoLeakHandler(NoLeakActivity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

但是为什么为static类型就会解决这个问题呢? 因为在java中所有非静态的对象都会持有当前类的强引用,而静态对象则只会持有当前类的弱引用。 声明为静态后,handler将会持有一个Activity的弱引用,而弱引用会很容易被gc回收,这样就能解决Activity结束后,gc却无法回收的情况。

5、什么是handler?

一套 Android 消息传递机制 / 异步通信机制

Handler可以发送和处理消息对象或Runnable对象,这些消息对象和Runnable对象与一个线程相关联。每个Handler的实例都关联了一个线程和线程的消息队列。当创建了一个Handler对象时,一个线程或消息队列同时也被创建,该Handler对象将发送和处理这些消息或Runnable对象。

handler类有两种主要用途:

  • 执行Runnable对象,还可以设置延迟。 - 两个线程之间发送消息,主要用来给主线程发送消息更新UI。
2、为什么使用handler?

解决多线程并发问题,假设如果在一个activity中,有多个线程去更新ui,并且都没有加锁机制,那界面显示肯定会不正常。于是andoird官方就封装了一套更新ui的机制,也可以用handler来实现多个线程之间的消息发送。

在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

3、怎么使用handler?

handler常用的方法有以下这些:

  • post(Runnable)发送即时消息
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)发送延时消息
  • sendEmptyMessage(int)发送空消息
  • sendMessage(Message)发送即时消息
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long)发送延时消息
4、为什么Android要设计只能通过Handler机制更新UI?

最根本的目的就是解决多线程并发问题

假设如果在一个Activity当中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样的问题?(更新界面错乱)如果对更新UI的操作都进行加锁处理,又会产生什么样的问题?(性能下降)

基于对以上目的的考虑,android给我们提供了一套更新UI的机制,我们只需遵循这样的机制,无需考虑多线程问题。

5、Looper、MessageQueue、Message、Handler的关系
6、Handler异步消息处理(HandlerThread)
7、子线程可以和主线程通信吗?主线程可以和子线程通信吗?
8、Android在子线程用handler发送的消息,主线程是怎么loop到的?

主线程会创建loop

10、sleep和wait区别?

sleep是让线程休眠,wait是等待。

使用上:从使用角度看,sleep是Thread线程类的方法,而wait是Object顶级类的方法。

sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用。

CPU及资源锁释放:sleep,wait调用后都会暂停当前线程并让出cpu的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,而wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行。

异常捕获:sleep需要捕获或者抛出异常,而wait/notify/notifyAll不需要。

11、谈一下你对ThreadLocal的了解?

Android源码学习(4) Handler之ThreadLocal

14、非UI线程真的不能更新UI吗?

总结:当检查当前线程是否是主线程这个方法checkThread()执行后,才不能在子线程更新UI。在此之前是可以在子线程中更新UI的

15、在子线程中创建Handler报错是为什么?

没有lopper.prepare

16、如何在子线程创建Looper?

Looper.prepare();

17、为什么通过Handler能实现线程的切换?

Handler.post的逻辑在哪个线程执行,是由Looper所在线程还是Handler所在线程决定的? 是由Looper所在线程决定的

最终逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此由Looper所在线程决定。

18、Handler的post/send()的原理?

建议看一下源码: 通过一系列sendMessageXXX()方法将msg通过消息队列的enqueueMessage()加入到队列中。

19、Handler的post方法发送的是同步消息吗?可以发送异步消息吗?

用户层面发送的都是同步消息 不能发送异步消息 异步消息只能由系统发送。

20、Handler的post()和postDelayed()方法的异同?

底层都是调用的sendMessageDelayed() post()传入的时间参数为0 postDelayed()传入的时间参数是需要的时间间隔。

21、Handler构造方法中通过Looper.myLooper();是如何获取到当前线程的Looper的?

myLooper()内部使用ThreadLocal实现,因此能够获取各个线程自己的Looper

22、Looper.loop()是如何阻塞的?MessageQueue.next()是如何阻塞的?

通过native方法:nativePollOnce()进行精准时间的阻塞。

23、Looper的两个退出方法以及它们的区别?

quit: quitSafely:

24、Looper.loop()的源码流程?

1.获取到Looper和消息队列

2.for无限循环,阻塞于消息队列的next方法

3.取出消息后调用msg.target.dispatchMessage(msg)进行消息分发

25、Looper.loop()在什么情况下会退出? 1.next方法返回的msg == null

2.线程意外终止

26、Looper.quit/quitSafely的本质是什么? 1.让消息队列的next()返回null,依次来退出Looper.loop()

ThreadLocal的原理 thread.threadLocals就是当前线程thread中的ThreadLocalMap ThreadLocalMap中有一个table数组,元素是Entry。根据ThreadLocal(需要转换获取到Hash Key)能get到对应的Enrty。 Entry中key为ThreadLocal, value就是存储的数值

27、如何获取到当前线程?

Thread.currentThread()就是当前线程

28、如何在ThreadLocalMap中,ThreadLocal如何作为键值对中的key?

通过ThreadLocal计算出Hash key,通过这个哈希值来进行存储和读取的。

29、Handler是怎么做到消息延时发送的

学习参考:

www.jianshu.com/p/b4d745c7f…

www.zhihu.com/question/34…

Android开发进阶从小工到专家