一衙伶、什么是進(jìn)程蕴潦、什么是線程,有和區(qū)別汪榔?
進(jìn)程和線程的基本定義是:進(jìn)程是分配資源的基本單位蒲拉,線程是獨(dú)立運(yùn)行和獨(dú)立調(diào)度的基本單位;
通俗的來講一個應(yīng)用程序一般就是一個進(jìn)程(進(jìn)程名:默認(rèn)就是包名)痴腌,我們訪問數(shù)據(jù)是以進(jìn)程為單位的(一般情況下進(jìn)程之間是不允許直接訪問到對方的數(shù)據(jù)雌团,除非使用跨進(jìn)程間通信)
而線程是cpu執(zhí)行的基本,一個進(jìn)程可以包含一個或多個線程
二士聪、為什么主線程不能執(zhí)行耗時操作锦援?
我們在打開一個應(yīng)用程序時,AMS的startProcessLocked()方法中啟動進(jìn)程時剥悟,會為進(jìn)程創(chuàng)建好主線程灵寺,也就是下圖:
傳遞的"android.app.ActivityThread"即為我們通俗意義上的“主線程”
進(jìn)程啟動的時候,會通過創(chuàng)建ActivityThread并調(diào)用其main(String[] args)方法開啟主線程区岗,因此進(jìn)程和主線程是一 一對應(yīng)的略板;
大家都知道主線程是用來處理UI渲染繪制的,同時主線程也負(fù)責(zé)處理AMS對四大組件的調(diào)度分發(fā)處理慈缔,最終完成執(zhí)行叮称;
如果在主線程中執(zhí)行耗時操作,線程里的loop循環(huán)會阻塞藐鹤,導(dǎo)致事件停止分發(fā)瓤檐,最直觀的就是界面卡住了,阻塞時間超過5秒直接ANR了娱节;
三挠蛉、為什么子線程無法更新UI界面?
由于Android的UI控件不是“線程安全”的括堤,如果多線程并發(fā)訪問碌秸,可能導(dǎo)致UI控件出現(xiàn)未知問題
(線程安全是指绍移,多線程編程中線程安全的代碼會通過同步機(jī)制悄窃,保證各個線程都能正確執(zhí)行)
那么為什么UI控件不加同步機(jī)制,讓它本省線程安全呢蹂窖?
加鎖會使UI訪問邏輯變得很復(fù)雜轧抗,加鎖會降低UI的訪問效率,因?yàn)榧渔i會阻塞某些線程的執(zhí)行
四瞬测、簡述Handler機(jī)制横媚?Handler機(jī)制的4個要素纠炮?
Handler是跨線程通信機(jī)制,因?yàn)橹骶€程不能執(zhí)行耗時操作灯蝴,需要在子線程中執(zhí)行耗時操作恢口,而子線程中不能直接對UI進(jìn)行操作;所以當(dāng)有耗時操作后的UI界面更新時穷躁,需要使用線程和Handler跨線程通信機(jī)制更新主線程UI界面耕肩;
四個要素:
Message(消息):需要被傳遞的消息
MessageQueue(消息隊(duì)列):負(fù)責(zé)存儲管理消息,單鏈表維護(hù)问潭,插入和刪除有優(yōu)勢猿诸;
Handler(消息處理器):負(fù)責(zé)發(fā)送、處理消息
Looper(消息池):負(fù)責(zé)關(guān)聯(lián)線程和消息的分發(fā)狡忙,Looper創(chuàng)建時會創(chuàng)建一個 MessageQueue梳虽;
Looper.loop()后創(chuàng)建循環(huán)持續(xù)從MessageQueue那新的消息傳遞給Handler處理;
五灾茁、 Handler機(jī)制的流程窜觉?
1、應(yīng)用程序創(chuàng)建進(jìn)程時北专,主線程ActivityThread也被創(chuàng)建
在主線程的main()函數(shù)中創(chuàng)建mainLooper并執(zhí)行Looper.loop();
2竖螃、四大組件里的Handler創(chuàng)建時
會獲取主線程的Looper并拿到Looper的MessageQueue
3、Handler通過sendMessage發(fā)送消息逗余,最終調(diào)用到queue.enqueueMessage
在消息隊(duì)列中添加該消息
------------------------enqueueMessage()源碼及分析如下:------------------------
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {//消息是否占用
throw new AndroidRuntimeException(msg + " This message is already in use.");
} else if (msg.target == null) {//msg.target即Handler特咆,不能為空
throw new AndroidRuntimeException("Message must have a target.");
} else {
synchronized(this) {
if (this.mQuitting) {//消息隊(duì)列的持有者Looper已經(jīng)quit了
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else {
msg.when = when;//延遲時間賦值給msg
Message p = this.mMessages;//消息隊(duì)列
boolean needWake;
if (p != null && when != 0L && when >= p.when) {
//消息隊(duì)列不為空,延時不為零录粱,延時大于隊(duì)列第一個消息的延時時長
needWake = this.mBlocked && p.target == null && msg.isAsynchronous();
//while循環(huán)依次比較延時時長腻格,時長越長,插入的位置越靠后
while(true) {
Message prev = p;
p = p.next;
if (p == null || when < p.when) {
msg.next = p;
prev.next = msg;
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
} else {
//反之就是:消息隊(duì)列為空 或 延時為零 或延時時長小于隊(duì)列第一個消息的延時時長
msg.next = p;
this.mMessages = msg;
needWake = this.mBlocked;
}
if (needWake) {
nativeWake(this.mPtr);
}
return true;
}
}
}
}
4啥繁、主線程執(zhí)行了Looper.loop()菜职,開始消息循環(huán)不斷輪詢調(diào)用MessageQueue.next()
取得隊(duì)列中下一個消息Message,并調(diào)用Handler的dispatchMessage(msg)傳遞給Handler
MessageQueue.next()函數(shù)從消息隊(duì)列取出消息源碼分析如下:
Message next() {
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
while(true) {
while(true) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(this.mPtr, nextPollTimeoutMillis);
synchronized(this) {
long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = this.mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while(msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now >= msg.when) {
this.mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
this.mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
nextPollTimeoutMillis = (int)Math.min(msg.when - now, 2147483647L);
} else {
nextPollTimeoutMillis = -1;
}
if (this.mQuitting) {//Looper.quit(),也就是退出程序了
this.dispose();
return null;
}
………………此處省略………………
this.mBlocked = true;//msg為空旗闽,且沒有退出程序酬核,Blocked在next()循環(huán)中
}
}
………此處省略………
}
5、Handler最終調(diào)用重寫的handleMessage處理消息
六适室、Looper.loop()是死循環(huán)嫡意,為什么不會造成應(yīng)用卡死?
這是一個很有意思的問題捣辆,經(jīng)過Handler消息機(jī)制的深入學(xué)習(xí)蔬螟,我們已經(jīng)知道:
1、一個程序能正常運(yùn)行汽畴、組件能正常調(diào)度旧巾、界面能正常刷新耸序,后面是消息機(jī)制不停在起作用;
2鲁猩、Looper.loop()循環(huán)和MessageQueue.next()循環(huán)坎怪,就是為了不斷把消息拿出,讓消息機(jī)制不會停止廓握;
3芋忿、當(dāng)消息隊(duì)列為空時,只是界面和組件暫時不需要更新疾棵,從MessageQueue.next()代碼可以看到戈钢,消息隊(duì)列為空循環(huán)不會停止,只有循環(huán)不停止等到有新的消息入列時才會及時取出來是尔;
4殉了、也就是說,恰恰是Looper.loop()的死循環(huán)拟枚,才能保證消息的及時發(fā)出薪铜,才能保持消息機(jī)制的正常運(yùn)行;
通過上面的描述我們其實(shí)已經(jīng)知道了恩溅,即應(yīng)用卡死(即ANR)和主線程阻塞是沒什么關(guān)系的隔箍;
ANR全稱Application Not Responding,即應(yīng)用程序無響應(yīng)脚乡,指的是界面阻塞蜒滩,即消息隊(duì)列阻塞;
七奶稠、那么在主線程里做耗時操作俯艰,會造成ANR呢?
ANR锌订,Application Not Responding即應(yīng)用程序無響應(yīng)竹握,是界面阻塞
1、應(yīng)用程序的界面更新辆飘,是由主線程的消息處理機(jī)制完成的
2啦辐、主線程的消息處理機(jī)制,是由Looper.loop()和MessageQueue.next()的死循環(huán)不斷取出消息蜈项,來維持消息機(jī)制不斷更新的
3芹关、主線程中新增耗時操作,就會把Looper.loop()的循環(huán)暫停住战得,等新增的耗時操作完成后Looper.loop()的循環(huán)才會繼續(xù)走下去
4充边、loop循環(huán)停止一段時間庸推,相當(dāng)于消息機(jī)制就停止了一段時間常侦,這段時間內(nèi)無法響應(yīng)用戶操作浇冰,界面UI無法更新
5、當(dāng)這個時間超過5s聋亡,即引發(fā)ANR
八肘习、一個線程能否有多個Looper,能否有多個Handler坡倔,Handler和Looper之間關(guān)系漂佩?
1、一個線程只能有一個Looper罪塔,并且只有一個MessageQueue被Looper持有
2投蝉、一個線程可以有多個Handler,Handler發(fā)送的Message會被標(biāo)記上Target
Handler和Looper是多對一的關(guān)系征堪,各個Handler發(fā)送的Message會被標(biāo)記上Target瘩缆,根據(jù)延時長短被先后放入到一個MessageQueue中,在Looper.loop()時依次取出msg佃蚜,根據(jù)msg的target發(fā)送回給指定的Handler處理
九庸娱、在子線程直接new Handler可以么?需要怎么做谐算?
不可以熟尉,看源碼可以知道
直接new Handler,會報(bào)RuntimeException
"Can't create handler inside thread that has not called Looper.prepare()"
就是洲脂,無法在沒有 Looper.prepare()之前就在線程中創(chuàng)建Handler
public class Handler {
public Handler() {
this((Handler.Callback)null, false);
}
public Handler(Handler.Callback callback, boolean async) {
this.mLooper = Looper.myLooper();
if (this.mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
} else {
this.mQueue = this.mLooper.mQueue;
this.mCallback = callback;
this.mAsynchronous = async;
}
}
…………
}
在四大組件中可以直接new Handler是因?yàn)榻锒骶€程ActivityThread的main()函數(shù)中會自動創(chuàng)建Looper對象無需我們自己管理;
在子線程中恐锦,正確的方法是:
先Looper.prepare()
然后new Handler()
最后Looper.loop()讓消息隊(duì)列運(yùn)行起來
不過我們不建議子線程中再加Handler
因?yàn)長ooper.loop()直接子線程就阻塞了雇毫,無法再做其他的耗時操作
十、Message要如何創(chuàng)建踩蔚?哪種創(chuàng)建方法最好棚放?
有三種方式:
第一種是新建了實(shí)例
后兩種方式比較好,直接復(fù)用了消息池中已有的Message實(shí)例
Message msg1 = new Message();
Message msg2 = Message.obtain();
Message msg3 = handler.obtainMessage();
十一馅闽、Handler的PostDelay()方法使用后飘蚯,消息隊(duì)列是如何處理的?
查看源碼:
postDelayed()直接調(diào)用的sendMessageDelayed()
并通過getPostMessage(Runnable r)獲取Message實(shí)例
最后還是調(diào)用到MessageQueue.enqueueMessage()函數(shù)將消息添加到消息鏈表
public final boolean postDelayed(Runnable r, long delayMillis) {
return this.sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0L) {
delayMillis = 0L;
}
return this.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = this.mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
} else {
return this.enqueueMessage(queue, msg, uptimeMillis);
}
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (this.mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
十二福也、Handler使用時應(yīng)注意哪些問題局骤?有什么解決方法?
Handler在使用時很容易遇到內(nèi)存泄漏問題
在發(fā)送消息是無論調(diào)用的是handler.sendMessage或handler.sendMessageDelayed最終都會調(diào)用到
enqueueMessage()將msg添加到MessageQueue中暴凑,如下圖源碼:
發(fā)送出去的msg都會持有handler實(shí)例
(這是因?yàn)橄?zhí)行時峦甩,也會根據(jù)msg持有的handler實(shí)例將msg發(fā)回給對應(yīng)的Handler執(zhí)行)
package android.os;
import android.os.IMessenger.Stub;
import android.util.Log;
import android.util.Printer;
public class Handler {
…………
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//msg將會持有Handler實(shí)例
if (this.mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
}
所以Handler在使用時遇到內(nèi)存泄漏問題的原因是:
handler發(fā)送的延時消息會持有handler實(shí)例,而handler我們很多時候是使用的匿名內(nèi)部類,
java的特性匿名內(nèi)部類會隱式持有外部類對象的引用
也就是說凯傲,如果在延時過程中Activity等組件退出了犬辰,msg持有handler,handler會持有activity冰单,
這就造成了activity的內(nèi)存無法回首幌缝,造成activity內(nèi)存泄漏
解決辦法:
Handler聲明時使用靜態(tài)內(nèi)部類,持有外部類的若應(yīng)用