1、數(shù)據(jù)通信會帶來什么開發(fā)中的問題?
(1)線程間如何進行通信
Handler通信實現(xiàn)的方案實際上是內(nèi)存共享方案。
(2)為什么線程間不會干擾
(3)為什么wait/notify的用武之地不大
因為Handler已經(jīng)將需要這部分功能進行了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中獲取消息進行處理。
3、MessageQuenue的創(chuàng)建,MQ是Looper的成員變量,在Looper的構(gòu)造函數(shù)中創(chuàng)建。
4、Handler的創(chuàng)建,在任意線程中創(chuàng)建Handler,都會通過Looper的ThreadLocal獲取一下當前線程的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點擊圖標-->fork一個zygote進程,分配一個JVM,JVM的main函數(shù)在ActivityThread中,在ActivityThread的main方法中,執(zhí)行
Looper.prepareMainLooper,為主線程準備一個Looper,執(zhí)行Loop.loop開始循環(huán)MQ。
Loop.loop中是一個死循環(huán),一直調(diào)用MQ的queue.next查詢讀取消息。
因為loop是個死循環(huán),也就是所有的代碼都會在Loop中執(zhí)行。
線程間通信只是Handler的一個附屬的功能,真實的作用是所有的代碼都在Handler中執(zhí)行。維持著Android APP運行的框架。所以要重視。
那Loop如何停掉,
for(;;){
Message msg = queue.next();
if(msg == null){//什么時候返回一個為空的msg的message,1、應(yīng)用退出,調(diào)用quit()
//沒有消息 ,意味著這messageQueue正在退出。
return;
}
}
handler-->sendMessage,消息起點
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()。
問題,從細節(jié)上來說,Message在動的過程
Message怎么來的,new 或者 obtain;無論Message怎么創(chuàng)建,都是一塊內(nèi)存。
子線程
主線程
因為內(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) {這個是隊列為空??磂lse不為空
{
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {//找到一個p,當p為空或者p的執(zhí)行時間大于當前入隊msg的時間
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next//將msg按照時間順序插入隊列
prev.next = msg;
}
排序算法,插入。
為什么是一個隊列,先進先出。
先進無法保證,因為有時間排序,那么先出吶?
看,loop ,一直都是mq.next 一直取最前面的一個,所以是一個隊列。
Looper源碼分析。
核心在與構(gòu)造函數(shù),一個數(shù)loop函數(shù),ThreadLocal
1、初始化
私有構(gòu)造函數(shù),如果構(gòu)造函數(shù)是共有的,那么Loop就滿天飛了,不能進行控制。
ThreadLocal,多線程,線程上下文的存儲變量。每一個線程都有一個ThreadLocalMap,里面有個如因信用的Entry鍵值對(ThreadLocal k,Object v)
ThreadLocal.set 先獲取當前線程對應(yīng)的map,map.set(this,value);
一個線程只有一個Looper ,并且Looper是不能改的。為什么?
因為一個線程中只有一個ThreadLocalMap >> (this,value) this是唯一的threadLocal,所以value是唯一的,如何保證ThreadLocal/只對應(yīng)一個value
<key,value> set(key1,value1) set(key1,value2);
因為在prepare之前判斷一下
if(sThreadLocal.get() != null){
throw 異常.
}
2、MessageQuenue屬于哪個線程
這個說法是錯誤的,只有執(zhí)行的函數(shù)才能說屬于哪個線程的。變量是可以共享的。
Handler設(shè)計的亮點
面試題:
1、一個線程有幾個Handler?
無數(shù)個,Handler機制只有一個
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、子線程中維護的Looper,消息隊列無消息的時候的處理方案是什么,有什么用?
需要quit
涉及到,睡眠和喚醒。MessageQueue的等待和喚醒機制。
Message的enqueueMessage沒有限制入隊的消息數(shù)量,因為這個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;//標識永久 等待,直到有人過來喚醒。
喚醒 if(needWake){
nativeWake(mPtr)
}

最終是調(diào)用到了Linux層的epoll_wait來實現(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方法都要進行加鎖。
到底: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進行回收,放到sPool中,
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
在池子中插入一個msg,這個是防止oom,和內(nèi)存抖動。避免多次new造成,使得內(nèi)存碎片嚴重,造成內(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 點擊事件 5s:也就是一個點擊Message的處理事件超過5s,然后用handler發(fā)送一個ANR消息,提醒。ANR的優(yōu)先級高。
廣播10s 。
這個是無關(guān)的問題,因為這些點擊事件被封裝為Message,
MSG:為啥block不會導(dǎo)致ANR?
block 線程沒事做了,CPU讓出。
消息機制之同步屏障:架構(gòu)思維
我們都知道,Android系統(tǒng)16ms會刷新一次屏幕,如果主線程的消息過多,在16ms之內(nèi)沒有執(zhí)行完,必然會造成卡頓或者掉幀。那怎么才能不排隊,沒有延時的處理呢?這個時候就需要異步消息,在處理異步消息的時候,我們就需要同步屏障,讓異步消息不用排隊等候處理。可以理解為同步屏障是一堵墻,把同步消息隊列攔住,先處理異步消息,等異步消息處理完了,這堵墻就會取消,然后繼續(xù)處理同步消息Silly_Monkey原文鏈接。
異步消息:立刻執(zhí)行
同步消息/普通消息: 放在MQ中執(zhí)行
消息時根據(jù)執(zhí)行時間進行先后排序,然后消息時保存在隊列中,因而消息只能從隊列的頭取出來,那么問題來了,那需要緊急處理的消息怎么辦?
msg.target = null 做一個標志(這就是一個同步屏障);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();
}
當然,也可以將子線程得Looper保存到子線程外部,用Handler(Looper)來創(chuàng)建Handler,但是把Looper放在外部,外部類持有Looper得引用,無法進行釋放,可能導(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
抽象類,子類繼承并實現(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)用需求:一項任務(wù)分為幾個子任務(wù),等子任務(wù)全部完成后,這項任務(wù)才算完成,
這個需求可以用多線程來處理,一個線程處理完 -->下一個-->下一個。
IntentService就可以幫我們完成這個工作,而且,能夠很好得管理線程,保證只有一個子線程處理工作,而且是一個一個得完成任務(wù),有條不紊。
到底還有別的地方在用嗎?
fragment生命周期管理
如何保證attach先執(zhí)行FragmentPagerAdapter
mCurTransaction.attach(fragment); mCurTransaction.detach((Fragment)object);
Glide生命周期管理
Handler loop休眠為什么不會導(dǎo)致ANR
Messagequeue隊列處理機制,在fragment生命周期管理中的應(yīng)用