什麼是Handler? Handler可以發送和處理消息對象或Runnable對象,這些消息對象和Runnable對象與一個線程相關聯。每個Handler的實例都關聯了一個線程和線程的消息隊列。當創建了一個Handler對象時,一個線程或消息隊列同時也被創建,該Handler對象將發送和處理這些消息 ...
什麼是Handler?
Handler可以發送和處理消息對象或Runnable對象,這些消息對象和Runnable對象與一個線程相關聯。每個Handler的實例都關聯了一個線程和線程的消息隊列。當創建了一個Handler對象時,一個線程或消息隊列同時也被創建,該Handler對象將發送和處理這些消息或Runnable對象。
handler類有兩種主要用途:
- 執行Runnable對象,還可以設置延遲。
- 兩個線程之間發送消息,主要用來給主線程發送消息更新UI。
為什麼要用Handler
解決多線程併發問題,假設如果在一個activity中,有多個線程去更新ui,並且都沒有加鎖機制,那界面顯示肯定會不正常。於是andoird官方就封裝了一套更新ui的機制,也可以用handler來實現多個線程之間的消息發送。
如何使用Handler
handler常用的方法有以下這些:
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable,long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
我們可以看到這些方法主要分為兩類,一種是傳入一個Runnable對象,一種是傳入一個Message對象。
用代碼來學習post一個Runnable對象
先創建Handler對象,直接new一個就行
private Handler handler=new Handler();
實現Runnable介面,用匿名實現方式,重寫run方法,就列印一個字元串。
private Runnable runnable=new Runnable() {
@Override
public void run() {
Log.i("MainActivity","Handler Runnable");
}
};
然後我們調用handler的post方法,這裡需要註意的是,post一個Runnable對象,底層用的是回調,不會開啟一個新的線程,所有Runnable的run方法還是在主線程裡面。是可以更新UI的。
handler.post(runnable);//執行
handler.postDelayed(runnable,2000);//延遲2秒後執行
運行程式,控制台列印的log如下:
05-18 19:17:14.901 17750-17750/com.ansen.handler I/MainActivity: Handler Runnable
05-18 19:17:16.901 17750-17750/com.ansen.handler I/MainActivity: Handler Runnable
從上面的log我們可以看到兩條Log的時間相差兩秒。這是因為我們用postDelayed方法的時候第二個參數設置了兩秒的延遲。
使用sendMessage方法傳遞消息
從方法的名字上我們可以理解用來發送消息,這個方法在android中使用頻率比較高,因為在Android中多線程中是不能更新UI的,必須要通過Handler把消息傳遞給UI線程,才能更新UI。當然也可以用Handler來兩個子線程發送消息。
我們給activity_main文件中TextView控制項設置一個id,然後在MainActivity中查找這個控制項,在多線程的for迴圈中給TextView賦值。增加後的代碼如下:
textview= (TextView) findViewById(R.id.textview);
new Thread(new Runnable(){
@Override
public void run(){
for(int i=1;i<=100;i++){
Log.i("MainActivity","當前值是:"+i);
textview.setText("當前值是:"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
重新運行代碼,程式奔潰。控制台列印如下log:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6024)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:820)
這是因為在android中不能在多線程中更新UI造成的。
每個應用啟動的時候,Android會啟動一個對應的主線程用來處理UI相關的事情,例如用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事件,並把相關的事件分發到對應的組件進行處理,所以主線程通常又被叫做UI線程。
這個時候我們就會用到Android的Handle類,Handle可以幫我們解決多線程不能更新UI問題,這裡我們只要知道使用這個類就行,在後面我們會詳細介紹它的原理。
接下來我們看如何用handler在主線程中接受子線程的消息,並且更新UI。首先new一個Handler的時候實現他的handleMessage方法,修改後的代碼如下:
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==UPDATE_UI){
textview.setText("當前值是:"+msg.obj);
}
}
};
我們可以看到把更新TextView的代碼放到這裡來了,並且用到handleMessage的msg參數。這個對象我們常用的一般就兩個屬性,what就是一個標示,我們發送消息的時候必需要指定值。obj:發送消息的參數。
再來看看多線程的run方法做了哪些改動,首先調用obtainMessage方法,這個方法呢是從消息池裡面返回一個Message對象,如果消息池沒有才會創建對象,這樣避免一直去new Message對象。message對象有what屬性是必需要賦值的,是一個int類型。前面我們講到過了,是一個標示。obj是發送消息用來傳參,這裡我們傳入的是i的值。最後調用handler.sendMessage(message)方法。然後我們handler的handleMessage方法就會回調。
new Thread(new Runnable(){
@Override
public void run(){
for(int i=1;i<=100;i++){
Log.i("MainActivity","當前值是:"+i);
Message message=handler.obtainMessage();
message.what=UPDATE_UI;
message.obj=i;
handler.sendMessage(message);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
還有sendEmptyMessage跟sendMessageDelayed方法我就不一一給大家解釋了,有興趣的朋友自己去實現一下。
源碼下載
如果你想第一時間看我們的後期文章,掃碼關註公眾號,每周不定期推送Android開發實戰教程文章,你還等什麼,趕快關註吧,學好技術,出任ceo,贏取白富美。。。。
Android開發666 - 安卓開發技術分享
掃描二維碼加關註