0
点赞
收藏
分享

微信扫一扫

Handler核心源码分析

年夜雪 2022-03-11 阅读 104

 

目录

Handler的使用

Handler初始化

发送消息

处理消息

MessageQueue的阻塞和唤醒

阻塞

唤醒

Handler对我们开发者的启发

亮点一

亮点二

Looper什么时候推出

Handler常见面试题


大家有没有发现,我们在Android开发过程中,很少遇到过多线程并发的问题,这个就得益于Android为我们提供的线程间通信工具 Handler了,所以我们要了解它是怎么实现跨线程通信的。

Handler的使用

我们首先知道Handler是怎么用的,再去剖析其核心源码。

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
	private static final String TAG = "MainActivity";
	private TextView mTvMain;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
	}
	
	@Override
	protected void onStart() {
		super.onStart();
		new Thread(() -> {
			try {
				// 模拟耗时操作
				Thread.sleep(5000);
				// 获取 Message 实例对象
				Message msg = Message.obtain();
				// 设置 Message 对象的识别内容
				msg.what = Constants.MSG_UPDATE_TEXT;
				// 通过 Handler 把消息发送出去
				handler.sendMessage(msg);
			} catch (InterruptedException e) {
				Log.e(TAG, "onStart: InterruptedException");
			}
		}).start();
	}
	
	private void initView() {
		mTvMain = findViewById(R.id.tv_main);
	}
	
	@SuppressLint("HandlerLeak")
	private Handler handler = new Handler() {
		@Override
		public void handleMessage(@NonNull Message msg) {
			switch (msg.what) {
				case Constants.MSG_UPDATE_TEXT:
					mTvMain.setText("已完成更新操作");
			}
		}
	};
}

接下来我们就按照上面代码案例去剖析Handler源码,顺序依次是Handler初始化、

