1、能講講Android的handler機制嗎?
消息處理機制本質:<strong>一個線程開啟循環模式持續監聽并依次處理其它線程發送給它的消息.</strong>
簡單的說:Android應用程序是通過消息來驅動的,系統為每個應用程序維護一個消息隊列(MessageQueue),應用程序的主線程不斷地(Looper)從這個消息隊列中獲取消息(Message),然后對這些消息進行處理(Handler),這樣就實現了通過消息來驅動應用程序的執行.
2、Android消息處理機制的工作原理
3、Handler、Looper、MessageQueue、Message之間的關系.
Handler:
消息的發送和處理者,一般使用sendMessage()和handleMessage()方法來發送和處理消息。
Message:
線程之間傳遞的消息,它可以在內部攜帶少量的信息,用于在不同線程之間交換數據。
MessageQueue:
用于存放所有的Handler發送的消息的隊列(單鏈表),這些消息會一直存在于消息隊列中等待被處理,消息的處理遵循先進先出原則。
每個線程中只有一個MessageQueue對象。
Looper:
循環監聽MessageQueue中是否存在消息,如果存在就會將消息取出并傳遞給Handler處理。
每個線程中只有一個Looper對象。
4、ThreadLocal是什么?Android如何保證一個線程中最多只有一個Looper,一個MessageQueue?
ThreadLocal可以在不同的線程之中互不干擾的存儲并提供數據。
工作原理:每個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例本身,value是真正要存儲的Object。每次獲取或者設置value都是對該ThreadLocalMap映射表進行的操作,是與其他線程分開的。
在Handler的實現機制中,默認通過Looper類的prepare()方法來創建Looper對象并將其存儲在ThreadLocal中(實際存儲在了當前Thread所維護的ThreadLocalMap中,key為sThreadLocal,值為新建的Looper對象),這就保證了當前線程中有且僅有一個Looper對象。而Looper對象的創建同時也伴隨著MessageQueeu對象的創建,自然而然也確定了其唯一性。具體代碼如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
//如果線程的TLS已有數據,則會拋出異常,一個線程只能有一個Looper,prepare不能重復調用。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//往線程的TLS插入數據,簡單理解相當于map.put(sThreadLocal,new Looper(quitAllowed));
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
5、怎樣實現一個帶有消息循環(Looper)的線程?
Android系統的UI線程就是一種帶有消息循環(Looper)機制的線程,這種線程可以綁定Handler對象,并通過Handler的sendMessage()函數向線程發送消息。
private Handler mHandler;
private Looper mLooper;
private void createLooperThread(){
new Thread(new Runnable() {
@Override
public void run() {
// Log.e("MainActivity",Thread.currentThread().getName());
Looper.prepare(); //創建Looper和MessageQueue對象
mLooper = Looper.myLooper(); // 獲取當前線程下的Looper對象
createHandler();
Looper.loop(); // 開啟Looper循環 由于loop()里面是個死循環,有消息就處理,沒消息就掛起休眠,因此此行代碼之后的代碼是無法運行的。只有調用mLooper.quit()方法后,loop才會中止,其后的代碼才能得以運行。
}
}).start();
}
// 創建屬于 mLooper對象所在線程 的Handler對象
private void createHandler(){
mHandler = new Handler(mLooper){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// Log.e("MainActivity",Thread.currentThread().getName());
};
}
}
6、談談對HandlerThread的理解
HandlerThread本質上就是一個普通Thread,只不過在內部建立了Looper循環,它的實現很簡單,就是在run方法中通過Loop.prepare()來創建消息隊列,并通過Loop.loop()來開啟消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler。
HandlerThread比較適用于單線程+異步隊列的場景,比如一個長時間運行且沒有 UI 交互的任務,就像在將用戶數據上傳到服務器前進行的數據壓縮的操作就適合用HandlerThread。
另:IntentService內部就是通過HandlerThread來實現的。
HandlerThread的使用方法如下:
private HandlerThread mHandlerThread;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 創建一個線程
mHandlerThread = new HandlerThread("handlerthread");
// 開啟一個線程
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 這個方法運行在mHandlerThread線程中,可執行耗時操作
Log.d("handler","消息:" + msg.what + " 線程:" + Thread.currentThread().getName());
}
};
//在主線程給handler發送消息
mHandler.sendEmptyMessage(1);
new Thread(new Runnable() {
@Override
public void run() {
//在子線程給handler發送數據
mHandler.sendEmptyMessage( 2 ) ;
}
}).start() ;
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}
7、發送消息有哪些方法?
Handler類 - sendxxx()方法:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what) {
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
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);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler類 - postxxx()方法:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean postAtFrontOfQueue(Runnable r) {
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
return m;
}
Activity類 - runOnUiThread()方法:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
View類 - postxxx()方法:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis);
return true;
}
8、插入消息的流程?
分析上述發送消息的方法,不難發現消息的發送最終都會到enqueueMessage()方法:
// 向消息隊列中插入消息
boolean enqueueMessage(Message msg, long when) {
// 判斷消息接收者handler是否為null
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // 判斷msg是否正在被使用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) { // MessageQueue處于quit狀態
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為使用狀態
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { // 若:消息隊列為空 or 欲在消息頭插入消息 or 欲插入的消息先于消息頭的消息執行,則:插入消息
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { // 循環遍歷消息隊列 將新消息插入合適位置
prev = p;
p = p.next;
if (p == null || when < p.when) { // 若已輪詢到消息隊列尾 or 欲插入的消息先于輪詢到的消息執行 則:跳出循環 -> 插入消息
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
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;
}
9、取出消息->處理消息的流程?
// 消息隊列的循環遍歷
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg); // 關鍵語句 調用handler的dispatchMessage()方法
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // 發送消息 如調用了post(Runnable r)等系列方法(會執行 msg.callback = runnable; 操作),則此處不為null
handleCallback(msg);
} else {
if (mCallback != null) { // 創建Handler對象時作為參數傳遞進來,可以用來實現消息的攔截
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
10、如何實現Handler消息攔截(不接收消息)
private Handler handler = new Handler(new Callback(){
@Override
public boolean handleMessage(Message msg) {
return true; // 設置true攔截消息
}
}){
@Override
public void handleMessage(Message msg) {
// 根據消息類型對消息進行處理
}
};
相信聰明的讀者熟悉問題9中的代碼后自然會一目了然此間原理
11、消息機制中的消息池實現原理,消息池為什么不會引起OOM?
通常,我們使用Message.obtain()
從消息池中獲取Message
,避免直接構造Message
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) { // 消息池鏈表頭部Message不為空 出池
Message m = sPool; // 取出鏈表頭部Message
sPool = m.next; // 鏈表的下一個Message為新的鏈表頭
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--; // 鏈表池大小 -1
return m;
}
}
return new Message(); // 消息池中沒有Message則重新構造
}
那么消息池中的消息哪里來的呢?我們知道,消息池的主要作用是消息的復用,那就只有當一個消息被new出來并使用結束后,才會進入消息池,也就是這個消息被回收到池中,等待復用,我們找找消息使用結束被回收的函數recycle()
:
public void recycle() {
if (isInUse()) { // 消息處于正在處于使用狀態
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// 把這個Message所有成員賦值成最初的狀態
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) { // 入池
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
看到這,相信你已經知道了消息是怎樣加到消息池和怎樣從消息池中取出:消息在使用結束recycle
的時候入池,在下次obtain
消息的時候從消息池中取出。
那么Android會因為Message Pool緩存的Message對象而造成OOM嗎?
對于這個問題,我可以明確的說APP不會因Message Pool而OOM
我們知道消息池中保存的最大消息數為MAX_POOL_SIZE
表示鏈表的最大長度為50,當我們執行入池操作時:
- 將待回收的Message對象字段置空(避免因Message過大,使靜態的消息池內存泄漏)。因此無論原先的Message對象有多大,最終被緩存進Message Pool前都被置空,那么這些緩存的Message對象所占內存大小對于一個app內存來說基本可以忽略。所以說,Message Pool并不會造成OOM。
- 以內置鎖的方式(線程安全),判斷當前線程池的大小是否小于50。若小于50,直接將Mesaage插入到消息池鏈表尾部;若大于等于50,則直接丟棄掉,那么這些被丟棄的Message將交由GC處理。
12、主線程中的Looper.loop()一直無限循環為什么不會造成ANR?
造成ANR的原因一般有兩種:
- 當前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了);
- 當前的事件正在處理,但沒有及時完成。
因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
而且主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環并不會對CPU性能有過多的消耗。
<b>也就說我們的代碼其實就是在這個循環里面去執行的,當然不會阻塞了。</b>
13、Handler內存泄漏的問題
造成內存泄漏的原因:
- Handler的生命周期與Activity不一致;
- Handler引用Activity阻止了GC對Activity的回收。
解決方案:
使用顯式引用:1.靜態內部類。 2. 外部類
使用弱引用:WeakReference
具體代碼:
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference< MainActivity >(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if (mActivity.get() == null) {
return;
}
mActivity.get().todo();
}
}
@Override
public void onDestroy() {
// If null, all callbacks and messages will be removed.
mHandler.removeCallbacksAndMessages(null);
}
未完待續...
參考文集:
Android 消息處理機制(Looper、Handler、MessageQueue,Message)
Android的消息機制之ThreadLocal的工作原理
Android面試:主線程中的Looper.loop()一直無限循環為什么不會造成ANR?