摘要:通過向消息池發(fā)送各種消息事件通過處理相應的消息事件。消息泵通過不斷地從中抽取,按分發(fā)機制將消息分發(fā)給目標處理者。也稱之為消息隊列,特點是先進先出,底層實現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)。
6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的?簡單說一下你的看法!
6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handler?
6.0.0.3 可以在子線程直接new一個Handler嗎?會出現(xiàn)什么問題,如何在子線程中使用handler?
6.0.0.4 說一下Handler內(nèi)存泄漏有哪些?造成造成內(nèi)存泄漏原因是什么?如何解決handler造成的內(nèi)存泄漏?
6.0.0.5 Activity如何自動綁定Looper?主線程中的Looper死循環(huán)和binder線程中的死循環(huán)有哪些區(qū)別?
6.0.0.6 為什么系統(tǒng)不建議在子線程訪問UI,不對UI控件的訪問加上鎖機制的原因?
6.0.0.7 Looper.loop是一個死循環(huán),拿不到需要處理的Message就會阻塞,那在UI線程中為什么不會導致ANR?
6.0.0.8 Handler.sendMessageDelayed()怎么實現(xiàn)延遲的?結(jié)合Looper.loop()循環(huán)中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。
6.0.0.9 Message可以如何創(chuàng)建?哪種效果更好,為什么?
6.0.1.0 MessageQueue作用是干什么的?MessageQueue的定義是什么?MessageQueue主要工作原理是怎樣的?
6.0.1.1 子線程更新UI有哪些方式?runOnUiThread如何實現(xiàn)子線程更新UI?View.post(Runnable r)更新UI?
6.0.1.3 使用Hanlder的postDealy()后消息隊列會發(fā)生什么變化?
6.0.1.4 ThreadLocal有什么作用?如何避免UI線程盡量只做跟UI相關(guān)的工作);
6.0.1.5 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handler?
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客,Python學習筆記等等,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:github.com/yangchong21…
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
作用:
跨線程通信。當子線程中進行耗時操作后需要更新UI時,通過Handler將有關(guān)UI的操作切換到主線程中執(zhí)行。
四要素:
Message(消息):需要被傳遞的消息,其中包含了消息ID,消息處理對象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊,最終由Handler處理。技術(shù)博客大總結(jié)
MessageQueue(消息隊列):用來存放Handler發(fā)送過來的消息,內(nèi)部通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表,等待Looper的抽取。
Handler(處理者):負責Message的發(fā)送及處理。通過 Handler.sendMessage() 向消息池發(fā)送各種消息事件;通過 Handler.handleMessage() 處理相應的消息事件。
Looper(消息泵):通過Looper.loop()不斷地從MessageQueue中抽取Message,按分發(fā)機制將消息分發(fā)給目標處理者。
具體流程
Handler.sendMessage()發(fā)送消息時,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;
通過Looper.loop()開啟循環(huán)后,不斷輪詢調(diào)用MessageQueue.next();
調(diào)用目標Handler.dispatchMessage()去傳遞消息,目標Handler收到消息后調(diào)用Handler.handlerMessage()處理消息。
Handler的post方法實現(xiàn)很簡單,如下所示
mHandler.post(new Runnable() {
@Override
public void run() {
}
});
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
view的post方法也很簡單,如下所示
可以發(fā)現(xiàn)其調(diào)用的就是activity中默認保存的handler對象的post方法
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
ViewRootImpl.getRunQueue().post(action);
return true;
}
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
為什么說要避免在子線程中手動創(chuàng)建looper?
下面這種使用方式,是非常危險的一種做法
在子線程中,如果手動為其創(chuàng)建Looper,那么在所有的事情完成以后應該調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待的狀態(tài),而如果退出Looper以后,這個線程就會立刻終止,因此建議不需要的時候終止Looper。(【 Looper.myLooper().quit(); 】)
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
直接在子線程中創(chuàng)建handler,看看會出現(xiàn)什么情況?博客
運行后可以得出在子線程中定義Handler對象出錯,難道Handler對象的定義或者是初始化只能在主線程中?其實不是這樣的,錯誤信息中提示的已經(jīng)很明顯了,在初始化Handler對象之前需要調(diào)用Looper.prepare()方法。
Handler的工作是依賴于Looper的,而Looper(與消息隊列)又是屬于某一個線程(ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定線程中存儲數(shù)據(jù),其他線程則無法獲取到),其他線程不能訪問。因此Handler就是間接跟線程是綁定在一起了。因此要使用Handler必須要保證Handler所創(chuàng)建的線程中有Looper對象并且啟動循環(huán)。因為子線程中默認是沒有Looper的,所以會報錯。
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i(TAG, "在子線程中定義Handler,接收并處理消息");
}
}
};
}
}.start();
}
});
如何正確運行。在這里問一個問題,在子線程中可以吐司嗎?答案是可以的,只不過又條件,詳細可以看這篇文章02.Toast源碼深度分析
這樣程序已經(jīng)不會報錯,那么這說明初始化Handler對象的時候我們是需要調(diào)用Looper.prepare()的,那么主線程中為什么可以直接初始化Handler呢?難道是主線程創(chuàng)建handler對象的時候,會自動調(diào)用Looper.prepare()方法的嗎?博客
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i(TAG, "在子線程中定義Handler,接收并處理消息");
}
}
};
//獲取Looper對象
mLooper = Looper.myLooper();
Looper.loop();
//在適當?shù)臅r候退出Looper的消息循環(huán),防止內(nèi)存泄漏
mLooper.quit();
}
}.start();
}
});
解決Handler內(nèi)存泄露主要2點
有延時消息,要在Activity銷毀的時候移除Messages
匿名內(nèi)部類導致的泄露改為匿名靜態(tài)內(nèi)部類,并且對上下文或者Activity使用弱引用。博客
問題代碼
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text); //模擬內(nèi)存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("yangchong");
}
}, 2000);
}
}
造成內(nèi)存泄漏原因分析
上述代碼通過內(nèi)部類的方式創(chuàng)建mHandler對象,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity,當執(zhí)行postDelayed方法時,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。
如何解決handler造成的內(nèi)存泄漏
第一種解決辦法
要想避免Handler引起內(nèi)存泄漏問題,需要我們在Activity關(guān)閉退出的時候的移除消息隊列中所有消息和所有的Runnable。
上述代碼只需在onDestroy()函數(shù)中調(diào)用mHandler.removeCallbacksAndMessages(null);就行了。
@Override
protected void onDestroy() {
super.onDestroy();
if(handler!=null){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
第二種解決方案
使用弱引用解決handler內(nèi)存泄漏問題,關(guān)于代碼案例,可以參考我的開源項目:github.com/yangchong21…
//自定義handler
public static class HandlerHolder extends Handler {
WeakReference mListenerWeakReference;
/**
* @param listener 收到消息回調(diào)接口
*/
HandlerHolder(OnReceiveMessageListener listener) {
mListenerWeakReference = new WeakReference<>(listener);
}
@Override
public void handleMessage(Message msg) {
if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){
mListenerWeakReference.get().handlerMessage(msg);
}
}
}
//創(chuàng)建handler對象
private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() {
@Override
public void handlerMessage(Message msg) {
switch (msg.what){
case 1:
TextView textView1 = (TextView) msg.obj;
showBottomInAnimation(textView1);
break;
case 2:
TextView textView2 = (TextView) msg.obj;
showBottomOutAnimation(textView2);
break;
}
}
});
//發(fā)送消息
Message message = new Message();
message.what = 1;
message.obj = textView;
handler.sendMessageDelayed(message,time);
即推薦使用靜態(tài)內(nèi)部類 + WeakReference 這種方式。每次使用前注意判空。
主線程如何自動調(diào)用Looper.prepare()。那就是ActivityThread,并且在main方法中我們會看到主線程也是通過Looper方式來維持一個消息循環(huán)。那么這個死循環(huán)會不會導致應用卡死,即使不會的話,它會慢慢的消耗越來越多的資源嗎?
對于線程即是一段可執(zhí)行的代碼,當可執(zhí)行代碼執(zhí)行完成后,線程生命周期便該終止了,線程退出。而對于主線程,我們是絕不希望會被運行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出。
例如,binder線程也是采用死循環(huán)的方法,通過循環(huán)方式不同與Binder驅(qū)動進行讀寫操作,當然并非簡單地死循環(huán),無消息時會休眠。但這里可能又引發(fā)了另一個問題,既然是死循環(huán)又如何去處理其他事務呢?通過創(chuàng)建新線程的方式。真正會卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發(fā)生ANR,looper.loop本身不會導致應用卡死。
可以看到Looper.prepare()方法在這里調(diào)用,所以在主線程中可以直接初始化Handler了。
//ActivityThread類中的main方法中重點代碼
//注意:這里省略了許多代碼
public static void main(String[] args) {
……
//創(chuàng)建Looper和MessageQueue對象,用于處理主線程的消息
Looper.prepareMainLooper();
//創(chuàng)建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創(chuàng)建新線程)
thread.attach(false);
……
//消息循環(huán)運行
Looper.loop();
//如果能執(zhí)行下面方法,說明應用崩潰或者是退出了...
throw new RuntimeException("Main thread loop unexpectedly exited");
}
并且可以看到還調(diào)用了:Looper.loop()方法,可以知道一個Handler的標準寫法其實是這樣的
Looper.prepare();
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 101) {
Log.i(TAG, "在子線程中定義Handler,并接收到消息");
}
}
};
Looper.loop();
思考:Looper.prepare()能否調(diào)用兩次或者多次
如果運行,則會報錯,并提示prepare中的Excetion信息。由此可以得出在每個線程中Looper.prepare()能且只能調(diào)用一次
//這里Looper.prepare()方法調(diào)用了兩次
Looper.prepare();
Looper.prepare();
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i(TAG, "在子線程中定義Handler,并接收到消息。。。");
}
}
};
Looper.loop();
Looper中用什么存儲消息
先看一下下面得源代碼
可以看到Looper中有一個ThreadLocal成員變量,熟悉JDK的同學應該知道,當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
看Looper對象的構(gòu)造方法,可以看到在其構(gòu)造方法中初始化了一個MessageQueue對象。MessageQueue也稱之為消息隊列,特點是先進先出,底層實現(xiàn)是單鏈表數(shù)據(jù)結(jié)構(gòu)。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
出自《Android藝術(shù)探索》
這是因為Android的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導致UI控件處于不可預期的狀態(tài),那么為什么系統(tǒng)不對UI控件的訪問加上鎖機制呢?缺點有兩個:
①首先加上鎖機制會讓UI訪問的邏輯變得復雜
②鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些線程的執(zhí)行。
所以最簡單且高效的方法就是采用單線程模型來處理UI操作。
為什么說子線程不能更新UI?
子線程是不能直接更新UI的。Android實現(xiàn)View更新有兩組方法,分別是invalidate和postInvalidate。前者在UI線程中使用,后者在非UI線程即子線程中使用。換句話說,在子線程調(diào)用 invalidate 方法會導致線程不安全。熟悉View工作原理的人都知道,invalidate 方法會通知 view 立即重繪,刷新界面。作一個假設,現(xiàn)在用 invalidate 在子線程中刷新界面,同時UI線程也在用 invalidate 刷新界面,這樣會不會導致界面的刷新不能同步?這就是invalidate不能在子線程中使用的原因。博客
問題描述
在處理消息的時候使用了Looper.loop()方法,并且在該方法中進入了一個死循環(huán),同時Looper.loop()方法是在主線程中調(diào)用的,那么為什么沒有造成阻塞呢?
ActivityThread中main方法
ActivityThread類的注釋上可以知道這個類管理著我們平常所說的主線程(UI線程)
首先 ActivityThread 并不是一個 Thread,就只是一個 final 類而已。我們常說的主線程就是從這個類的 main 方法開始,main 方法很簡短
public static final void main(String[] args) { ... //創(chuàng)建Looper和MessageQueue Looper.prepareMainLooper(); ... //輪詢器開始輪詢 Looper.loop(); ... }
Looper.loop()方法無限循環(huán)
看看Looper.loop()方法無限循環(huán)部分的代碼
while (true) {
//取出消息隊列的消息,可能會阻塞
Message msg = queue.next(); // might block
...
//解析消息,分發(fā)消息
msg.target.dispatchMessage(msg);
...
}
為什么這個死循環(huán)不會造成ANR異常呢?
因為Android 的是由事件驅(qū)動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。技術(shù)博客大總結(jié)
處理消息handleMessage方法
如下所示
可以看見Activity的生命周期都是依靠主線程的Looper.loop,當收到不同Message時則采用相應措施。
如果某個消息處理時間過長,比如你在onCreate(),onResume()里面處理耗時操作,那么下一次的消息比如用戶的點擊事件不能處理了,整個循環(huán)就會產(chǎn)生卡頓,時間一長就成了ANR。
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...........
}
}
loop的循環(huán)消耗性能嗎?
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環(huán)并不會對CPU性能有過多的消耗。
簡單的來說:ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán),那么你的程序也就可以退出了。
創(chuàng)建Message對象的幾種方式:技術(shù)博客大總結(jié)
Message msg = new Message();
Message msg = Message.obtain();
Message msg = handler1.obtainMessage();
后兩種方法都是從整個Messge池中返回一個新的Message實例,能有效避免重復Message創(chuàng)建對象,因此更鼓勵這種方式創(chuàng)建Message
runOnUiThread如何實現(xiàn)子線程更新UI
看看源碼,如下所示
如果msg.callback為空的話,會直接調(diào)用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會在主線程中。
在runOnUiThread程序首先會判斷當前線程是否是UI線程,如果是就直接運行,如果不是則post,這時其實質(zhì)還是使用的Handler機制來處理線程與UI通訊。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
MessageQueue作用是干什么的
MessageQueue,主要包含2個操作:插入和讀取。
讀取操作會伴隨著刪除操作,插入和讀取對應的方法分別為enqueueMessage和next,其中enqueueMessage的作用是往消息隊列中插入一條消息,而next的作用是從消息隊列中取出一條消息并將其從消息隊列中移除。
雖然MessageQueue叫消息隊列,但是它的內(nèi)部實現(xiàn)并不是用的隊列。
實際上它是通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表,單鏈表在插入和刪除上比較有優(yōu)勢。
MessageQueue的定義是什么
通過源碼我們可以知道,MessageQueue維護了一個消息列表。Messgae并不是直接添加到MessageQueue中,而是通過和Looper相關(guān)聯(lián)的Handler來添加的。在當前線程中可以通過調(diào)用Looper.myQueue()方法來獲取當前線程的MessageQueue。博客
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
* You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue
MessageQueue主要工作原理是怎樣的?
源碼如下所示
在Message的源碼中定義了一個成員屬性target,其類型為Handler。由上面enqueuMessage的源碼,我們可以看到,當Message沒有處理其的Handler或該Message正在被處理的時候,都不能正常進入MessageQueue,這一點也是很容易理解的。當線程處于死亡狀態(tài)的時候,Message會被回收掉,而不再進入該線程對應的MessageQueue中。否則,一切正常,enqueMessage就執(zhí)行單鏈表的插入操作,將Message插入到MessageQueue中。
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) {
// 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 dont 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) {
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;
}
next()方法源碼分析
在 MessageQueue 中消息的讀取其實是通過內(nèi)部的 next() 方法進行的,next() 方法是一個無限循環(huán)的方法。博客
如果消息隊列中沒有消息,則該方法會一直阻塞,
當有新消息來的時候 next() 方法會返回這條消息并將其從單鏈表中刪除。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
子線程更新UI有哪些方式
主線程中定義Handler,子線程通過mHandler發(fā)送消息,主線程Handler的handleMessage更新UI
用Activity對象的runOnUiThread方法
創(chuàng)建Handler,傳入getMainLooper
View.post(Runnable r)
runOnUiThread如何實現(xiàn)子線程更新UI
如何使用代碼如下所示
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_0.setText("滾犢子++++");
}
});
}
}).start();
看看源碼,如下所示
如果msg.callback為空的話,會直接調(diào)用我們的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler對象是在主線程中創(chuàng)建的,所以handler的handlerMessage方法的執(zhí)行也會在主線程中。
在runOnUiThread程序首先會判斷當前線程是否是UI線程,如果是就直接運行,如果不是則post,這時其實質(zhì)還是使用的Handler機制來處理線程與UI通訊。博客
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
View.post(Runnable r)更新UI
代碼如下所示
tv_0.post(new Runnable() {
@Override
public void run() {
tv_0.setText("滾犢子");
}
});
源碼原理如下所示
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;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
View.post(Runnable r)使用注意事項
看源碼的注釋可知:如果view已經(jīng)attached,則調(diào)用ViewRootImpl中的ViewRootHandler,放入主線程Lopper等待執(zhí)行。如果detach,則將其暫存在RunQueue當中,等待其它線程取出執(zhí)行。
View.post(Runnable r)很多時候在子線程調(diào)用,用于進行子線程無法完成的操作,或者在該方法中通過getMeasuredWidth()獲取view的寬高。需要注意的是,在子線程調(diào)用該函數(shù),可能不會被執(zhí)行,原因是該view不是attached狀態(tài)。博客
子線程更新UI總結(jié)概括
handler.post(Runnable r)、 view.post(Runnable r)、activity.runOnUIThread(Runnable r)等方法。跟進去看源碼,發(fā)現(xiàn)其實它們的實現(xiàn)原理都還是一樣,最終都是通過Handler發(fā)送消息來實現(xiàn)的。
post delay的Message并不是先等待一定時間再放入到MessageQueue中,而是直接進入并阻塞當前線程,然后將其delay的時間和隊頭的進行比較,按照觸發(fā)時間進行排序,如果觸發(fā)時間更近則放入隊頭,保證隊頭的時間最小、隊尾的時間最大。此時,如果隊頭的Message正是被delay的,則將當前線程堵塞一段時間,直到等待足夠時間再喚醒執(zhí)行該Message,否則喚醒后直接執(zhí)行。
線程本地存儲的功能
ThreadLocal類可實現(xiàn)線程本地存儲的功能,把共享數(shù)據(jù)的可見范圍限制在同一個線程之內(nèi),無須同步就能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題,這里可理解為ThreadLocal幫助Handler找到本線程的Looper。
技術(shù)博客大總結(jié)
怎么存儲呢?底層數(shù)據(jù)結(jié)構(gòu)是啥?
每個線程的Thread對象中都有一個ThreadLocalMap對象,它存儲了一組以ThreadLocal.threadLocalHashCode為key、以本地線程變量為value的鍵值對,而ThreadLocal對象就是當前線程的ThreadLocalMap的訪問入口,也就包含了一個獨一無二的threadLocalHashCode值,通過這個值就可以在線程鍵值值對中找回對應的本地線程變量。
如何避免UI線程盡量只做跟UI相關(guān)的工作);
耗時的操作(比如數(shù)據(jù)庫操作,I/O,連接網(wǎng)絡或者別的有可能阻塞UI線程的操作)把它放在多帶帶的線程處理盡量用Handler來處理UIthread和別的thread之間的交互.使用Thread或者HandlerThread時,調(diào)用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)設置優(yōu)先級,否則仍然會降低程序響應,因為默認Thread的優(yōu)先級和主線程相同。使用Handler處理工作線程結(jié)果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。
注意:一個Thread只能有一個Looper,可以有多個Handler
Looper有一個MessageQueue,可以處理來自多個Handler的Message;MessageQueue有一組待處理的Message,這些Message可來自不同的Handler;Message中記錄了負責發(fā)送和處理消息的Handler;Handler中有Looper和MessageQueue。
為什么一個線程只有一個Looper?技術(shù)博客大總結(jié)
需使用Looper的prepare方法,Looper.prepare()??梢钥聪略创a,Android中一個線程最多僅僅能有一個Looper,若在已有Looper的線程中調(diào)用Looper.prepare()會拋出RuntimeException(“Only one Looper may be created per thread”)。
所以一個線程只有一個Looper,不知道這樣解釋是否合理!更多可以查看我的博客匯總:github.com/yangchong21…
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
關(guān)于其他內(nèi)容介紹
1.技術(shù)博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
我的個人站點:www.yczbj.org, www.ycbjie.cn
github:github.com/yangchong21…
知乎:www.zhihu.com/people/yczb…
簡書:www.jianshu.com/u/b7b2c6ed9…
csdn:my.csdn.net/m0_37700275
喜馬拉雅聽書:www.ximalaya.com/zhubo/71989…
開源中國:my.oschina.net/zbj1618/blo…
泡在網(wǎng)上的日子:www.jcodecraeer.com/member/cont…
郵箱:yangchong211@163.com
阿里云博客:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
segmentfault頭條:segmentfault.com/u/xiangjian…
掘金:juejin.im/user/593943…
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/7059.html
摘要:通過向消息池發(fā)送各種消息事件通過處理相應的消息事件。子線程往消息隊列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。 目錄介紹 6.0.0.1 談談消息機制Hander作用?有哪些要素?流程是怎樣的? 6.0.0.2 為什么一個線程只有一個Looper、只有一個MessageQueue,可以有多個Handle...
摘要:作為的四大組件之二,其應用場景非常多。作用可以監(jiān)聽或接收應用或系統(tǒng)發(fā)出的廣播消息,并做出響應??梢灾付í毩⒌倪M程四大組件都可以通過此屬性指定自己的獨立進程。對于應用內(nèi)廣播的動態(tài)注冊方式,回調(diào)中的返回值是。 前言 Hi,大家好,又雙見面啦,上一期我們講了如何使用Activity,肯定有不少小伙伴已經(jīng)創(chuàng)建了屬于自己的FirstActivity,那么這一期我們主要為大家介紹第二個重要組件Br...
閱讀 943·2023-04-25 19:43
閱讀 4239·2021-11-30 14:52
閱讀 4024·2021-11-30 14:52
閱讀 4132·2021-11-29 11:00
閱讀 4023·2021-11-29 11:00
閱讀 4154·2021-11-29 11:00
閱讀 3871·2021-11-29 11:00
閱讀 6761·2021-11-29 11:00