Handler初始化

 最终会调用如下方法:

	/*
	 * 将此标志设置为 true 以检测扩展此 Handler 类的匿名类、本地类或成员类。 这种类可能会造成泄漏。
	 */
	private static final boolean FIND_POTENTIAL_LEAKS = false;
	
	// 如果我们没有调用Looper.prepare(),sThreadLocal.get() 将返回null
	// @UnsupportedAppUsage 不让我们开发者使用
	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	final Looper mLooper;
	final MessageQueue mQueue;
	// @UnsupportedAppUsage 不让我们开发者使用
	final Handler.Callback mCallback;
	final boolean mAsynchronous;
	public Handler(@Nullable Handler.Callback callback, boolean async) {
		if (FIND_POTENTIAL_LEAKS) {
			final Class<? extends Handler> klass = getClass();
			// 如果我们创建的Handler类对象是匿名类,或者是成员类(内部类)、或者局部类(方法中创建的类)并且该类不是静态的
			// 就有内存泄漏的风险。
			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;
	}
	
	
	/**
	 * 返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
	 *
	 * @return Looper对象
	 */
	public static @android.annotation.Nullable
	Looper myLooper() {
		return sThreadLocal.get();
	}

这里我们重点关注一下 mLooper = Looper.myLooper(); 这行代码,myLooper的实现是:sThreadLocal.get();这里我们就要说说 ThreadLocal了,可参考我的另一篇博文:并发编程基础(二)—— ThreadLocal及CAS基本原理剖析

其实说白了,ThreadLocal 正如其名,它就是一个线程本地副本,我们的线程内部会有一个ThreadLocal.ThreadLocalMap的成员:

再来看看具体的get方法:

	/**
	 * 返回此线程中 ThreadLocalMap成员以ThreadLocal为key对应的值(Object类型),如果ThreadLocalMap成员为null,
	 * 则首先将其初始化,调用 {@link #initialValue} 。
	 *
	 * @return 返回当前线程的中 ThreadLocal 对应的值
	 */
	public T get() {
		// 获取当前线程
		Thread t = Thread.currentThread();
		// 获取当前线程的 ThreadLocal.ThreadLocalMap 成员
		ThreadLocalMap map = getMap(t);
		// 如果map不为空
		if (map != null) {
			// 通过 ThreadLocal 获取Entry
			ThreadLocalMap.Entry e = map.getEntry(this);// 这里的this就是当前线程副本 ThreadLocal 的实例
			// 如果 Entry 不为空,返回它的 value 属性的值
			if (e != null) {
				@SuppressWarnings("unchecked")
				T result = (T) e.value;
				return result;
			}
		}
		//否则设置初始值
		return setInitialValue();
	}

上面的代码包含了 ThreadLocalMap.Entry:

我们发现 Entry 的构造函数包含 ThreadLocal 和 Object,而它又是 ThreadLocalMap  的内部类,那我们就可以理解为 ThreadLocalMap 是以 ThreadLocal 为 key,任意对象为 value 的键值对结构(Map结构),即是一一对应的。

既然有 get 方法,那就有 set 方法了,我们发现在 Looper.prepare() 调用了 set:

	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
		// 如果已经给当前线程的 ThreadLocal 设置过 一个Looper,则抛出异常,这就保证了 一个 ThreadLocal 只对应一个 Looper
		if (sThreadLocal.get() != null) {
			throw new RuntimeException("Only one Looper may be created per thread");
		}
		// 否则 new 一个 Looper对象,并设置给当前线程的 ThreadLocal
		sThreadLocal.set(new Looper(quitAllowed));
	}

	// Looper 的构造方法
	private Looper(boolean quitAllowed) {
		mQueue = new MessageQueue(quitAllowed);
		mThread = Thread.currentThread();
	}

 如上代码通过判断 sThreadLocal.get() 是否为 null 的逻辑,保证了每一个线程的 sThreadLocal 只会调用一次 set 方法把 sThreadLocal 和Looper 绑定起来,而 sThreadLocal 又是static final的,那也就是说一旦线程调用了 Looper 的方法, sThreadLocal 就会被赋值,并且一旦赋值就不可更改,说明 Looper 与 ThreadLocal 是一一对应的。而且我们在 Android 源码中全局搜索 MessageQueue 的构造方法,发现只有在 Looper 的构造方法中调用了
new MessageQueue(quitAllowed),并且 MessageQueue 的构造方法是包管理权限,也就是说我们普通开发者是不能调用的,那也就说明了通过 Looper 构造方法唯一创建 MessageQueue 对象,实现了 Looper 和 MessageQueue 的一一对应。再结合上面我们对 ThreadLocal 的学习,我们知道了 一个线程对应唯一的 ThreadLocal , 那么就说明 Android的 Handler 机制中,Thread、ThreadLocal、Looper、MessageQueue这四者是一一对应。

发送消息

Handler的主要方法

调用流程:

Handler.sendMessage() --> Handler.sendMessageDelayed()--> Handler.sendMessageAtTime()-->  Handler.enqueueMessage()--> MessageQueue.enqueueMessage()

可以看到最终会调用 MessageQueue.enqueueMessage() 将 Message 插入队列。

Handler工作流程图

 

