从Android6.0源码的角度剖析Handler消息机制原理

版权声明:精髓原创,欢迎转载、指正、探讨。--蒋东国 https://blog.csdn.net/AndrExpert/article/details/84037318

ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到Activity中取执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

转载请声明出处:https://blog.csdn.net/AndrExpert/article/details/84037318

1. Handler消息机制源码剖析

 众所周知,为了保证UI渲染的实时性和防止出现不可知的问题,Android规定渲染更新UI只能在主线程进行,且主线程不能执行耗时操作,否则就会报ANR异常,因此,耗时操作必须在子线程中进行。那么问题来了,倘若我们需要在子线程中需要更新UI怎么办?不捉急,Android系统为我们提供了Handler消息处理机制来搞定,通常的做法是:首先,在主线程创建一个Handler的实例并重写它的handleMessage()方法,该方法用于接受处理消息;然后,当线程执行完耗时任务后,使用主线的Handler实例send发送一个消息Message给主线程,通知主线程更新UI。整个过程非常简单,但是当你了解过它的实现原理,才真的能够体会越是简单的东西,被设计得越巧妙。

1.1 实例化Handler对象,同时实例化一个Looper和MessageQuue

  在Handler的构造方法中,主要做三件事情:首先,创建当前线程对应的一个Looper对象,如果该实例不存在,则抛出异常"Can't create handler inside thread that has not called Looper.prepare()";其次,创建当前线程与Looper对应的一个消息队列MessageQueue,它被缓存在Looper对象的mQueue变量中;最后,设置消息处理回调接口,可以不设置。Handler构造方法源码如下:

public Handler(Callback callback, boolean async) {
	// 判断当前Hander或其子类是否为静态类
	// 如果不是,容易导致内存泄露,这里只是给出警告提示
	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());
		}
	}
	// 获取当前线程的Looper实例
	// 如果不存在,则提示需先调用 Looper.prepare()
	mLooper = Looper.myLooper();
	if (mLooper == null) {
		throw new RuntimeException("Can't create handler inside thread 
			that has not called Looper.prepare()");
	}
	// 获取当前线程的消息队列
	mQueue = mLooper.mQueue;
	// 设置回调接口,在处理消息时会讲到
	mCallback = callback;
	mAsynchronous = async;
}    

 Looper.myLooper()方法用于返回当前线程的Looper对象,该对象创建后会被存放到ThreadLocal中。ThreadLocal是一个全局对象,它能够确保每个线程都拥有自己的Looper和MessageQueue,后面我们会单独讲解。Looper.myLooper()源码如下:

// 全局对象sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
	// 返回当前线程的Looper对象
	return sThreadLocal.get();
}

  通过查看Looper类源码发现,它构造方法为私有(private)方法,因此无法在Looper类的外部通过new的方式实例化一个Looper对象。也就是说,我们只能通过Looper内部向外提供的方法实现对Looper对象的创建,而这个方法就是Looper.prepare(),该方法又继续调用了Looper的私有静态方法prepare(boolean quitAllowed)。prepare(boolean quitAllowed)主要做两件事情:
  (1) 确保每一个线程只对应一个Looper实例;
  (2) 创建当前线程的Looper实例,并将其存储到ThreadLocal变量中,同时创建一个与Looper对象相对应的消息队列MessageQueue。

public static void prepare() {
	prepare(true);
}

