androidhandlerui
1. 能講講Android的Handler機制嗎
Android的Handler機制是通俗講為了互相發消息,一般是子線程給主線程發消息完成相應操作。
安卓中最常見的操作是子線程操作完事後得到數據想更新UI,安卓有規定不允許在子線程中刷新UI,所以Handler出現了。
使用和理解大致步驟。
創建全局Handler對象handler,然後在主線程中初始化它(一般在oncreate中),把它的handmessage裡面的方法重寫,這個方法是收到子線程發給它的消息後執行的邏輯。
在子線程中獲取數據,調用handler.sendmessage,把要發的消息放在message中。message會添加到Messagequue(消息隊列中,handler創建就帶的)。
3.對象handler被創建和初始化的時候,系統自動會啟動Handler.looper,也就是一個消息輪詢器,它不斷的去查看有沒有消息進入到Messagequue(消息隊列中),有就取出交給handler的handmessage去處理。//這段邏輯是系統自動執行,理解就行。*純手打,不騙人~~~
2. [Android源碼分析] - 非同步通信Handler機制
一、問題:在Android啟動後會在新進程里創建一個主線程,也叫UI線程( 非線程安全 )這個線程主要負責監聽屏幕點擊事件與界面繪制。當Application需要進行耗時操作如網路請求等,如直接在主線程進行容易發生ANR錯誤。所以會創建子線程來執行耗時任務,當子線程執行完畢需要通知UI線程並修改界面時,不可以直接在子線程修改UI,怎麼辦?
解決方法:Message Queue機制可以實現子線程與UI線程的通信。
該機制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable對象 發給Looper,由它把消息放入所屬線程的消息隊列中,然後Looper又會自動把消息隊列里的消息/Runnable對象 廣播 到所屬線程里的Handler,由Handler處理接收到的消息或Runnable對象。
1、Handler
每次創建Handler對象時,它會自動綁定到創建它的線程上。如果是主線程則默認包含一個Message Queue,否則需要自己創建一個消息隊列來存儲。
Handler是多個線程通信的信使。比如在線程A中創建AHandler,給它綁定一個ALooper,同時創建屬於A的消息隊列AMessageQueue。然後在線程B中使用AHandler發送消息給ALooper,ALooper會把消息存入到AMessageQueue,然後再把AMessageQueue廣播給A線程里的AHandler,它接收到消息會進行處理。從而實現通信。
2、Message Queue
在主線程里默認包含了一個消息隊列不需要手動創建。在子線程里,使用Looper.prepare()方法後,會先檢查子線程是否已有一個looper對象,如果有則無法創建,因為每個線程只能擁有一個消息隊列。沒有的話就為子線程創建一個消息隊列。
Handler類包含Looper指針和MessageQueue指針,而Looper里包含實際MessageQueue與當前線程指針。
下面分別就UI線程和worker線程講解handler創建過程:
首先,創建handler時,會自動檢查當前線程是否包含looper對象,如果包含,則將handler內的消息隊列指向looper內部的消息隊列,否則,拋出異常請求執行looper.prepare()方法。
- 在 UI線程 中,系統自動創建了Looper 對象,所以,直接new一個handler即可使用該機制;
- 在 worker線程 中,如果直接創建handler會拋出運行時異常-即通過查『線程-value』映射表發現當前線程無looper對象。所以需要先調用Looper.prepare()方法。在prepare方法里,利用ThreadLocal<Looper>對象為當前線程創建一個Looper(利用了一個Values類,即一個Map映射表,專為thread存儲value,此處為當前thread存儲一個looper對象)。然後繼續創建handler, 讓handler內部的消息隊列指向該looper的消息隊列(這個很重要,讓handler指向looper里的消息隊列,即二者共享同一個消息隊列,然後handler向這個消息隊列發送消息,looper從這個消息隊列獲取消息) 。然後looper循環消息隊列即可。當獲取到message消息,會找出message對象里的target,即原始發送handler,從而回調handler的handleMessage() 方法進行處理。
- handler與looper共享消息隊列 ,所以handler發送消息只要入列,looper直接取消息即可。
- 線程與looper映射表 :一個線程最多可以映射一個looper對象。通過查表可知當前線程是否包含looper,如果已經包含則不再創建新looper。
5、基於這樣的機制是怎樣實現線程隔離的,即在線程中通信呢。
核心在於 每一個線程擁有自己的handler、message queue、looper體系 。而 每個線程的Handler是公開 的。B線程可以調用A線程的handler發送消息到A的共享消息隊列去,然後A的looper會自動從共享消息隊列取出消息進行處理。反之一樣。
二、上面是基於子線程中利用主線程提供的Handler發送消息出去,然後主線程的Looper從消息隊列中獲取並處理。那麼還有另外兩種情況:
1、主線程發送消息到子線程中;
採用的方法和前面類似。要在子線程中實例化AHandler並設定處理消息的方法,同時由於子線程沒有消息隊列和Looper的輪詢,所以要加上Looper.prepare(),Looper.loop()分別創建消息隊列和開啟輪詢。然後在主線程中使用該AHandler去發送消息即可。
2、子線程A與子線程B之間的通信。
1、 Handler為什麼能夠實現不同線程的通信?核心點在哪?
不同線程之間,每個線程擁有自己的Handler、消息隊列和Looper。Handler是公共的,線程可以通過使用目標線程的Handler對象來發送消息,這個消息會自動發送到所屬線程的消息隊列中去,線程自帶的Looper對象會不斷循環從裡面取出消息並把消息發送給Handler,回調自身Handler的handlerMessage方法,從而實現了消息的線程間傳遞。
2、 Handler的核心是一種事件激活式(類似傳遞一個中斷)的還是主要是用於傳遞大量數據的?重點在Message的內容,偏向於數據傳輸還是事件傳輸。
目前的理解,它所依賴的是消息隊列,發送的自然是消息,即類似事件中斷。
0、 Android消息處理機制(Handler、Looper、MessageQueue與Message)
1、 Handler、Looper源碼閱讀
2、 Android非同步消息處理機制完全解析,帶你從源碼的角度徹底理解
謝謝!
wingjay
![](https://avatars0.githubusercontent.com/u/9619875?v=3&s=460)
3. android怎麼樣在子線程實例化的handler去更新UI
之前用過android-async-http,雖然沒認真看過源碼,但也有簡單的瀏覽過,心裡一直有個疑問,因為android-async-http也是採用hanlder機制來執行回調的,也就是說handler是它實例化的,可我們知道handler的一個重要作用是將一個任務切換到handler所在的線程去執行的,我的疑問就是:如果我們在子線程調用android-async-http的網路請求,這時候如果handler是在子線程被實例化的呢(當然我沒認真研究過源碼,也不知道它是怎麼實現),還能更新UI嗎?
我們都正常以為handler在哪個線程實例化的,我們通過handler就可以把任務切換到該任務去執行,我們看如下代碼:
private void initd() {
new Thread(new Runnable() {
@Override
public
void run() {
Looper.prepare();
Handler handler = new
Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "測試子線程彈出toast",
Toast.LENGTH_LONG).show();
((TextView)findViewById(R.id.main_tv_text)).setText("測試子線程");
}
};
Looper.loop();
handler.sendEmptyMessage(0);
}
}).start();
}
經過我的測試上面這段方法是無法更新UI的,因為handler是在子線程實例化的,並非在UI線程,也證實了我們的想法。
如果我的疑問存在(我沒嘗試過在子線程使用android-async-http,不知道什麼情況,這里只是做個假設),那麼它使怎麼實現的呢。
最近看了android開發藝術探索,特意去研究了一下android的消息機制,才弄明白了Handler的工作原理,其實起關鍵作用的是Looper並不是handler,我們先來看下Looper的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper會把所在的當前的線程保存起來,而handler的工作需要Looper,於是我嘗試了一下如下代碼:
private void init() {
new Thread(new Runnable() {
@Override
public
void run() {
Handler handler = new
Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "測試子線程彈出toast",
Toast.LENGTH_LONG).show();
((TextView)findViewById(R.id.main_tv_text)).setText("測試子線程");
}
};
handler.sendEmptyMessage(0);
}
}).start();
}
handler實例化的時候,我傳入的是UI線程的Looper,確實是可以更新UI。
總結:
1、handler執行任務不是在它實例化所在的線程決定的,而是關鍵在於Looper。
2、我們可以在子線程實例化handler並且可以用它來更新UI了。
4. Android:在一個非主線程內直接調用UI線程的Handler實例,這樣沒問題嗎
我們開發應用程序的時候,處於線程安全的原因子線程通常是不能直接更新主線程(UI線程)中的UI元素的,那麼在Android開發中有幾種方法解決這個問題,其中方法之一就是利用Handler處理的。
下面說下有關Handler相關的知識。
多線程一些基礎知識回顧:
在介紹Handler類相關知識之前,我們先看看在Java中是如何創建多線程的
方法有兩種:
通過繼承Thread類,重寫Run方法來實現
通過繼承介面Runnable實現多線程
接下來讓我們看看Handler是什麼?如何來用
Handler是這么定義的:
主要接受子線程發送的數據, 並用此數據配合主線程更新UI.
Handler的主要作用:主要用於非同步消息的處理
Handler的運行過程:
當(子線程)發出一個消息之後,首先進入一個(主線程的)消息隊列,發送消息的函數即刻返回,而在主線程中的Handler逐個的在消息隊列中將消息取出,然後對消息進行處理。這樣就實現了跨線程的UI更新(實際上還是在主線程中完成的)。
這種機制通常用來處理相對耗時比較長的操作,如訪問網路比較耗時的操作,讀取文大文件,比較耗時的操作處理等。
在大白話一點的介紹它的運行過程:
啟動應用時Android開啟一個主線程 (也就是UI線程) , 如果此時需要一個耗時的操作,例如: 聯網讀取數據,或者讀取本地較大的一個文件的時候,你不能把這些操作放在主線程中,如果你放在主線程中的話,界面會出現假死現象(這也就是你在主線程中直接訪問網路時會提示你異常的原因,如我們上篇文章所述Android主線程不能訪問網路異常解決辦法
)。
這個時候我們需要把這些耗時的操作,放在一個子線程中,因為子線程涉及到UI更新,Android主線程是線程不安全的,更新UI只能在主線程中更新.。
這個時候,Handler就出現了,來解決這個復雜的問題,由於Handler運行在主線程中(UI線程中), 它與子線程可以通過Message對象來傳遞數據, 這個時候,Handler就承擔著接受子線程傳過來的(子線程用sedMessage()方法傳弟)Message對象,(裡麵包含數據) , 把這些消息放入主線程隊列中,配合主線程進行更新UI。
接下來我們看看關於Handler都有哪些方法(它都能幹什麼):
其中Handler對象的一些主要方法,如下:
post(Runnable) postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
正如方法名字中看到的,有兩類方法:
(1)在某個主線程中執行Runnable
(2)在子線程中發送一個消息,在主線程中檢測該消息處理
線程間傳遞Message對象的sendMessage方法和發送Runnable多線程對象的post方法。正對應著上面所說的兩個特性1)、2)
下面開發個Handler實例做說明:
用post的方法執行一個Runnable對象,在該對象中隨機產生一個10-100之間的隨機數,賦值到UI主線程中的TextView中線程,執行5次,每次相隔5秒, 拼接每次的數字, 最後執行結果應該如:10 22 33 44 61
主要代碼如下:
int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.post(run);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
String s = String.valueOf(msg.what);
TextView tv = (TextView)findViewById(R.id.textView);
tv.setText(tv.getText() + 」 」 + s);
}
};
Runnable run = new Runnable(){
@Override
public void run(){
Random r = new Random();
int rnum = r.nextInt((100 – 10) + 1) + 10;
handler.sendEmptyMessage(rnum);
handler.postDelayed(run, 5000);
i++;
if (i==5){
handler.removeCallbacks(run);
}
}
};
5. Android-Handle(線程間通信)詳解
線程間通信是在Android開發中比較經常遇到的,我們刷新UI界面一般是通過子線程做完某些事情後,要改變主頁面就要通過數據的通信,讓主線程接收到信息後自己改變UI界面。
1. Handle 先進先出原則;
2. Looper 類用來管理特定線程內對象之間的消息交換(MessageExchange);
3. Message 類用來保存數據。
1.Looper: 一個線程可以產生一個Looper對象,由它來管理此線程里的MessageQueue(消息隊列);
2.Handler: 你可以構造Handler對象來與Looper溝通,以便push新消息到MessageQueue里;或者接收Looper從Message Queue取出)所送來的消息;
android.os.Message的主要功能是進行消息的封裝,同時可以指定消息的操作形式,Message類定義的變數和常用方法如下:
在整個消息處理機制中,message又叫task,封裝了任務攜帶的信息和處理該任務的handler。message的用法比較簡單,但是有這么幾點需要注意:
在使用Handler處理Message時,需要Looper(通道)來完成。在一個Activity中,系統會自動幫用戶啟動Looper對象,而在一個用戶自定義的類中,則需要用戶手工調用Looper類中的方法,然後才可以正常啟動Looper對象。Looper的字面意思是「循環者」,它被設計用來使一個普通線程變成Looper線程。所謂Looper線程就是循環工作的線程。在程序開發中(尤其是GUI開發中),我們經常會需要一個線程不斷循環,一旦有新任務則執行,執行完繼續等待下一個任務,這就是Looper線程。使用Looper類創建Looper線程很簡單:
這是在子線程中創建Handler的情況,如果在主線程中創建Handler是不需要調用 Looper.prepare(); 和 Looper.loop(); 方法。
Handler是更新UI界面的機制,也是消息處理的機制。我們可以通過Handle發送消息,也可以處理消息。
Android在設計的時候,封裝了一套消息創建、傳遞、處理機制,如果不遵循這樣的機制就沒有辦法更新UI信息,就會拋出異常。
創建Handler實例化對象時,可以重寫的回調方法:
6. Android-Handler同步屏障
消息機制的同步屏障,其實就是阻礙同步消息,只讓非同步消息通過。而開啟同步屏障的方法就是調用下面的方法:
源碼如下:
在這里可以看到,Message對象初始化的時候並沒有給target賦值,因此target==null的來源就找得到了。這樣就可以插入一條target==null的消息,這個消息就是一個同步屏障。
那麼開啟消息屏障後,所謂的非同步消息又是如何處理的呢?
消息的最終處理其實都是在消息輪詢器Looper#loop()中,而loop()循環中會調用MessageQueue#next()從消息隊列中進行取消息。
從上面的MessageQueue.next方法可以看出,當消息隊列開啟同步屏障的時候(即標識為msg.target==null),消息機制在處理消息的時候,會優先處理非同步消息。這樣,同步屏障就起到了一種過濾和優先順序的作用。
如果上圖所示,在消息隊列中有同步消息和非同步消息(黃色部分)以及一道牆(同步屏障--紅色部分)。有了同步屏障的存在,msg_2和msg_M這兩個非同步消息可以被優先處理,而後面的msg_3等同步消息則不會被處理。那麼這些同步消息什麼時候可以被處理呢?就需要先移除這個同步屏障,即調用MessageQueue#removeSyncBarrier()
同步屏障一般在日常開發中比較少用,而在系統源碼中就有使用。Android系統中的UI更新相關的消息即為非同步消息,需要優先處理。
16ms左右刷新UI,而是60hz的屏幕,即1s刷新60次。
在Android中什麼是非同步消息?即給:
比如,在View更新時,draw、requestLayout、invalidate等很多地方都調用了。ViewRootImpl#scheleTraversals()。
在這里,mChoreographer.postCallback最終會執行到了Choreographer#postCallbackDelayedInternal()
可以看到,這里就開啟了同步屏障,並且發送了非同步消息。由於UI相關的消息是優先順序最高的,這樣系統就會優先處理這些非同步消息。
最後,當然要移除同步屏障的時候,調用ViewRootImpl#unscheleTraversals
在ViewRootImpl中的doTraversal()方法中也會移除同步屏障,這里移除是因為requestLayout或者invalidate的時候,刷新之後,在doTraversal()中就會移除同步屏障,因為此時消息已經發送並且處理了。