那么接下来我们剖析enquque方法:

	Message next; // Message的成员
	
	Message mMessages;// MessageQueue 的成员,可以看作是队列的队头
	boolean enqueueMessage(Message msg, long when) {
        // 省略不是很重要的代码

        // 加锁保证线程安全,防止多个线程同时给MessageQueue中插入消息
		synchronized (this) {
            // 省略不是很重要的代码
			
			// 给要插入消息队列(MessageQueue)中的消息指定延迟时间,也就是在多久之后处理此消息
			msg.when = when;
			// 把消息队列的队头赋值给 p
			Message p = mMessages;
			// 是否唤醒消息队列
			boolean needWake;
			// 最开始,消息队列肯定是空的。那如果当前消息队列中没有消息,或者我们插入的消息的等待时间是0,那我们就把消息插入,
			// 并让其指向null;
			// 或者消息队列中有一个即将处理的消息,而我们插入的消息等待时间小于即将要处理的消息,那就把我们插入的消息放在其前面。
			if (p == null || when == 0 || when < p.when) {
				// 插入的消息的下一个消息指向p,p可能为null
				msg.next = p;
				// 把我们要插入的消息赋值给消息队列的队头,实现插入队列操作
				mMessages = msg;
				// 当消息队列中没有消息时,就会阻塞,此时 mBlocked为 true
				needWake = mBlocked;
			}
			// 这种情况是消息队列中有 N多个消息了
			else {
                // 插入队列中间。 通常我们不必唤醒消息队列,除非队列头部有屏障,并且消息是队列中最早的异步消息。
				needWake = mBlocked && p.target == null && msg.isAsynchronous();
				
				Message prev;
				// 通过死循环,不停地比较我们要插入的消息与队列中已有的消息
				for (;;) {
					// 当前消息队列中用于作比较的消息作为前一个消息
					prev = p;
					// 然后让 prev指向 p(p指向p.next)
					p = p.next;
					// 从消息队列中的第一个消息开始遍历,直到消息队列的末尾即null值,方可跳出循环;
					// 或者要插入的消息等待时间小于当前我们正在做比较的消息,此时也跳出循环。
					if (p == null || when < p.when) {
						break;
					}
				}
				// 插入消息
				msg.next = p; // invariant: p == prev.next
				prev.next = msg;
			}
		}
		return true;
	}

Message入队示意图

处理消息

入队我们讲完了,下来该讲出队了。出队我们是通过 Looper.loop()实现:

	/**
	 * 在当前线程中运行消息队列。 请务必调用 {@link #quit()} 来结束循环。以下代码省略不必要(看不懂)的代码进行解析
	 */
	public static void loop() {
		// 获取当前线程的 Looper 对象
		final Looper me = myLooper();
		if (me == null) {
			// 这个异常在我们初学者会经常遇到,因为初学者总是忘记在子线程调用 Looper.prepare()
			throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
		}
		// 获取当前线程的 MessageQueue 对象
		final MessageQueue queue = me.mQueue;
		
		for (; ; ) {
			// 取出消息 --- 出队
			Message msg = queue.next(); // 可能会阻塞,当MessageQueue没有消息是,会调用nativePollOnce(ptr, -1);一直挂起
			if (msg == null) {
				// 没有消息表示消息队列正在推出
				return;
			}
			
			try {
				// target是Message中的 Handler 成员
				msg.target.dispatchMessage(msg);
			} catch (Exception exception) { }
			finally { }
			
			// 回收可能正在使用的消息
			msg.recycleUnchecked();
		}
	}

loop方法中包含一个死循环,通过 MessageQueue.next() 不停地取出消息:

	/**
	 * 消息出队,同样省略 N多行不是很重要的(看不懂的)代码
	 *
	 * 尝试检索下一条消息, 如果找到返回。
	 *
	 * @return 返回即将要被处理的消息
	 */
	private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
	
	Message next() {
		int pendingIdleHandlerCount = -1; // -1 only during first iteration
		int nextPollTimeoutMillis = 0;
		for (;;) {
			// 调用 native 方法睡眠 nextPollTimeoutMillis 这么多毫秒,如果该值为-1,则会无限等待
			nativePollOnce(ptr, nextPollTimeoutMillis);
			synchronized (this) {
				// 自启动以来的非睡眠正常运行时间毫秒数。
				final long now = SystemClock.uptimeMillis();
				Message prevMsg = null;
				Message msg = mMessages;// mMessages可以看作是消息队列的队头
				if (msg != null && msg.target == null) {
					// 被一道屏障挡住了,查找队列中的下一条异步消息。
					do {
						prevMsg = msg;
						msg = msg.next;
					} while (msg != null && !msg.isAsynchronous());
				}
				if (msg != null) {
					if (now < msg.when) {
						// 下一条消息尚未准备好,设置延迟时间以在它准备好时唤醒。
						nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
					} else {
						// 下一条消息已就绪,等待被处理,mBlocked置为 false
						mBlocked = false;
						if (prevMsg != null) {
							prevMsg.next = msg.next;
						} else {
							mMessages = msg.next;
						}
						msg.next = null;
						// 返回一个消息等待处理
						return msg;
					}
				} else {
					// 没有消息的情况
					nextPollTimeoutMillis = -1;
				}
				
				if (pendingIdleHandlerCount < 0
						&& (mMessages == null || now < mMessages.when)) {
					// mIdleHandlers是一个ArrayList,通过 addIdleHandler 添加,一般我们不会调用此方法,
					// 所以大多数情况下 mIdleHandlers.size()是0
					pendingIdleHandlerCount = mIdleHandlers.size();
				}
				if (pendingIdleHandlerCount <= 0) {
					// 没有要运行的空闲 Handler ,循环并等待更多时间 ,所以大多情况下是一直阻塞的,这也就解释了为什么我们的
					// Activity 显示出来之后,我们只要一直亮屏,它就不会结束,因为ActivityThread的main方法调用了Looper.loop,
					// 使得程序一直挂起。
					mBlocked = true;
					continue;
				}
			}
		}
	}