private static void prepare(boolean quitAllowed) {
	// 每一个线程只能存在一个Looper对象
	if (sThreadLocal.get() != null) {
		throw new RuntimeException("Only one Looper may be created per thread");
	}
	//实例化一个Looper对象,存储到ThreadLocal中
	sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
	// 创建一个MessageQueue消息队列
	mQueue = new MessageQueue(quitAllowed);
	// 获取当前线程对象
	mThread = Thread.currentThread();
}
2.2 使用Handler发送消息

 在实际开发中,通常我们调用Handler的sendMessage(Message msg)、sendEmptyMessage(int what)或sendEmptyMessageDelayed(long time)方法来发送一条消息(Message),这些方法最终会调用sendMessageAtTime(),该方法首先会获取获取当前线程的消息队列实例mQueue,然后继续调用enqueueMessage()方法通过调用当前消息队列MessageQueue.enqueueMessage()方法将消息Message插入到当前消息队列中。Handler的sendMessageAtTime()和enqueueMessage()方法源码如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	// 获取消息队列实例mQueue
	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) {
	// 将当前Handler实例缓存到消息Message的target变量中
	msg.target = this;
	if (mAsynchronous) {
		msg.setAsynchronous(true);
	}
	// 将消息(Message)插入到消息队列中
	return queue.enqueueMessage(msg, uptimeMillis);
}

 接下来,我们重点分析下MessageQueue.enqueueMessage()方法,它的主要工作就是判断消息队列是否为空,如果为空,则直接插入消息Message;如果不为空,则找到消息队列的末尾位置再将消息Message插入。另外,从enqueueMessage()方法的源码可知,该方法是线程安全的。enqueueMessage()方法源码如下:

boolean enqueueMessage(Message msg, long when) {
	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.");
	}
	// 同步锁,该方法为线程安全
	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 {
			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;
				}
			}
			// 将Message插入到消息队列中
			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;
}
2.2 使用Handler接收处理消息

 众所周知,Handler消息机制中接收和处理消息Message最终是在Handler的handleMessage(Message msg) 方法中实现。那么问题来了,在Handler消息机制中是如何将Message传递到handleMessage的?这时候,Looper就派上用场,它有一个Looper.loop()方法,该方法被调用执行后会不断的循环遍历消息队列MessageQueue,然后将获取到的消息Message传递给Handler的handleMessage()方法。下面我们就来看下Looper.loop()方法具体实现,源码如下:

public static void loop() {
	// 获取当前线程的Looper对象
	final Looper me = myLooper();
	if (me == null) {
		throw new RuntimeException("No Looper; Looper.prepare() wasn't called on 						this thread.");
	}
	// 获取当前线程的MessageQueue对象
	// 它被缓存在Looper对象的mQueue变量中
	final MessageQueue queue = me.mQueue;
	Binder.clearCallingIdentity();
	final long ident = Binder.clearCallingIdentity();
	// 开启一个无限循环,取Message
	for (;;) {
		// 从消息队列中取一条Message
		Message msg = queue.next(); // might block
		if (msg == null) {
			// No message indicates that the message queue is quitting.
			return;
		}

		// This must be in a local variable, in case a UI event sets the logger
		Printer logging = me.mLogging;
		if (logging != null) {
			logging.println(">>>>> Dispatching to " + msg.target + " " +
						msg.callback + ": " + msg.what);
		}
		// 将消息传递(发送)到Handler的dispatchMessage
		// 其中,msg.target即当前线程的Handler对象
		msg.target.dispatchMessage(msg);
		...
		msg.recycleUnchecked();
	}
}

 从Looper.loop()方法源码可知:首先,该方法会获得当前线程的Looper对象,以便从Looper对象中获得对应的消息队列MessageQueue的实例queue;然后,开启一个无限循环不断地从消息队列queue中取Message(消息),只有当消息队列被quit时queue.next()返回null才会退出循序,因此,我们在一个线程中开启loop时待使用完毕后需要调用Looper.quit()或Looper.quitSafely()进行退出循环操作。其中,这两个方法均调用MessageQueue的quit(boolean safe)方法。其源码如下:

