摘自BAT面試寶典視頻
問題引入:點擊后更新TextView
重點:
1 惋鸥、不能在子線程更新UI
2、OOM:HAndler使用不當(dāng)可能引起內(nèi)存泄漏
3悍缠、Message的優(yōu)化:要用Handler卦绣。obtainMessage()而不是new,會消耗內(nèi)存飞蚓。
4滤港、在子線程創(chuàng)建Handler,要準(zhǔn)備Looper:Looper.prepare()趴拧。
5蜗搔、空指針異常:Handler消息處理完了單頁面銷毀了劲藐,就會拋出異常。
Handler整體架構(gòu) (4個關(guān)鍵類基本關(guān)系幕后類Thread)
handler能做什么樟凄?
1聘芜、處理延時任務(wù):推送將來的Message或Runnable到消息隊列;
2缝龄、線程間通信:在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列汰现;
源碼分析(線程如何跨越、生產(chǎn)者消費者設(shè)計模式叔壤、ThreadLocal原理)
從“handler.sendMessage()”發(fā)送消息出發(fā)
sendMessage(msg)
---->sendMessageDelay(msg,0)
---->sendMessageAtTime(msg,SysMillis()+delay)
---->enqueueMessage(queue,msg,uptimeMillis);
---->queue.enqueueMessage(msg,uptimeMillis);
enqueueMessage()往MessageQueue發(fā)送消息
其他流程圖
所有的send和post都是MessageQueue.enqueueMessage()瞎饲!
MessageQueue.java
用一個for循環(huán)不斷地.next找消息
是一個鏈表隊列,新消息來時比較時間后插入相應(yīng)位置
消息的處理
Handler mHandler = new Handler(){
public void handlerMessage(Message msg){
super.handlerMessage(msg);
}
}
如何handlerMessage炼绘?從MessageQueue里找next()方法
又有一個for循環(huán)
messageQueue.next()返還嗅战、銷毀隊列里的消息
.next()是誰調(diào)用的?--->Looper.java
for循環(huán)一直在運行
Loop在被誰調(diào)用俺亮?---》ActivityThread.java
*如上圖主線程里不需要準(zhǔn)備Loop
ActivityThread讓Loop跑起來的篡腌。
Handler框架手寫
定義4個工具類
Handler
public class Handler{
final Looper mLooper;
final MessageQueue mQueue;
public Handler(){
mLooper = Looper.myLooper();//1
mQueue = mLooper.mQueue;//1為什么不是new
}
//發(fā)送消息
public void sendMessage(Message msg){
enqueueMessage(msg);
}
public void enqueueMessage(Message msg){
msg.target = this;//人跟著箱子去了傳送帶
mQueue.enqueueMessage(msg);
}
//分發(fā)消息
public void dispachMessage(Message msg){
handlerMessage(msg);
}
//處理消息
public void handlerMessage(Message
msg){
}
}
public class Looper{
public MessageQueue mQueue;
public static ThreadLoca<Looper> sThreadLocal= new ThreadLoca<>();
private Looper(){
mQueue = new MessageQueue();
}
public static prepare(){
//看下面ThreadLocal解釋
if(sThreadLocal.get()!=null){
throw new RuntimeException("Only one Looper may ...")
}
sThreadLocal.set(new Looper(.));
}
public static Looper myLooper(){
return ThreadLocal.get();
}
//啟動looper 讓MQ run
public void loop(){
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for(;;){
Message msg = queue .next();
if(msg!=null){
msg.target.dispachMessage(msg);
}
}
}
}
public class MessageQueue{
BlockingQueue <Message> queue ;//實現(xiàn)倉庫的阻塞隊列
private static final int MAXCOUNT = 10;//倉庫大小
public MessageQueue(){
queue= new ArrayBlockingQueue<>(MAXCOUNT );
}
//往隊列里添加消息
public void enqueueMessage(Message msg){
try{
queue.put(msg)
}catch(){
}
}
//往隊列里取消息
public Message next(){
Message msg = null;
try{
msg =queue.take();
}catch(InterruptException e){
}
return msg;
}
}
public class Message{
Handler target;
Object obj;
public Message(){
}
public String toString(){
return obj.toString();
}
}
測試代碼
public calss HandlerMain{
public static void main(String [] args){
Looper.prepare();
Handler handler = new Handler(){
public void handleMessage(Message msg){
System.out.println("Thread ID "+Thread.currentThread().getName()+" received msg: "+msg.toSting())
};
};
new Thread(new Runnable(){
public void run(){
while(true){
Message msg = new Message();
msg.obj = UUID.randomUUID().toString();
System.out.println(Thread.currentThread().);
handler.sendMessage(msg);
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
})
Looper.loop();
}
}
一個線程只有一個Looper弟蚀!
*ThreadLocal線程隔離工具類
類似HashMap<Key,Value>
Key---線程ID
Value---Looper對象
通過ThreadLocal確保Looper唯一悍及,通過Looper確保MessageQueue唯一
子線程和主線程的通信(MQ由子線程寫入在主線程讀雀钜浴)是借助內(nèi)存實現(xiàn)的
Message.obtion()運用了享原設(shè)計模式 ,復(fù)用了Message本讥。
子線程中真的不能更新UI嗎珊泳?
可以
1、
//在oncreate里
new Thread(newRunnable(){
public void run (){
button.setText("可以正常更新不報異常");
}
}).run();
原理在ActivityManagerService.java里
在onCreate沒到onResume時拷沸,是不會檢測是在子線程還是在主線程的
ViewRootImpl的創(chuàng)建在onResume方法回調(diào)之后色查,而我們是在onCreate方法中創(chuàng)建了子線程并訪問UI,在那個時刻撞芍,ViewRootImpl是沒有創(chuàng)建的秧了,無法檢測當(dāng)前線程是否是UI線程,所以程序沒有崩潰一樣能跑起來勤庐,而之后修改了程序示惊,讓線程休眠了200毫秒后,程序就崩了愉镰。很明顯200毫秒后ViewRootImpl已經(jīng)創(chuàng)建了米罚,可以執(zhí)行checkThread方法檢查當(dāng)前線程。
2丈探、Surface可以在子線程中更新UI
SurfaceView與View的刷新方法都是一樣的录择,通過lockCanvas和unlockCanvasAndPost方法來進(jìn)行畫的,但SurfaceView能在UI線程中刷新,也能在其它線程中刷新隘竭,而View只能在UI線程中刷新塘秦,View的刷新有一個checkThread(在ViewRootImp.java中)的判斷,如果不是在UI線程中就會拋異常动看, 這是google人為這樣設(shè)計的尊剔,不讓其它線程刷新View,SurfaceView就不會進(jìn)行判斷菱皆,這樣它就可以在其它線程中進(jìn)行刷新须误。