最终调用  Handler.dispatchMessage(),而 Handler.dispatchMessage() 就会回调 handleMessage()。

 

MessageQueue的阻塞和唤醒

阻塞

其实在上面的代码示例中,我已经讲过在调用 MessageQueue.next() 时,会调用 nativePollOnce(ptr, nextPollTimeoutMillis) 实现阻塞,当 nextPollTimeoutMillis 是-1时,会一直阻塞。没明白的同学再好好看看上面的代码(一定要看注释)。

唤醒

在MessageQueue.enqueueMessage() 中有这样一段逻辑:

//  是否需要唤醒
boolean needWake;
// 如果消息队列没有消息,那么 mBlocked 就为 true,那我插入消息时就得唤醒消息队列
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    // 如果 mBlocked 为true,即队列阻塞,则需要唤醒
    needWake = mBlocked;
}

if (needWake) {
    // 调用 native 方法唤醒队列
    nativeWake(mPtr);
}

// 没有 Handler 要处理时,或者可以理解为消息队列中没有消息时,消息队列就会阻塞
if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more.
    mBlocked = true;
    continue;
}

上面的代码意思是说,在 MessageQueue 中没有 Message 时,它就会阻塞,此时我们插入 Message,就会唤醒 Message。

Handler对我们开发者的启发

亮点一

 

	/**
	 * 在此处处理系统消息。
	 */
	public void dispatchMessage(@NonNull Message msg) {
		// 如果 Message 自己有callback,就调用 Message.callback.run()
		if (msg.callback != null) {
			handleCallback(msg);
		} else {
			// 如果我们创建 Handler 时给 Callback 赋值了,就走这里
			if (mCallback != null) {
				// 当 Callback 处理完消息之后,我们可以根据返回值确定,要不要走最后面的 handleMessage(msg);
				// 这个就与我们的View事件分发机制有些相似点了,根据返回值决定事件是否继续往下分发
				if (mCallback.handleMessage(msg)) {
					return;
				}
			}
			handleMessage(msg);
		}
	}
	
	private static void handleCallback(Message message) {
		message.callback.run();
	}
	
	/**
	 * 可以在实例化 Handler 时使用回调接口,以避免必须实现自己的 Handler 子类。
	 */
	public interface Callback {
		/**
		 * @param msg 一个{@link android.os.Message Message} 对象实例
		 * @return 如果不需要进一步处理,则为 true
		 */
		boolean handleMessage(@NonNull Message msg);
	}

上面代码就体现面向对象的封装思想,Message 封装了自己的 Callback ,如果 Message 是设置了它自己的 Callback,就回调自己的 callback.run();同样 Handler 封装了自己的 Callback ,如果 Handler 是设置了它自己的 Callback, 就回调自己的 handleMessage(),并且我们可以根据返回值决定要不要执行 Handler 子类重写的 handleMessage()。这样使得程序很灵活,有点像责任链模式。

