從Android6.0源碼的角度剖析Handler消息機制原理

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一個非常好的選擇!