void quit(boolean safe) {
	// 主线程(UI线程)所属的消息队列禁止退出
	if (!mQuitAllowed) {
		throw new IllegalStateException("Main thread not allowed to quit.");
	}
	synchronized (this) {
		if (mQuitting) {
			return;
	}
	// 将退出标志置true
	mQuitting = true;

	if (safe) {
		removeAllFutureMessagesLocked();
	} else {
		removeAllMessagesLocked();
	}
	// We can assume mPtr != 0 because mQuitting was previously false.
	nativeWake(mPtr);
}
    

最后,获取缓存在Message对象中对应当前线程的Handler对象(注:msg.target即为Handler对象,可从Handler.enqueueMessage()方法中获知),并调用它的dispatchMessage(Message msg)方法将Message(消息)传递给Handler进行处理。通过查看dispatchMessage的源码可知,Handler处理消息的方式有三种,即(1)如果msg.callback不为空,则调用Runnable的run()方法来处理,其中callback在Message.obtain(Handler h, Runnable callback)中设置;(2)如果mCallback不为空,则调用mCallback.handleMessage(msg)处理消息,其中mCallback在Handler的构造方法<Handler(Callback callback, boolean async) >中设置;(3)使用Handler的handleMessage(Message)处理消息,该方法是一个空方法,我们通过在Handler继承类中重写该方法实现消息最终的处理,这也是我们在开发中常用的方式。Handler.dispatchMessage()相关源码如下:

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		// 处理方式一
		handleCallback(msg);
	} else {
		// 处理方式二
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
					return;
			}
		}
		// 处理方式三
		handleMessage(msg);
	}
}

// Handler.handleCallback()方法
private static void handleCallback(Message message) {
	// message.callback即为一个Runnable对象
	message.callback.run();
}

// Handler的内部接口Callback
public interface Callback {
	public boolean handleMessage(Message msg);
}

//Handler.handleMessage()方法
public void handleMessage(Message msg) {

}

 至此,关于Handler消息处理机制原理基本分析完毕,下面作个小结:

(1) Handler的消息处理机制由一个Handler对象、一个MessageQueue和一个Looper对象组成,Handler对象、MessageQueue对象和Looper对象属于同一个线程。其中,Handler用于发送和处理消息(Message);MessageQueue是一个消息队列,实质是一个单链表,用于存储消息(Message);Looper用于构建内部消息循环系统,它会不断地从消息队列MessageQueue中读取消息(Message),并将其传递给Handler进行处理。
在这里插入图片描述

(2) 使用Handler消息机制传递处理消息时,遵循以下流程(线程A处理消息,线程B发送消息):

 //-----------------------线程A处理消息-------------------
  // 第一步:创建Looper对象,同时会创建一个对应的MessageQueue
  Looper.prepare();
  // 第二步:创建Handler对象,通常为其子类的实例
  static ThreadAHandler aHandler = new ThreadAHandler() {
        // 处理消息
        void handleMessage(Message msg) {}
  }
  // 第三步:开启消息循环
  Looper.loop();
  // 第四步:退出循环,选择合适的时机
  Looper.quitSafely();
  
  //-----------------------线程B处理消息-------------------
  aHandler.sendMessage(Message.obtain(...));

(3) Handler除了提供send形式方法发送消息,还支持post形式方法发送消息。post形式方法实质调用了send形式方法,只是最终消息的处理按照上述第一种方式实现,即在Runnable的run()方法中处理消息。

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

// 构造一个Message实例
private static Message getPostMessage(Runnable r) {
	Message m = Message.obtain();
	// 对Message的callback进行赋值
	m.callback = r;
	return m;
}

(4) 在主线程(UI线程)中,比如Activity中,只需实现第二步即可实现在主线程中接收处理子线程发送过来的消息,同时更新UI。

2. Android主线程中的消息循环

 众所周知,Android规定访问(更新)UI只能在主线程中进行,如果在子线程中访问UI,那么程序就好抛出异常。因此,在日常开发中,通常会在子线程中处理耗时任务,然后再切换到主线程中根据任务结果更新UI,而这个过程就是通过Handler消息处理机制实现的。根据上一小节的结论,如果我们需要在主线程中处理子线程发送过来的消息Message,应该首先创建主线程的Looper,并loop开启消息循环。但是,在实际开发中我们却只是完成了对Handler的创建,即实现了消息传递-处理功能。这又是为什么呢?其实,主线程的消息循环系统也是遵从上述构建流程的,只是在APP启动后,Android系统会执行ActivityThread的main()方法(注:针对于非系统进程,如果为系统应用,则进程的入口为ActivityThread的SystemMain()方法),并会为其创建一个主线程ActivityThread,同时帮我们完成了Looper、MessageQueue的创建,并开启消息循环。ActivityThread的main()方法源码如下:

