1卵史、數(shù)據(jù)通信會帶來什么開發(fā)中的問題菜循?
(1)線程間如何進(jìn)行通信
Handler通信實(shí)現(xiàn)的方案實(shí)際上是內(nèi)存共享方案柄驻。
(2)為什么線程間不會干擾
(3)為什么wait/notify的用武之地不大
因?yàn)镠andler已經(jīng)將需要這部分功能進(jìn)行了Linux層的封裝
1、Looper的創(chuàng)建涮母,如果想在一個線程中使用Handler則第一步為執(zhí)行Looper.prepare 配深,Looper中存在一個靜態(tài)的變量ThreadLocal携添,prepare方法中new一個Looper,并將這個Looper保存到ThreadLocal中篓叶。
2烈掠、Looper的無線循環(huán)啟動,在子線程的run方法中的最后缸托,啟動Looper.loop, 這個是一個無限循環(huán)左敌,一直從MQ中獲取消息進(jìn)行處理。
3嗦董、MessageQuenue的創(chuàng)建母谎,MQ是Looper的成員變量,在Looper的構(gòu)造函數(shù)中創(chuàng)建京革。
4奇唤、Handler的創(chuàng)建,在任意線程中創(chuàng)建Handler匹摇,都會通過Looper的ThreadLocal獲取一下當(dāng)前線程的Looper咬扇,如果沒有獲得Looper則拋出異常,Handler會持有Looper和從Looper中獲取的MQ廊勃、
5懈贺、使用经窖,Handler.sendMessage,最終都會調(diào)用MQ的enqueueMessage將消息入隊
6、在Looper從MQ中拿到消息后梭灿,就會調(diào)用與消息綁定的Handler的handlerMessage方法,也就是msg.handler.handlerMessage處理消息堡妒。
1、主線程的Handler啟動
一個APP的啟動流程是從桌面啟動器Lanuncher點(diǎn)擊圖標(biāo)-->fork一個zygote進(jìn)程搬泥,分配一個JVM,JVM的main函數(shù)在ActivityThread中,在ActivityThread的main方法中伏尼,執(zhí)行
Looper.prepareMainLooper,為主線程準(zhǔn)備一個Looper忿檩,執(zhí)行Loop.loop開始循環(huán)MQ爆阶。
Loop.loop中是一個死循環(huán)燥透,一直調(diào)用MQ的queue.next查詢讀取消息。
因?yàn)閘oop是個死循環(huán)扰她,也就是所有的代碼都會在Loop中執(zhí)行兽掰。
線程間通信只是Handler的一個附屬的功能,真實(shí)的作用是所有的代碼都在Handler中執(zhí)行窖壕。維持著Android APP運(yùn)行的框架瞻讽。所以要重視。
那Loop如何停掉速勇,
for(;;){
Message msg = queue.next();
if(msg == null){//什么時候返回一個為空的msg的message,1养匈、應(yīng)用退出呕乎,調(diào)用quit()
//沒有消息 陨晶,意味著這messageQueue正在退出。
return;
}
}
handler-->sendMessage,消息起點(diǎn)
handlerMessage //消息結(jié)束湿刽。中間發(fā)生了什么,需要看源碼撮躁。
優(yōu)先級隊列
入隊: Handler.sendMessage-> MQ-> enqueueMessage,向MQ中放入Message买雾,在消息隊列中插入一個消息。
出隊: Loop.loop 中MQ.next()方法嗤军,出隊列晃危。那是誰來取吶,是Loop來調(diào)用震叮,
Looper.loop->MQ.next->msg.target.dispatch->handleMessage()鳍鸵。
問題偿乖,從細(xì)節(jié)上來說,Message在動的過程
Message怎么來的媳禁,new 或者 obtain;無論Message怎么創(chuàng)建画切,都是一塊內(nèi)存霍弹。
子線程
主線程
因?yàn)閮?nèi)存不分線程,所以子線程和主線程都可以使用一塊內(nèi)存拧烦。
handle = new Handler(){
handleMessage()
{
}
}
new Thead(new Runable(){
void run(){
Looper.prepare
Loop.loop
handle.sendEmptyMessage(new Message)钝计;//子線程創(chuàng)建消息齐佳。
}
})
MQ的數(shù)據(jù)結(jié)構(gòu)炼吴,由單鏈表形成的優(yōu)先級隊列疫衩。優(yōu)先級隊列是有順序的闷煤。
Message.next-->Message-->next-->Message
先后順序,時間假褪,先后順序近顷。
有sendMessageAtTime方法窒升,有時間順序。
那么是怎么排序的吶域醇?看enqueueMessage
if (p == null || when == 0 || when < p.when) {這個是隊列為空冤寿《搅看else不為空
{
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {//找到一個p狠角,當(dāng)p為空或者p的執(zhí)行時間大于當(dāng)前入隊msg的時間
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next//將msg按照時間順序插入隊列
prev.next = msg;
}
排序算法丰歌,插入。
為什么是一個隊列眼溶,先進(jìn)先出晓勇。
先進(jìn)無法保證,因?yàn)橛袝r間排序绰筛,那么先出吶铝噩?
看,loop 毛甲,一直都是mq.next 一直取最前面的一個具被,所以是一個隊列硬猫。
Looper源碼分析。
核心在與構(gòu)造函數(shù)坑雅,一個數(shù)loop函數(shù)裹粤,ThreadLocal
1蜂林、初始化
私有構(gòu)造函數(shù),如果構(gòu)造函數(shù)是共有的矮锈,那么Loop就滿天飛了苞笨,不能進(jìn)行控制子眶。
ThreadLocal,多線程粤咪,線程上下文的存儲變量寥枝。每一個線程都有一個ThreadLocalMap,里面有個如因信用的Entry鍵值對(ThreadLocal k,Object v)
ThreadLocal.set 先獲取當(dāng)前線程對應(yīng)的map,map.set(this,value);
一個線程只有一個Looper ,并且Looper是不能改的。為什么蝌麸?
因?yàn)橐粋€線程中只有一個ThreadLocalMap >> (this,value) this是唯一的threadLocal,所以value是唯一的艾疟,如何保證ThreadLocal/只對應(yīng)一個value
<key,value> set(key1,value1) set(key1,value2);
因?yàn)樵趐repare之前判斷一下
if(sThreadLocal.get() != null){
throw 異常.
}
2蔽莱、MessageQuenue屬于哪個線程
這個說法是錯誤的盗冷,只有執(zhí)行的函數(shù)才能說屬于哪個線程的。變量是可以共享的柑司。
Handler設(shè)計的亮點(diǎn)
面試題:
1锅劝、一個線程有幾個Handler?
無數(shù)個故爵,Handler機(jī)制只有一個
2、一個線程有幾個Looper?劲室,只有一個结窘,用過threadLocap和prepare 的 threadLocal,get來保證只能為一個線程設(shè)置一個Looper
3隧枫、Handler內(nèi)存泄露的原因?為什么其他的內(nèi)部類沒有說過這個問題?
static :
內(nèi)部類持有外部類的對象确买。
recycleView adpter ViewHolder 也是內(nèi)部類纱皆?
是生命周期的問題。
enqueueMessage{
msg.target = thiss;
}
Message持有了Handler ,Handler持有了Activity 铛楣,message可能會特定的時間后執(zhí)行艺普,那么message就不能被釋放歧譬,也就是Handler不能被釋放,Handler持有的Activity也不能被釋放矢洲,形成了內(nèi)存泄露读虏。
4袁滥、為啥主線程可以直接new Handler呻拌,子線程想要new Handler要干什么?
5靴拱、子線程中維護(hù)的Looper,消息隊列無消息的時候的處理方案是什么猾普,有什么用?
需要quit
涉及到初家,睡眠和喚醒。MessageQueue的等待和喚醒機(jī)制陌知。
Message的enqueueMessage沒有限制入隊的消息數(shù)量仆葡,因?yàn)檫@個MQ是大家公用的志笼。
MQ的輪詢?nèi)∠?如果沒有消息則會阻塞
這里的阻塞有兩個方面阻塞
1、如果取出的消息還沒有到要執(zhí)行的時刻韧掩,那就得阻塞
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);
}計算
循環(huán)一遍后從頭開始窖铡, nativePollOnce(ptr, nextPollTimeoutMillis);//睡眠執(zhí)行等待操作
2万伤、第二層等待
如果消息隊列為空敌买,
nextPollTimeoutMillis = -1;//標(biāo)識永久 等待,直到有人過來喚醒聋庵。
喚醒 if(needWake){
nativeWake(mPtr)
}
最終是調(diào)用到了Linux層的epoll_wait來實(shí)現(xiàn)等待祭玉。
最終也是調(diào)用到了Linux層的脱货。
子線程的消息為空時一定要調(diào)用quit來退出:
quit:喚醒線程-->messageQuenue->null->退出loop
6振峻、既然可以存在多個Handler往MessageQueue中添加數(shù)據(jù)(發(fā)消息時各個Handler可能處于不同的線程择份,那么如何確保線程安全的荣赶?)
enqueueMessage會加synchronized:內(nèi)置鎖?為啥叫內(nèi)置鎖利诺,是由jvm完成剩燥,系統(tǒng)的。
可以鎖代碼塊,對象胜卤。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) //只要是鎖了this赁项,其他的函數(shù)和方法都不能調(diào)用悠菜,都是等待狀態(tài)
一個線程只有一個可以操作MQ的地方,而這個地方又加了鎖摩窃,所以可以確保線程安全猾愿。
enqueueMessage next quit方法都要進(jìn)行加鎖蒂秘。
到底:Message:從子線程->>發(fā)送到主線程
首先淘太,內(nèi)存是沒有線程的
子線程:里面執(zhí)行的函數(shù)蒲牧,這個函數(shù)就在子線程里面
thread: handler.sendMessage(msg) -> MessageQueue.enqueMessage(msg) MessageQueue是一個容器。
主線程的Loop就會去輪詢主線程對應(yīng)的MessageQueue显熏,loop函數(shù)是在主線程調(diào)度喘蟆,所以在MessageQueue的next中會調(diào)用msg.target.dispatchMessage方法鼓鲁,調(diào)用handleMessage處理Message
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>( );整個APP里面唯一骇吭,所有線程公用一個。
Looper線程唯一棘脐。
7蛀缝、Message如何創(chuàng)建
obtain 和 new
注意:在Looper的loop中取出一個msg
msg.target.dispatchMessage(msg);后并沒有直接return 而是 執(zhí)行msg.recycleUnchecked();對msg進(jìn)行回收,放到sPool中屈梁,
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
在池子中插入一個msg,這個是防止oom在讶,和內(nèi)存抖動构哺。避免多次new造成,使得內(nèi)存碎片嚴(yán)重蝗碎,造成內(nèi)存抖動蹦骑,造成OOM臀防,注意new的內(nèi)存是連續(xù)的袱衷,如果內(nèi)存夠致燥,但是不連續(xù),所以要不斷回收辐益,無法回收就OOM了智政。
這種設(shè)計模式就用了享元設(shè)計模式箱蝠,內(nèi)存復(fù)用。
8劫拗、Looper的死循環(huán)為什么不會導(dǎo)致應(yīng)用卡死
ANR 點(diǎn)擊事件 5s:也就是一個點(diǎn)擊Message的處理事件超過5s,然后用handler發(fā)送一個ANR消息杨幼,提醒。ANR的優(yōu)先級高四瘫。
廣播10s 找蜜。
這個是無關(guān)的問題,因?yàn)檫@些點(diǎn)擊事件被封裝為Message弓叛,
MSG:為啥block不會導(dǎo)致ANR?
block 線程沒事做了撰筷,CPU讓出毕籽。
消息機(jī)制之同步屏障:架構(gòu)思維
我們都知道井辆,Android系統(tǒng)16ms會刷新一次屏幕杯缺,如果主線程的消息過多萍肆,在16ms之內(nèi)沒有執(zhí)行完,必然會造成卡頓或者掉幀蜡塌。那怎么才能不排隊勿负,沒有延時的處理呢馏艾?這個時候就需要異步消息劳曹,在處理異步消息的時候,我們就需要同步屏障琅摩,讓異步消息不用排隊等候處理铁孵。可以理解為同步屏障是一堵墻房资,把同步消息隊列攔住蜕劝,先處理異步消息,等異步消息處理完了岖沛,這堵墻就會取消,然后繼續(xù)處理同步消息Silly_Monkey原文鏈接搭独。
異步消息:立刻執(zhí)行
同步消息/普通消息: 放在MQ中執(zhí)行
消息時根據(jù)執(zhí)行時間進(jìn)行先后排序婴削,然后消息時保存在隊列中,因而消息只能從隊列的頭取出來牙肝,那么問題來了唉俗,那需要緊急處理的消息怎么辦?
msg.target = null 做一個標(biāo)志(這就是一個同步屏障)配椭;msg1 -> msg2 -> msg3-> msg4 ->
20: 第20個消息非常重要虫溜,必須馬上執(zhí)行。 如何去做股缸?如何確保立即執(zhí)行衡楞。
msg.target = null -> msg1 -> msg2 -> msg3-> msg4 ->
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());找到一個異步消息,退出處理同步消息乓序。
}
什么時候用到:就是在更新UI寺酪。
ViewRootImpl;
shceduleTraversalsf方法,調(diào)用MessageQueue得postSyncBarrier方法發(fā)送同步屏障和removeSyncBarrier方法移除同步屏障替劈。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//發(fā)送同步屏障
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//發(fā)送異步消息
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//UI更新消息是異步得
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
什么時候刪除同步屏障
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
HandlerThread存在得意義
1寄雀、方便使用,方便初始化
2陨献、保證了線程得安全盒犹,解決有可能得異步問題。
如果不用眨业,HandlerThread急膀,那么我們要在使用Handler在子線程中處理消息,那么就需要在子線程得run方法創(chuàng)建Handler龄捡。
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
當(dāng)然卓嫂,也可以將子線程得Looper保存到子線程外部,用Handler(Looper)來創(chuàng)建Handler聘殖,但是把Looper放在外部,外部類持有Looper得引用晨雳,無法進(jìn)行釋放行瑞,可能導(dǎo)致內(nèi)存問題。
public void run() {
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
HandlerThread就是一個線程餐禁,不過在run方法中完了Looper得工作血久。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//此時喚醒其他等待得鎖,但并不釋放鎖帮非,一定等到整個synchronized代碼塊執(zhí)行完了才釋放鎖氧吐。
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
處理完任務(wù)>>service自動停止,內(nèi)存釋放末盔。
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//發(fā)現(xiàn)Looper沒有創(chuàng)建完成有人想要獲取looper筑舅,等待,釋放鎖陨舱,wait();synchronized什么關(guān)系
//notifyall 釋放鎖嗎豁翎?
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
IntentService
抽象類,子類繼承并實(shí)現(xiàn)onHandleIntent來處理耗時任務(wù)隅忿。
Handler得應(yīng)用
Service 處理后臺任務(wù)
一般new Thread處理任務(wù),而在IntenService中邦尊,onCreate方法里面創(chuàng)建了HandlerThread和一個ServiceHandler,在onStart方法中
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);執(zhí)行任務(wù)
stopSelf(msg.arg1);自殺背桐,關(guān)閉Service
}
}
應(yīng)用需求:一項(xiàng)任務(wù)分為幾個子任務(wù),等子任務(wù)全部完成后蝉揍,這項(xiàng)任務(wù)才算完成链峭,
這個需求可以用多線程來處理,一個線程處理完 -->下一個-->下一個又沾。
IntentService就可以幫我們完成這個工作弊仪,而且,能夠很好得管理線程杖刷,保證只有一個子線程處理工作励饵,而且是一個一個得完成任務(wù),有條不紊滑燃。
到底還有別的地方在用嗎役听?
fragment生命周期管理
如何保證attach先執(zhí)行FragmentPagerAdapter
mCurTransaction.attach(fragment); mCurTransaction.detach((Fragment)object);
Glide生命周期管理
Handler loop休眠為什么不會導(dǎo)致ANR
Messagequeue隊列處理機(jī)制,在fragment生命周期管理中的應(yīng)用