浅谈Handler
前言
積土成山,風(fēng)雨興焉;積水成淵,蛟龍生焉;積善成德,而神明自得;
Handler消息傳遞機(jī)制
出于性能優(yōu)化考慮,Android的UI操作并不是線程安全的,這意義著如果有多個(gè)線程并發(fā)操作UI組件,則可能導(dǎo)致線程安全問題。為了解決這個(gè)問題,Android制定了一條簡單的規(guī)則:只允許UI線程修改Activity里的UI組件。在實(shí)際開發(fā)中,需要讓新啟動(dòng)的線程周期性地改變界面組件的屬性值,這就需要借助于Handler的消息傳遞機(jī)制來實(shí)現(xiàn)了。
Handler類簡介
Handler類的主要作用有兩個(gè)。
- 在新啟動(dòng)的線程中發(fā)送消息。
- 在主線程中獲取、處理消息。
Handler類的功能看似簡單,似乎只要在新啟動(dòng)的線程中發(fā)送消息,在主線程中獲取、處理消息。但這個(gè)過程涉及兩個(gè)問題:新啟動(dòng)的線程何時(shí)發(fā)送消息呢?主線程何時(shí)去獲取并處理消息呢?
為了讓主線程能在適當(dāng)?shù)臅r(shí)機(jī)處理新啟動(dòng)的線程所發(fā)送的消息,顯然只能通過回調(diào)的方式來實(shí)現(xiàn)——需要重寫Handler類中處理消息的方法。
Handler類包含如下方法用于發(fā)送、處理消息。
- void handleMessage(Message msg):處理消息的方法。通常被重寫。
- final boolean hasMessages(int what):檢查消息隊(duì)列是否包含what屬性為指定值的消息。
- final boolean hasMessages(int what, Object object):檢查消息隊(duì)列中是否包含what屬性為指定值且object屬性為指定對象的消息。
- sendEmptyMessage(int what):發(fā)送空消息。
- final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少秒后發(fā)送空消息。
- final boolean sendMessage(Message msg):立即發(fā)送消息。
- final boolean sendMessageDelayed(Message msg,long delayMillis)
本實(shí)例將通過一個(gè)新線程來周期性地修改ImageView所顯示的圖片,自動(dòng)播放圖片還可以使用ViewFlipper和AdapterViewFlipper組件來實(shí)現(xiàn)。
代碼示例
MainActivity.java
public class MainActivity extends Activity {// 定義周期性顯示的圖片IDint[] imageIds = new int[]{R.drawable.baxianhua,R.drawable.dengta,R.drawable.juhua,R.drawable.kaola,R.drawable.qie,R.drawable.shamo,R.drawable.shuimo,R.drawable.yujinx};int currentImageId = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.handler);final ImageView show = (ImageView) findViewById(R.id.image);final Handler myHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {if(msg.what == 0x123){//動(dòng)態(tài)地修改所顯示的圖片show.setImageResource(imageIds[currentImageId++ % imageIds.length]);}}};//定義一個(gè)計(jì)時(shí)器,讓該計(jì)時(shí)器周期性地執(zhí)行指定任務(wù)new Timer().schedule(new TimerTask() {@Overridepublic void run() {//發(fā)送空消息myHandler.sendEmptyMessage(0x123);}}, 0, 1200);//1.2秒更改一次} }效果
Screenshot_20171026-112831.pngHandler、Loop、MessageQueue的工作原理
為了更好地理解Handler的工作原理,下面介紹一下與Handler一起工作的幾個(gè)組件。
Message:Handler接收和處理的消息對象。
Looper:每個(gè)線程只能擁有一個(gè)Looper。它的loop()方法負(fù)責(zé)讀取MessageQueue中的消息,讀到信息之后就把消息交給發(fā)送該消息的Handler進(jìn)行處理。
MessageQueue:消息隊(duì)列,它采用先進(jìn)先出的方式來管理Message。程序創(chuàng)建Looper對象時(shí),會在它的構(gòu)造器中創(chuàng)建MessageQueue對象。
Handler的作用有兩個(gè)——發(fā)送消息和處理消息,程序使用Handler發(fā)送消息,由Handler發(fā)送的消息必須被送到指定的MessageQueue。也就是說當(dāng)前線程必須要有一個(gè)MessageQueue,不過MessageQueue是由Looper負(fù)責(zé)管理的。如果希望Handler正常工作,必須在當(dāng)前線程中有一個(gè)Looper對象。為了保證當(dāng)前線程中有Looper對象,可以分如下兩種情況處理。
主UI線程中,系統(tǒng)已經(jīng)初始化了一個(gè)Looper對象,因此程序直接創(chuàng)建Handler即可,然后就可通過Handler來發(fā)送消息、處理消息了。
程序員自己啟動(dòng)的子線程,必須自己創(chuàng)建一個(gè)Looper對象,并啟動(dòng)它。創(chuàng)建Looper對象調(diào)用它的prepare()方法,調(diào)用loop()方法來啟動(dòng)它。
在線程中使用Handler的步驟如下。
調(diào)用Looper的prepare()方法為當(dāng)前線程創(chuàng)建Looper對象,創(chuàng)建Looper對象時(shí),它的構(gòu)造器會創(chuàng)建與之配套的MessageQueue。
有了Looper之后,創(chuàng)建Handler子類的實(shí)例,重寫handleMessage()方法,該方法負(fù)責(zé)處理來自于其他線程的消息。
調(diào)用Looper的loop()方法啟動(dòng)Looper。
接下來看一段程序,該程序的功能是允許用戶輸入一個(gè)數(shù)值上限,當(dāng)用戶單擊“計(jì)算”按鈕時(shí),該應(yīng)用會將該上限數(shù)值發(fā)送到新啟動(dòng)的線程中,讓該線程來計(jì)算該范圍內(nèi)的所有質(zhì)數(shù)。
代碼示例
public class MainActivity extends Activity {static final String UPPER_NUM = "upper";EditText et;CalThread calThread;// 定義一個(gè)線程類class CalThread extends Thread {public Handler handler;public void run() {Looper.prepare();handler = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == 0x123) {int upper = msg.getData().getInt(UPPER_NUM);List<Integer> nums = new ArrayList<Integer>();//計(jì)算從2開始、到upper的所有質(zhì)數(shù)outer:for (int i = 2; i <= upper; i++) {for(int j = 2;j <= Math.sqrt(i); j++) {if(i % j == 0){continue outer;}}nums.add(i);}//使用Toast顯示統(tǒng)計(jì)出來的所有質(zhì)數(shù)Toast.makeText(MainActivity.this, nums.toString(), Toast.LENGTH_LONG).show();}}};Looper.loop();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.handlerdemo1);et = (EditText) findViewById(R.id.et);calThread = new CalThread();calThread.start();}public void computer(View v){//創(chuàng)建消息Message msg = new Message();msg.what = 0x123;Bundle bundle = new Bundle();bundle.putInt(UPPER_NUM, Integer.parseInt(et.getText().toString()));msg.setData(bundle);//向新線程中的Handler發(fā)送消息calThread.handler.sendMessage(msg);} }效果
Screenshot_20171026-134515.png提示
盡量避免在UI線程中執(zhí)行耗時(shí)操作。
總結(jié)
- 上一篇: Git 命令集 实践整理
- 下一篇: SICP 4.21