閑來無事颂翼,準備好好梳理一下Handler機制,之前分析過沒有寫成博客慨灭,結(jié)果就是慢慢的淡忘了朦乏,這次趁著剛分析完,趕緊寫下來氧骤。
在開始分析之前先打打基礎(chǔ)呻疹,理解理解什么是線程以及什么是Handler,這里大部分內(nèi)容引用一篇來自伯樂在線的文章筹陵,因為看來看去關(guān)于基礎(chǔ)的部分這個人已經(jīng)說得很好了刽锤,我就負責(zé)把主要的部分抽取出來。
原文地址:Android線程和Handler基礎(chǔ)入門
現(xiàn)在大多數(shù)的移動設(shè)備已經(jīng)變得越來越快朦佩,但是它們其實也不算是非巢⑺迹快。如果你想讓你的APP既可以承受一些繁雜的工作而又不影響用戶體驗的話吕粗,那么必須把任務(wù)并行執(zhí)行纺荧。在Android上,我們使用線程颅筋。
什么是線程宙暇?
線程或者線程執(zhí)行本質(zhì)上就是一串命令(也是程序代碼),然后我們把它發(fā)送給操作系統(tǒng)執(zhí)行议泵。
一般來說占贫,我們的CPU在任何時候一個核只能處理一個線程。多核處理器(目前大多數(shù)Android設(shè)備已經(jīng)都是多核)顧名思義先口,就是可以同時處理多線程(通俗地講就是可以同時處理多件事)型奥。
多核處理與單核多任務(wù)處理的實質(zhì)
上面我說的是一般情況,并不是所有的描述都是一定正確的碉京。因為單核也可以用多任務(wù)模擬出多線程厢汹。
每個運行在線程中的任務(wù)都可以分解成多條指令,而且這些指令不用同時執(zhí)行谐宙。所以烫葬,單核設(shè)備可以首先切換到線程1去執(zhí)行指令1A,然后切換到線程2去執(zhí)行指令2A,接著返回到線程1再去執(zhí)行1B搭综、1C垢箕、1D,然后繼續(xù)切換到線程2兑巾,執(zhí)行2B条获、2C等等,以此類推蒋歌。
這個線程之間的切換十分迅速帅掘,以至于在單核的設(shè)備中也會發(fā)生。幾乎所有的線程都在相同的時間內(nèi)進行任務(wù)處理奋姿。其實锄开,這都是因為速度太快造成的假象素标,就像電影《黑客帝國》里的特工Brown一樣称诗,可以變幻出很多的頭和手。
Java核心里的線程
在Java中头遭,如果要想做平行任務(wù)處理的話寓免,會在Runnable里面執(zhí)行你的代碼〖莆可以繼承Thread類袜香,或者實現(xiàn)Runnable接口:
// 1
public class IAmAThread extends Thread {
public IAmAThread() {
super("IAmAThread");
}
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
new IAmAThread().start();
// 2
public class IAmARunnable implements Runnable {
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
IAmARunnable myRunnable = new IAmARunnable();
new Thread(myRunnable).start();
這兩個方法基本上是一樣的。第一個版本是創(chuàng)建一個Thread類鲫惶,第二個版本是需要創(chuàng)建一個Runnable對象蜈首,然后也需要一個Thread類來調(diào)用它。
Android上的線程
無論何時啟動APP欠母,所有的組件都會運行在一個單獨的線程中(默認的)——叫做主線程欢策。這個線程主要用于處理UI的操作并為視圖組件和小部件分發(fā)事件等,因此主線程也被稱作UI線程(Main Thread)赏淌。除了Main Thread之外的線程都可稱為Worker Thread踩寇。Main Thread主要負責(zé)控制UI頁面的顯示、更新六水、交互等俺孙。 因此所有在UI線程中的操作要求越短越好,只有這樣用戶才會覺得操作比較流暢掷贾。一個比較好的做法是把一些比較耗時的操作睛榄,例如網(wǎng)絡(luò)請求、數(shù)據(jù)庫操作想帅、 復(fù)雜計算等邏輯都封裝到單獨的線程场靴,這樣就可以避免阻塞主線程,這樣就需要用到了Android的Handler機制博脑。
這里劃重點:Handler負責(zé)與子線程進行通訊憎乙,從而讓子線程與主線程之間建立起協(xié)作的橋梁票罐,使Android的UI更新的問題得到完美的解決
怎么創(chuàng)建Handler
既然Handler有這樣的好處,那么看Handler怎么用泞边,官方給出了兩種方式創(chuàng)建一個Handler:
1该押、使用默認的構(gòu)造方法:new Handler()。
2阵谚、使用帶參的構(gòu)造方法蚕礼,參數(shù)是一個Runnable對象或者回調(diào)對象。
//第一種方法
private Handler handler = new Handler();
private Runnable myRunnable= new Runnable() {
public void run() {
//一些耗時操作
}
};
//其他地方調(diào)用
handler.post(xxx);
這里就寫一個post方法梢什,實際上還有很多奠蹬,諸如postDelayed、postAtTime
//第二種方法
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
//根據(jù)參數(shù)進行操作
break;
}
super.handleMessage(msg);
}
};
//其他地方調(diào)用
myHandler.sendMessage(xxx);
如何使用Handler
這里使用一個簡單的Demo來演示Handler的用法嗡午,界面偏簡單就不貼了囤躁,直接貼代碼,模擬的是點擊Button執(zhí)行下載荔睹,下載完成后更新UI狸演。
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("開始下載文件");
//此處讓線程DownloadThread休眠5秒中,模擬文件的耗時過程
Thread.sleep(5000);
System.out.println("文件下載完成");
//文件下載完成后更新UI
MainActivity.this.statusTextView.setText("文件下載完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
按照以前寫Java的思路的話可能會這么寫僻他,但是運行程序時候會發(fā)現(xiàn)控制臺報錯:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
錯誤的意思是只有創(chuàng)建View的原始線程才能更新View宵距。出現(xiàn)這樣錯誤的原因是Android中的View不是線程安全的,下面給出合理的解釋:
因為UI訪問是沒有加鎖的吨拗,在多個線程中訪問UI是不安全的满哪,如果有多個子線程都去更新UI,會導(dǎo)致界面不斷改變而混亂不堪劝篷。所以最好的解決辦法就是只有一個線程有更新UI的權(quán)限哨鸭,所以這個時候就只能有一個線程振臂高呼:放開那女孩,讓我來携龟!那么最合適的人選只能是主線程兔跌。
來自---Android中線程那些事
那么為了規(guī)避Android的這種機制,我們這里分別采用Handler的兩種方式來實現(xiàn)上面的代碼:
A峡蟋、使用post方式
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主線程中創(chuàng)建坟桅,所以自動綁定主線程
private Handler uiHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("開始下載文件");
//此處讓線程DownloadThread休眠5秒中,模擬文件的耗時過程
Thread.sleep(5000);
System.out.println("文件下載完成");
//文件下載完成后更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
MainActivity.this.statusTextView.setText("文件下載完成");
}
};
uiHandler.post(runnable);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
B蕊蝗、使用sendMessage方式實現(xiàn)
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主線程中創(chuàng)建仅乓,所以自動綁定主線程
private Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("文件下載完成");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("開始下載文件");
//此處讓線程DownloadThread休眠5秒中,模擬文件的耗時過程
Thread.sleep(5000);
System.out.println("文件下載完成");
//文件下載完成后更新UI
Message msg = new Message();
//雖然Message的構(gòu)造函數(shù)式public的蓬戚,我們也可以通過以下兩種方式通過循環(huán)對象獲取Message
//msg = Message.obtain(uiHandler);
//msg = uiHandler.obtainMessage();
//what是我們自定義的一個Message的識別碼夸楣,以便于在Handler的handleMessage方法中根據(jù)what識別
//出不同的Message,以便我們做出不同的處理操作
msg.what = 1;
//我們可以通過arg1和arg2給Message傳入簡單的數(shù)據(jù)
msg.arg1 = 123;
msg.arg2 = 321;
//我們也可以通過給obj賦值Object類型傳遞向Message傳入任意數(shù)據(jù)
//msg.obj = null;
//我們還可以通過setData方法和getData方法向Message中寫入和讀取Bundle類型的數(shù)據(jù)
//msg.setData(null);
//Bundle data = msg.getData();
//將該Message發(fā)送給對應(yīng)的Handler
uiHandler.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
以上代碼來自博客:Android中Handler的使用
上面這兩種形式都能達到我們的要求,在此不一一測驗豫喧,注釋寫的很詳細了石洗,看到這里應(yīng)該已經(jīng)大致知道了如何使用Handler,但是我想我們應(yīng)該遠遠不滿足于此紧显,下一篇博客將帶著大家從源碼一起看看Handler機制到底是怎么實現(xiàn)的讲衫。
Handler機制從入門到放棄(二)