亮点二

大家有没有想过,Handler 调用了 dispatchMessage 之后,就把消息出来完了,那消息是怎么回收的?

其实在 Looper.loop() 中,最终会调用 Message.recycleUnchecked() 进行所谓的消息回收(其实消息并未被回收),我们来看源码:

	public static final Object sPoolSync = new Object();
	
	/**
	 * 回收可能正在使用的消息。 处理排队的消息时,由 MessageQueue 和 Looper 在内部使用。
	 */
	@UnsupportedAppUsage
	void recycleUnchecked() {
		// 将消息标记为正在使用,同时保留在回收对象池中。清除所有其他详细信息。
		flags = FLAG_IN_USE;
		what = 0;
		arg1 = 0;
		arg2 = 0;
		obj = null;
		replyTo = null;
		sendingUid = UID_NONE;
		workSourceUid = UID_NONE;
		when = 0;
		target = null;
		callback = null;
		data = null;


        // 加锁,避免多个线程回收消息时,消息池的消息混乱		
		synchronized (sPoolSync) {
			if (sPoolSize < MAX_POOL_SIZE) {
				// 消息池的队头
				next = sPool;
				// 把当前处理完的消息赋值给队头
				sPool = this;
				// 消息池的消息量加一
				sPoolSize++;
			}
		}
	}

然后再看看Message.obtain():

	/**
	 * 从消息池中返回一个 Message 实例。 避免我们在很多情况下创建新的消息对象。
	 */
	public static Message obtain() {
		synchronized (sPoolSync) {
			// 消息池队头不为空
			if (sPool != null) {
				// 队头赋值给要返回的消息对象
				Message m = sPool;
				// 队头指向 m 的下一个节点
				sPool = m.next;
				// m 的下一个节点置空
				m.next = null;
				m.flags = 0; // clear in-use flag
				sPoolSize--;
				return m;
			}
		}
		return new Message();
	}

我们发现上述代码跟我们登机之前做安检的场景很像,Message 就好比是放行李的盒子,Message的成员what、arg1、arg2、objtct就相当于是行李,当我们过完安检之后,盒子不会被丢弃,而是放在安检门口以备下一个过安检的人使用,这好比我们的消息池,其实这里所用的就是享元模式。

那么这样做有什么好处,从内存优化的角度思考,通过 Message.obtain() 获取消息大大减小了 new Message() 的调用,也就减少了连续内存空间被过度破坏,即不至于过度碎片化,也就是内存中连续空间更多了,那OOM出现的概率就小了。可能有同学说GC是干什么吃的,那大家有没有想过,既然 GC会帮我们回收垃圾,释放内存,为什么还会出现OOM。其实 GC即便用了标记整理算法,使得内存空间连续,但是GC线程工作的时候,会STW(stop the world),即其他所有线程都得挂起。而且我们不断地new 对象,又不断地触发 GC,会产生内存抖动,从而导致卡顿,所以从性能优化的角度来讲,我们尽量避免不必要的内存开销。

Looper什么时候推出

我们如果在子线程中创建 Looper 经常会有内存泄漏的问题,因为大部分同学都没有释放 Looper。那怎么办释放呢?通过调用 Looper.quitSafely() 或者 Looper.quit()

	/**
	 * 安全退出Looper。
	 * 处理完消息队列中所有剩余的消息后,立即终止 {@link #loop} 方法。 但是,在 loop 终止之前,将不会传递未来到期的待处理的延
	 * 迟消息。在要求 Looper 退出后,任何向队列发布消息的尝试都将失败。 例如,{@link Handler#sendMessage(Message)} 方法
	 * 将返回 false。
	 */
	public void quitSafely() {
		mQueue.quit(true);// 安全退出传的参数是 true,而Looper.quit()传的参数是 false
	}

