扣丁學(xué)堂解析Android 異步消息分發(fā)機(jī)制
2018-09-04 10:36:53
1434瀏覽
隨著互聯(lián)網(wǎng)IT行業(yè)的發(fā)展,越來越多的人選擇入坑互聯(lián)網(wǎng),程序員是一個很好的選擇,但是很多沒有基礎(chǔ)的同學(xué)都會選擇先去培訓(xùn)機(jī)構(gòu)培訓(xùn),今天小編先來給大家講一下Android異步消息分發(fā)機(jī)制。
Android的消息機(jī)制
–主要是指Handler的運行機(jī)制和MessageQueue、looper的工作過程。
MessageQueue
MessageQueue消息隊列,用來存儲消息,雖然稱為消息隊列,但是它的存儲結(jié)構(gòu)是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲消息隊列的。
包含兩個操作插入(enqueueMessage)和讀?。╪ext)
enqueueMessage:往消息鏈表中插入一條信息
next:從消息列表中讀取一天消息,并將其從消息隊列中移除,next方法是一個無限循環(huán)的方法,如果消息列表里沒有消息,那么會一直阻塞在這里。
Looper
Looper消息循環(huán),用來處理消息。
Looper會不停的從MessageQueue中查看是否有新的消息,如果有新消息就會立即處理
Looper.java
privateLooper(booleanquitAllowed){
mQueue=newMessageQueue(quitAllowed);
mThread=Thread.currentThread();
}
在Looper的構(gòu)造方法會創(chuàng)建一個MessageQueue,消息隊列,然后將當(dāng)前線程的對象保存起來。
publicstatic@NullableLoopermyLooper(){
returnsThreadLocal.get();
}
privatestaticvoidprepare(booleanquitAllowed){
if(sThreadLocal.get()!=null){
thrownewRuntimeException("OnlyoneLoopermaybecreatedperthread");
}
sThreadLocal.set(newLooper(quitAllowed));
}
問題為什么Looper.prepare()不能被調(diào)用兩次?
答:因為每次調(diào)用Looper.prepare()的時候的都會判斷ThreadLocal是否已經(jīng)存儲當(dāng)前的Looper對象,如果已經(jīng)存在就會拋出異常。
/**
*Runthemessagequeueinthisthread.Besuretocall
*{@link#quit()}toendtheloop.
*/
publicstaticvoidloop(){
finalLooperme=myLooper();
if(me==null){
thrownewRuntimeException("NoLooper;Looper.prepare()wasn'tcalledonthisthread.");
}
finalMessageQueuequeue=me.mQueue;
//Makesuretheidentityofthisthreadisthatofthelocalprocess,
//andkeeptrackofwhatthatidentitytokenactuallyis.
Binder.clearCallingIdentity();
finallongident=Binder.clearCallingIdentity();
for(;;){
Messagemsg=queue.next();//mightblock
if(msg==null){
//Nomessageindicatesthatthemessagequeueisquitting.
return;
}
//Thismustbeinalocalvariable,incaseaUIeventsetsthelogger
finalPrinterlogging=me.mLogging;
if(logging!=null){
logging.println(">>>>>Dispatchingto"+msg.target+""+
msg.callback+":"+msg.what);
}
finallongtraceTag=me.mTraceTag;
if(traceTag!=0&&Trace.isTagEnabled(traceTag)){
Trace.traceBegin(traceTag,msg.target.getTraceName(msg));
}
try{
msg.target.dispatchMessage(msg);
}finally{
if(traceTag!=0){
Trace.traceEnd(traceTag);
}
}
if(logging!=null){
logging.println("<<<<<Finishedto"+msg.target+""+msg.callback);
}
//Makesurethatduringthecourseofdispatchingthe
//identityofthethreadwasn'tcorrupted.
finallongnewIdent=Binder.clearCallingIdentity();
if(ident!=newIdent){
Log.wtf(TAG,"Threadidentitychangedfrom0x"
+Long.toHexString(ident)+"to0x"
+Long.toHexString(newIdent)+"whiledispatchingto"
+msg.target.getClass().getName()+""
+msg.callback+"what="+msg.what);
}
msg.recycleUnchecked();
}
}
loop方法就是一個死循環(huán),跳出死循環(huán)的方法,是MessageQueue的Next方法為空,即queue.next()為空。Looper調(diào)用quit方法或者quitSafely方法,通知消息隊列退出,會使MessageQueue的next方法返回為空。
loop調(diào)用MessageQueue的next方法獲取新的消息,如果沒有消息,next方法會一直阻塞在那里,導(dǎo)致loop方法也會一直阻塞在那里。如果next方法返回了新的消息,Looper就會處理新的消息,調(diào)用msg.target.dispatchMessage(msg),msg.target就是發(fā)送這條消息的Handler對象(Handler中有講解)。這樣就把代碼邏輯切換到指定線程去執(zhí)行了。
3.ThreadLocal
線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程存儲數(shù)據(jù),而且存儲后,只能在指定線程獲取存儲的數(shù)據(jù),其他線程無法獲取。
應(yīng)用:當(dāng)某些數(shù)據(jù)是以線程為作用域,并且不同線程具有不同數(shù)據(jù)
存在Looper中,并不是線程,作用是在每個線程中存儲數(shù)據(jù)
問題:Handler內(nèi)部是如何獲取當(dāng)前線程的Looper的?
Handler.java
publicHandler(Callbackcallback,booleanasync){
if(FIND_POTENTIAL_LEAKS){
finalClass<?extendsHandler>klass=getClass();
if((klass.isAnonymousClass()||klass.isMemberClass()||klass.isLocalClass())&&
(klass.getModifiers()&Modifier.STATIC)==0){
Log.w(TAG,"ThefollowingHandlerclassshouldbestaticorleaksmightoccur:"+
klass.getCanonicalName());
}
}
mLooper=Looper.myLooper();
if(mLooper==null){
thrownewRuntimeException(
"Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()");
}
mQueue=mLooper.mQueue;
mCallback=callback;
mAsynchronous=async;
}
答:Handler通過ThreadLocal獲取每個線程的Looper.ThreadLocal可以在不同線程中互不干擾的存儲并提供數(shù)據(jù)。
在調(diào)用Looper.prepare()的時候“sThreadLocal.set(newLooper(quitAllowed));”-創(chuàng)建了一個Looper實例并將一個Looper的實例放入了ThreadLocal存儲。然后在Handler中調(diào)用“mLooper=Looper.myLooper();”-從sThreadLocal.get()獲取到Looper對象。
問題:主線程中為什么可以默認(rèn)使用Handler?
答:線程默認(rèn)是沒有Looper的,如果要使用Handler,就必須為線程創(chuàng)建Looper.但是主線程,也就是UI線程,它就是ActivityThread,ActivityThread被創(chuàng)建的時候默認(rèn)會初始化Looper,當(dāng)前UI線程調(diào)用了Looper.prepare()和Looper.loop()方法.
4.Handler
主要工作:發(fā)送和接收消息;
publicfinalbooleansendMessage(Messagemsg)
{
returnsendMessageDelayed(msg,0);
}
publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis)
{
if(delayMillis<0){
delayMillis=0;
}
returnsendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);
}
publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){
MessageQueuequeue=mQueue;
if(queue==null){
RuntimeExceptione=newRuntimeException(
this+"sendMessageAtTime()calledwithnomQueue");
Log.w("Looper",e.getMessage(),e);
returnfalse;
}
returnenqueueMessage(queue,msg,uptimeMillis);
}
privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){
msg.target=this;
if(mAsynchronous){
msg.setAsynchronous(true);
}
returnqueue.enqueueMessage(msg,uptimeMillis);
}
由上面可以看出Handler發(fā)送消息的過程就是向消息鏈表中插入了一條消息,MessageQueue的next方法會返回這條消息給Looper,Looper收到消息,通過msg.target.dispatchMessage(msg)交給Handler處理。
上面enqueueMessage方法首先“msg.target=this;”會把this賦值給msg.target,也就是說把Handler賦值給msg的target屬性。
publicvoiddispatchMessage(Messagemsg){
if(msg.callback!=null){
handleCallback(msg);
}else{
if(mCallback!=null){
if(mCallback.handleMessage(msg)){
return;
}
}
handleMessage(msg);
}
}
首先會檢查Message的callback是否為null,不為空就處理通過handleCallback來處理消息。
callback是一個Runnable接口,就是Handler的post方法傳遞的Runnable參數(shù)
privatestaticvoidhandleCallback(Messagemessage){
message.callback.run();
}
舉個栗子第一種handler的使用方式,post方法
HandlermHandler=newhandler();
mHandler.post(newRunnable()
{
@Override
publicvoidrun()
{
Log.e("TAG",Thread.currentThread().getName());
mTxt.setText("yoxi");
}
});
其次查看mCallback是否為空,mCallback是個接口
publicinterfaceCallback{
publicbooleanhandleMessage(Messagemsg);
}
Callback提供另一種使用Handler的方式,不想派生Handler的子類,可以通過Callback來實現(xiàn)。
舉個栗子第二種使用Handler的方式
privateHandlermHandler=newHandler()
{
publicvoidhandleMessage(android.os.Messagemsg)
{
switch(msg.what)
{
casevalue:
break;
default:
break;
}
};
};
最后,調(diào)用Handler的handleMessage方法來處理消息。
以上就是扣丁學(xué)堂Android在線學(xué)習(xí)小編給大家分享的Android模擬器相關(guān)的內(nèi)容,希望對小伙伴們有所幫助,想要了解更多內(nèi)容的小伙伴可以登錄扣丁學(xué)堂官網(wǎng)咨詢。想要學(xué)好Android開發(fā)小編給大家推薦口碑良好的扣丁學(xué)堂,扣丁學(xué)堂有專業(yè)老師制定的Android學(xué)習(xí)路線圖輔助學(xué)員學(xué)習(xí),此外還有與時俱進(jìn)的Android課程體系和大量的Android視頻教程供學(xué)員觀看學(xué)習(xí),想要學(xué)好Android開發(fā)技術(shù)的小伙伴快快行動吧。
【關(guān)注微信公眾號獲取更多學(xué)習(xí)資料】
查看更多關(guān)于“Android開發(fā)技術(shù)”的相關(guān)資訊>>
標(biāo)簽:
Android培訓(xùn)