1. 程式人生 > >從Android6.0原始碼的角度剖析Handler訊息機制原理

從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方法