最终会调用到MessageQueue.quit():

	/**
	 * 退出Looper
	 *
	 * @param safe 是否需要安全退出
	 */
	void quit(boolean safe) {
		// 如果不允许退出,会抛异常,ActivityThread中的Looper就不允许退出。
		if (!mQuitAllowed) {
			throw new IllegalStateException("Main thread not allowed to quit.");
		}

		synchronized (this) {
			// 如果正在退出,则return
			if (mQuitting) {
				return;
			}
			mQuitting = true;

			if (safe) {
				// 安全退出
				removeAllFutureMessagesLocked();
			} else {
				// 不安全退出
				removeAllMessagesLocked();
			}

			// We can assume mPtr != 0 because mQuitting was previously false.
			nativeWake(mPtr);
		}
	}

安全退出的实现:

private void removeAllFutureMessagesLocked() {
		// 获取当前时间
		final long now = SystemClock.uptimeMillis();
		// 消息队列的队头赋值给 p
		Message p = mMessages;
		// 如果队头存在
		if (p != null) {
			// 如果队头延迟时间大于当前时间,移除所有消息
			if (p.when > now) {
				removeAllMessagesLocked();
			} else {// 继续判断,取队列中所有大于当前时间的消息
				Message n;
				for (;;) {
					n = p.next;
					if (n == null) {
						return;
					}
					if (n.when > now) {
						break;
					}
					p = n;
				}
				p.next = null;
				// 将所有所有大于当前时间的消息回收,延迟时间小于当前时间的消息即使消息队列退出了,仍然会继续被取出执行
				do {
					p = n;
					n = p.next;
					p.recycleUnchecked();
				} while (n != null);
			}
		}
	}

不安全退出的实现:

	/**
	 * 移除消息队列所有消息,包括延迟时间小于当前时间的消息
	 */
	private void removeAllMessagesLocked() {
		Message p = mMessages;
		// 轮循队列中所有 Message对象,一一缓存到消息池中
		while (p != null) {
			Message n = p.next;
			// 缓存到消息池中
			p.recycleUnchecked();
			p = n;
		}
		// 队头置空
		mMessages = null;
	}

Handler常见面试题

1.一个线程有几个 Handler?
在一个线程中我们可以创建多个 Handler。

2.一个线程中有几个 Looper?是如何保证的?
一个线程中只有一个 Looper;
我们在线程中使用 Looper之前,先要调用 Looper.preprare,而prepare 中用到了 ThreadLocal,ThreadLocal是线程本地副本,是每个线程独有的成员,线程就是通过它实现线程数据隔离的,并且 ThreadLocal 存储数据是通过其内部类 ThreadLocalMap中的内部类 Entry 存储的,Entry 的构造方法中包含两个成员,ThreadLocal 和 Object,所以在 Handler机制中,可以把 ThreadLocalMap 理解为以 ThreadLocal 为 key,Looper为 value的键值对。然后我们发现 Looper中的 ThreadLocal 成员是被 static final 修饰的,那也就是说 Looper 被加载时,它的成员 ThreadLocal 就被初始化了,且不可更改。那就保证了 ThreadLocal 的唯一性,那我只要再保证,Looper 的唯一性,就可以说明在 Handler机制中,Thread、Looper、ThreadLocal三者是一一对应的 。我们发现 prepare 方法中会先判断是否给 ThreadLocal 已经设置过值,如果没有设置过,new Looper()设置给它,否则会抛出异常。这就说明了 ThreadLocal 和 Looper 也是一一对应的了。

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


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


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


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

7.我们使用 Message 时应该如何创建它?
Message.obtain();因为它采用享元模式,重复利用回收的消息,大大减少new Message() 的概率,从内存优化的角度看,减少不必要的内存开销,可以有效避免内存过度碎片化,从而降低出现OOM的概率。

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

某些题目答案过段时间再公布, 大家开动聪明的小脑袋先思考,欢迎评论区与我交流,文章有不准确之处,还望指正 ヾ( ̄ー ̄)X(^▽^)ゞ

举报

相关推荐

0 条评论