public static void main(String[] args) {
    ...
	// 创建主线程的Looper
	//prepareMainLooper()会调用Looper.prepare()方法,并将Looper对象缓存到sMainLooper变量
    Looper.prepareMainLooper();
	// 为主线程实例化一个ActivityThread
	// 用于管理主线程在应用进程中的执行等逻辑
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
	// 创建主线程的Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
	...
	// 开始循环读取消息
    Looper.loop();
    // 如果退出消息循环,报错
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

代码解析:

(1) 首先,调用Looper.prepareMainLooper()方法创建主线程的Looper对象。该方法会调用Looper.prepare()方法,并将Looper对象缓存到sMainLooper变量;

public static void prepareMainLooper() {
	prepare(false);
	synchronized (Looper.class) {
		// 在主线程再次调用prepareMainLooper会报错
		if (sMainLooper != null) {
			throw new IllegalStateException("The main Looper has already been prepared.");
		}
		sMainLooper = myLooper();
	}
}

(2) 其次,实例化一个ActivityThread对象,它管理应用进程的主线程的执行,并根据AMS的要求负责调度和执行Activities、Broadcasts、Services以及其他操作。ActivityThread的attach()方法会判断是否为系统进程,来执行相应的初始化操作,对于非系统进程来说,主要是将IActivityManager与IApplicationThread进行绑定。

//ActivityThread.attach()
private void attach(boolean system) {
	// 缓存ActivityThread实例
	sCurrentActivityThread = this;
	// 缓存是否为系统进程标志
	mSystemThread = system;
	// 该进程是否为系统进程
	// 很明显,我们的应用一般是非系统进程
	// 因此,system=false
	if (!system) {
		ViewRootImpl.addFirstDrawHandler(new Runnable() {
		@Override
			public void run() {
				ensureJitEnabled();
			}
		});
		android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
				UserHandle.myUserId());
		RuntimeInit.setApplicationObject(mAppThread.asBinder());
		// 获取应用Activity管理器AcitivtyManager
		final IActivityManager mgr = ActivityManagerNative.getDefault();
		try {
			// 将Activity管理器与Application绑定
			mgr.attachApplication(mAppThread);
		} catch (RemoteException ex) {
			// Ignore
		}
		// Watch for getting close to heap limit.
		// 设置GC
		BinderInternal.addGcWatcher(new Runnable() {
			@Override 
			public void run() {
					if (!mSomeActivitiesChanged) {
						return;
					}
					Runtime runtime = Runtime.getRuntime();
					// dalvi虚拟机缓存最大值
					long dalvikMax = runtime.maxMemory();
					// dalvi虚拟机已用空间
					long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
					if (dalvikUsed > ((3*dalvikMax)/4)) {
						...
						mSomeActivitiesChanged = false;
					try {
							mgr.releaseSomeActivities(mAppThread);
						} catch (RemoteException e) {
				}
			}
		}
	});
} else {
	// 系统进程 执行流程
	...
}

(3) 第三,为主线程创建一个Handler对象mH,但是这个对象为default属性,只供内部使用,这也解释了如果我们需要使用主线程的消息循环系统,只需在主线程重新创建一个Handler即可。

// H继承于Handler
final H mH = new H();

final Handler getHandler() {
	return mH;
}

3. 详解ThreadLocal工作机制

 前面说到,为了保证每个线程拥有自己的Looper对象,在Looper源码中是将创建好的Looper对象保存到同一个全局ThreadLocal对象sThreadLocal中。ThreadLocal是一个线程内部的数据存储类,它支持在多个线程中各自维护一套数据的副本,实现互不干扰地存储和修改数据。下面我们就从分析ThreadLocal源码的角度,来理解ThreadLocal的工作机制,其实主要是分析ThreadLocal的get()和set()方法。需要注意的是,ThreadLocal源码从Android7.0(API 24)后被重构,与Android6.0有一定的区别,这个以后有需要再分析。

3.1 ThreadLocal.set(T value),存储ThreadLocal的值
public void set(T value) {
	// 获取当前线程
	Thread currentThread = Thread.currentThread();
	// 从ThreadLocal.Values获取当前线程对应的Values对象
	// 如果不存在,则new一个出来,并缓存到ThreadLocal.Values
	Values values = values(currentThread);
	if (values == null) {
		values = initializeValues(currentThread);
	}
	// 存储value
	values.put(this, value);
}

代码解析:

(1) 首先,set方法会获取当前线程的Thread对象,然后通过values方法获取当前线程的ThreadLocal数据,即Values对象,它被缓存在Thread类内部的ThreadLocal.Values变量中。如果ThreadLocal.Values返回空,则为当前线程new一个Values出来,并缓存到ThreadLocal.Values。由此可知,Values就是用来存储、操作ThreadLocal的值,且每个线程都对应着一个Values对象

// 返回当前线程的ThreadLocal数据
Values values(Thread current) {
	return current.localValues;
}
// 为当前线程创建对应的Values对象
Values initializeValues(Thread current) {
	return current.localValues = new Values();
}

(2) 接着,我们来分析Values是如何存储ThreadLocal的值?通过查看Values源码,Values内部有一个数组:Object[] table;,ThreadLocal的值就存在这个table数组中,存储的规则根据Values.put(…)方法可知:ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值就存储在索引为index+1的table数组中。

void put(ThreadLocal<?> key, Object value) {
	cleanUp();
	// Keep track of first tombstone. That's where we want to go back
	// and add an entry if necessary.
	int firstTombstone = -1;
	
	for (int index = key.hash & mask;; index = next(index)) {
		Object k = table[index];
		// 如果ThreadLocal的值已经存在,则替换
		if (k == key.reference) {
				// Replace existing entry.
				table[index + 1] = value;
				return;
		}
		// 将ThreadLocal的值插入到数组下标为(index + 1)位置
		// 即table[index + 1] = value;
		if (k == null) {
				if (firstTombstone == -1) {
				// Fill in null slot.
                table[index] = key.reference;
                table[index + 1] = value;
                size++;
                return;
		}

        // Go back and replace first tombstone.
        table[firstTombstone] = key.reference;
        table[firstTombstone + 1] = value;
        tombstones--;
        size++;
        return;
	}

    // Remember first tombstone.
    if (firstTombstone == -1 && k == TOMBSTONE) {
    	firstTombstone = index;
    	}
	}
}
3.2 ThreadLocal.get(),获取ThreadLocal的值

 通过ThreadLocal的get()方法,可以获取存储在ThreadLocal的值。该方法比较简单,即首先获取当前线程及其对应的Values,然后通过查找到ThreadLocal的reference字段所在数组table中的下标index,就可以找到ThreadLocal的值,即table[index + 1]。源码如下:

    public T get() {
        // 获取当前线程
        Thread currentThread = Thread.currentThread();
        // 获取当前线程对应的Values
        // 如果values不为空,则找到ThreadLocal的reference的下标
        // table[index + 1]就是ThreadLocal的值
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

 至此,ThreadLocal的工作原理基本分析完毕,这里作个小结:ThreadLocal是一个线程内部的数据存储类,它支持在多个线程中各自维护一套数据的副本,实现互不干扰地存储和修改数据。也就是说,当我们通过ThreadLocal在指定的线程中存储数据后,除了该线程能够获取到存储的数据,其他的线程是无法获取到的,这是因为使用ThreadLocal存储数据时,在ThreadLocal的set()方法会为每个线程创建一个Values对象,该对象包括一个Object table[]数组,我们的数据将被存储在这个table数组中。当使用同一个ThreadLocal的get()方法读取数据时,实际上取出的是当前线程对应的table数组中的数据,从而有效地保证了不同线程之间存储同类型时的独立性。因此,当数据是以线程作为作用域并且不同线程具有不同的数据副本时,ThreadLocal一个非常好的选择!

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/84037318