為什么要用Handler:
出于性能優化考慮,Android的UI操作并不是線程安全的,這意味著如果有多個線程并發操作UI組件,可能導致線程安全問題。為了解決這個問題,Android制定了一條簡單的原則:只允許UI線程(亦即主線程)修改Activity中的UI組件。
當一個程序第一次啟動時,Android會同時啟動一條主線程,主線程主要負責處理與UI相關的事件,如用戶的按鍵事件、用戶接觸屏幕的事件、屏幕繪圖事件,并把相關的事件分發到相應的組件進行處理,所以主線程通常又叫做UI線程。
Handler的概念:
1)**執行計劃任務**,可以在預定的時間執行某些任務,可以模擬定時器
2)**線程間通信**。在Android的應用啟動時,會創建一個主線程,主線程會創建一個
消息隊列來處理各種消息。**當你創建子線程時,你可以在你的子線程中拿到父線程中
創建的Handler 對象,就可以通過該對象向父線程的消息隊列發送消息了**。由于Android要求在UI線程中更新界面,因此,可以通過該方法在其它線程中更新界面。
Handler類包含如下方法用于發送、處理消息:
? void handlerMessage(Message msg):處理消息的方法,該方法通常用于被重寫。
? final boolean hasMessage(int what):檢查消息隊列中是否包含what屬性為指定值的消息。
? sendEmptyMessage(int what):發送空消息
? final boolean sendMessage(Message msg):立即發送消息,注意這塊返回值,如果message成功的被放到message queue里面則返回true,反之,返回false;(個人建議:對于這類問題不必主觀去記它,當實際使用時,直接查看源碼即可,源碼中有詳細的注釋)
Handler的作用:
(1)在一個線程中發送消息。
(2)在另一個線程中獲取、處理消息。
Handler處理的基本原理:
為了讓主線程能及時處理子線程發送的消息,顯然只能通過回調的方法來實現----開發者只要重寫Handler類中的方法,**當新啟動的線程發送消息時,消息會發送至與之關聯的MessageQueue,而Handler會不斷的從MessageQuere中獲取并處理消息**-----這將導致Handler類中處理消息的方法被回調。
在線程中使用Handler的基本步驟如下:
在被調用線程中完成以下內容:
(1)調用 Looper的prepare()方法為當前線程創建Looper對象,創建Looper對象時,它的構造器會創建與之配套的MessageQueue。
(2)有了Looper之后,創建Handler子類的實例,重寫HandlerMessage()方法,該方法負責處理來自其它線程的消息。
(3)調用Looper的loop()方法啟動Looper。
注:若被調用線程是主線程類,由于系統自動為主線程創建了Looper的實例,因此第一、三步驟可省略,而只需要做第2步即可。
在調用線程中完成:
(1)創建message,并填充內容。
(2)使用被調用類創建的Handler實例,調用sendMessage(Message msg)方法。
Handler 與線程的關系
Handler 一般運行于主線程內,也可以運行在子線程中,但是一定要創建Looper對象。主線程中Android已經為之創建了Looper對象
使用Handler的兩種常見情況:
1、只能在主UI中修改UI。但實際上,**有部分UI需要在子線程中控制其修改邏輯,因此子線程需要通過handler通知主線程修改UI**。這在游戲開發中尤其常見,比如需要讓新啟動的線程周期性的改變UI。、
**子線程通知主UI線程修改UI組件**的例子,新線程周期性的修改ImageView所顯示的圖片:
**這個例子是Handler在主線程中獲取,處理消息,在子線程中發送消息**
~~~
public class HandlerTest extends Activity
{
// 定義周期性顯示的圖片的ID
int[] imageIds = new int[]
{
R.drawable.java,
R.drawable.ee,
R.drawable.ajax,
R.drawable.xml,
R.drawable.classic
};
int currentImageId = 0;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final ImageView show = (ImageView) findViewById(R.id.show);
final Handler myHandler = new Handler()//在主線程中,獲取,處理消息,更新UI組件,可以修改UI組件
{
@Override
public void handleMessage(Message msg)
{
// 如果該消息是本程序所發送的
if (msg.what == 0x1233)
{
// 動態地修改所顯示的圖片
show.setImageResource(imageIds[currentImageId++
% imageIds.length]);
}
}
};
// 定義一個計時器,讓該計時器周期性地執行指定任務。子線程通知主線程修改UI組件,實現進程間通信
new Timer().schedule(new TimerTask()
{
@Override
public void run()
{
// 發送空消息
myHandler.sendEmptyMessage(0x1233);在線程中發送消息
}
}, 0, 1200);
}
}
~~~
2、為避免ANR,**應該在子線程中執行耗時較長的操作,而此操作完成后,有可能需要通知主線程修改UI**。
**在子線程中執行耗時任務后,通知主線程修改UI組件**的例子:使用新進程計算質數,并用Toast顯示
**這個例子是在主線程中發送消息,在子線程中獲取,處理消息**
~~~
public class CalPrime extends Activity
{
static final String UPPER_NUM = "upper";
EditText etNum;
CalThread calThread;
// 定義一個線程類
class CalThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();//創建Looper對象,每個線程使用Handler時都要有一個Looper對象
mHandler = new Handler()//在子線程中用handler獲取,處理消息
{
// 定義處理消息的方法
@Override
public void handleMessage(Message msg)
{
if(msg.what == 0x123)
{
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
// 計算從2開始、到upper的所有質數
outer:
for (int i = 2 ; i <= upper ; i++)
{
// 用i處于從2開始、到i的平方根的所有數
for (int j = 2 ; j <= Math.sqrt(i) ; j++)
{
// 如果可以整除,表明這個數不是質數
if(i != 2 && i % j == 0)
{
continue outer;
}
}
nums.add(i);
}
// 使用Toast顯示統計出來的所有質數
Toast.makeText(CalPrime.this , nums.toString()
, Toast.LENGTH_LONG).show();
}
}
};
Looper.loop();//啟動Looper
}
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
etNum = (EditText)findViewById(R.id.etNum);
calThread = new CalThread();
// 啟動新線程
calThread.start();
}
// 為按鈕的點擊事件提供事件處理函數
public void cal(View source)
{
// 創建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM ,
Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
// 在主線程中向新線程中的Handler發送消息
calThread.mHandler.sendMessage(msg);//在主線程中發送消息
}
}
~~~
- 前言
- Eclipse搭建android環境及Genymotion模擬器安裝問題解決方法
- 表格布局(TableLayout)及重要屬性
- 幀布局(FrameLayout)及屬性
- layout_width和width,layout_height和height
- UI組件之TextView及其子類
- UI組件之TextView及其子類(一)TextView和EditText
- UI組件之TextView及其子類(二)RadioButton和CheckBox
- UI組件之TextView及其子類(三)ToggleButton和Switch
- UI組件之TextView及其子類(四)AnalogClock,DigitalClock
- UI組件之TextView及其子類(五)計時器Chronometer
- UI組件之ImageView及其子類(一)ImageView顯示圖片
- UI組件之ImageView及其子類(二)ImageButton ,ZoomButton
- UI組件之AdapterView及其子類關系,Adapter接口及其實現類關系
- UI組件之AdapterView及其子類(一)三種Adapter適配器填充ListView
- UI組件之AdapterView及其子類(二)GridView網格視圖的使用
- UI組件之AdapterView及其子類(三)Spinner控件詳解
- UI組件之AdapterView及其子類(四)Gallery畫廊控件使用
- UI組件之AdapterView及其子類(五)ListView組件和ListActivity
- UI組件之AdapterView及其子類(六)ExpandableListView組件和ExpandableListActivity的使用
- UI組件之 ProgressBar及其子類(一)ProgressBar進度條的使用
- UI組件之ProgressBar及其子類(二)SeekBar拖動條和RatingBar星級評分條的使用
- ViewFlipper的功能和用法
- Toast的功能和用法
- TabHost選項卡的 功能和用法
- AlertDialog創建6種對話框的用法
- Android基于監聽的事件處理機制
- Android基于回調的事件處理
- Handler消息傳遞機制(一)
- Handler消息傳遞機制(二)Handler,Loop,Message,MessageQueue的工作原理
- 啟動Activity的兩種方式startActivity和startActivityForResult(一)
- 啟動Activity的兩種方式startActivity和startActivityForResult(二)
- Activity的生命周期理解
- Bundle在Activity之間交換數據
- 通過 Intent 傳遞類對象
- Intent對象詳解(一)
- Intent對象詳解(二)
- 使用指定的Action,Category調用系統Activity
- 使用Action,Data屬性啟動系統Activity
- Android數據存儲的三種方式-SharedPrefrences,File,SQLite