Android进阶 - Handler底层源码分析(二)

208 阅读6分钟

Android进阶 - Handler底层源码分析二


上一篇本篇带领大家分析handler常见面试题.

一个线程有几个 Handler?

一个线程中可以有多个Handler,new几个就有一个

一个线程有几个 Looper?如何保证?

一个线程中只能有一个Looper,

在调用Looper.prepare()的时候,会通过ThreadLocal来初始化Looper,

Handler.java

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
    }

通过sThreadLocal.get()方法来判断当前TheradLocal是否有值,如果有值则提示Only one Looper may be created per thread异常,如果没有Looper通过set()方法设置Looper,所以一个线程只能存在一个Looper

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在Looper初始化的时候会初始化MessageQueue(),所以一个线程中耶只有一个MessageQueue()

Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?

Handler内存泄漏是因为内部类持有外部类的引用
在这里插入图片描述
在这里Handler持有Activity的引用,因为Handler可以调用到Activity的方法

在调用Handler.sendXXX/postXX发送消息的时候,会调用到enqueueMessage()方法,msg.target会把Handler对象保存下来,所以Message持有Handler对象

class Message{
	Handler target;
}

class Handler{
	private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

如果现在有一个消息需要等待5分钟来执行,但此时页面已经关闭了,消息还在队列中等待执行,那么Message持有Handler对象,Handler持有Activity对象,所以Activity不会被GC回收导致内存泄漏.

其他的内部类为什么不会内存泄漏,例如RecyclerView中的ViewHolder?

因为生命周期不同,ViewHolder在RecyclerView消失的时候也会消失.

为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?

因为在启动的时候,ActivityThread中系统帮我们调用了Looper.prepare() 和Looper.loop()方法

方式一:

new Thread(() -> {
            Looper.prepare();
            TextView tv = findViewById(R.id.tv);
            tv.setText("青岛啤酒111");

            Looper.loop();
        }).start();

效果图(1.1)

方式二:

    //传入参数 = 线程名字
    HandlerThread handlerThread = new HandlerThread("TextHandler");
    //启动线程
    handlerThread.start();

    //消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
    Handler handler1 = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            TextView tv2 = findViewById(R.id.tv2);
            tv2.setText("当前线程名为:"+Thread.currentThread().getName());
        }
    };

    // 使用线程Handler向工作线程的消息队列发送消息
    Message msg = Message.obtain();
    msg.what = 2; //消息的标识
    //  通过Handler发送消息到其绑定的消息队列
    handler1.sendMessage(msg);

效果图(1.2)

参考自:www.jianshu.com/p/9c10beaa1…

子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

调用Looper.quit()方法

  • Looper.quit():

    • 调用后直接终止Looper,不在处理任何Message,所有尝试把Message放进消息队列的操作都会失败,比如Handler.sendMessage()会返回 false,但是存在不安全性,因为有可能有Message还在消息队列中没来的及处理就终止Looper了。
  • Looper.quitsafely()

    • 调用后会在所有消息都处理后再终止Looper,所有尝试把Message放进消息队列的操作也都会失败。

参考自:www.jianshu.com/p/2c7c7863d…

既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?

class MessageQueue{
	//往队列中添加消息
	boolean enqueueMessage(Message msg, long when) {
	    	...
	        synchronized (this) {
	          	...
	            if (needWake) {
	                //native底层 唤醒
	                nativeWake(mPtr);
	            }
	        }
	        return true;
	    }

	//取出队列中的消息
	Message next() {
    	...
        for (;;) {
            //native底层实现堵塞,堵塞状态可被新消息唤醒,头一次进来不会延迟
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
				...
			}
}

在enqueueMessage()存消息和next()取消息的时候,都用了synchronized()同步锁,将代码都同步了

消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。

我们使用 Message 时应该如何创建它?

  • 一种是直接 new 一个 Message 对象,
  • 另一种是通过调用 Message.obtain() 的方式去复用一个已经被回收的 Message,

当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。

Message 中的 sPool 就是用来存放被回收的 Message,当我们调用 obtain 后,会先查看是否有可复用的对象,如果真的没有才会去创建一个新的 Message 对象。

补充:主要的 Message 回收时机是:

  • 在 MessageQuese 中 remove Message 后;
  • 我们主动调用 Message 的 recycle 方法后;

Message 的数据结构是什么样子?

单链表,Message 中会通过 next 来持有下一个 Message 对象的引用

class Message{
	Message next;
}

Looper死循环为什么不会导致应用卡死

class Looper{
	public static void loop() {
		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;
            }
            ...
        }
	}
}

来看看MessageQueue的中的next代码:

class MessageQueue{

	//往队列中添加消息
	boolean enqueueMessage(Message msg, long when) {
	    	...
	        synchronized (this) {
	          	...
	            if (needWake) {
	                //native底层 唤醒
	                nativeWake(mPtr);
	            }
	        }
	        return true;
	    }
	    
	    //取出队列中的消息
		Message next() {
     		...
        for (;;) {
            //native底层实现堵塞,堵塞状态可被新消息唤醒,头一次进来不会延迟
            nativePollOnce(ptr, nextPollTimeoutMillis);
          }
}

在MessageQueue.next()方法,获取消息的时候会通过nativePollOnce()实现堵塞,堵塞状态可被新消息唤醒,

在MessageQueue.enqueueMessage()往队列中添加消息的时候,会通过nativeWake()唤醒

简单理解:
一般情况下,Looper.loop()方法在阻塞状态,如果现在有消息会执行到MessageQueue.enqueueMessage()会把loop()唤醒,然后调用MessageQueue.next()取消息,之后又会把loop()阻塞

HandlerThread是什么?

HandlerThread是Thread的子类,严格意义上来说就是一个线程,只是它在自己的线程里面帮我们创建了Looper

如有不足,欢迎评论补充~

上一篇

博主首页

原创不易,您的点赞就是对我最大的支持~