(內(nèi)容來自《Android第一行代碼(第二版)》)
本文目錄
1. 線程的基本用法
2. 在子線程中更新UI
3. 解析異步消息處理機(jī)制
4. 使用AsyncTask
分割線
當(dāng)我們?cè)诔绦蛑袌?zhí)行一些耗時(shí)操作時(shí),比如發(fā)起一條網(wǎng)絡(luò)請(qǐng)求,考慮到網(wǎng)速等原因,服務(wù)器未必會(huì)立刻響應(yīng)我們的請(qǐng)求,此時(shí)我們就需要將這些操作放在子線程中去運(yùn)行,以防止主線程被阻塞。
1. 線程的基本用法
新建一個(gè)類繼承自Thread,然后重寫父類的run()方法
class MyThread extends Thread{
@Override
public void run(){
//處理具體的耗時(shí)邏輯
}
}
如何啟動(dòng)這個(gè)線程呢,只需要new出一個(gè)MyThread的實(shí)例,然后調(diào)用它的start()方法,這樣run()方法中的代碼就會(huì)在子線程中運(yùn)行了
new MyThread().start();
更多的時(shí)候我們會(huì)選擇使用實(shí)現(xiàn)Runnable接口的方式來定義一個(gè)線程
class MyThread implements Runnable{
@Override
public void run(){
//處理具體的耗時(shí)邏輯
}
}
啟動(dòng)該線程的方法
MyThread myThread = new MyThread();
new Thread(myThread) .start();
這里Thread的構(gòu)造函數(shù)接收一個(gè)Runnable參數(shù),而我們new出的MyThread 正是一個(gè)實(shí)現(xiàn)了Runable接口的對(duì)象,所以可以將它直接傳入Thread的構(gòu)造函數(shù)里。接著調(diào)用Thread的start()方法,run()方法中的代碼就會(huì)在子線程中運(yùn)行了。
當(dāng)然如果不想專門定義一個(gè)類去實(shí)現(xiàn)Runnable接口,也可以使用匿名類的方式實(shí)現(xiàn)
new Thread(new Runnable(){
@Override
public void run(){
//處理具體的耗時(shí)邏輯
}
}).start();
2. 在子線程中更新UI
和許多其他的GUI庫(kù)一樣,Android的UI也是線程不安全的,也就是說想要更新程序里的UI元素,必須在主線程中進(jìn)行,否則就會(huì)出現(xiàn)異常。
下面通過一個(gè)具體的例子來驗(yàn)證一下。
新建一個(gè)AndroidThreadTest項(xiàng)目
-
修改activity_main.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/change_text"
android:text="Change Text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text"
android:layout_centerInParent="true"
android:textSize="20sp"/>
</RelativeLayout>
布局文件中定義了兩個(gè)控件:
TextView用于在屏幕的正中央顯示一個(gè)Hello World字符串
Button用于改變TextView中顯示的內(nèi)容
我們希望在點(diǎn)擊Button后可以把TextView中顯示的字符串改成Nice to meet you
-
修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable(){
@Override
public void run(){
text.setText("Nice to meet you");
}
}).start();
break;
default:
break;
}
}
}
我們?cè)?code>Change Text按鈕點(diǎn)擊事件里開啟了一個(gè)子線程,然后在子線程中調(diào)用TextView的setText()方法將顯示的字符串改成Nice to meet you。
代碼邏輯很簡(jiǎn)單,不過我們是在子線程中更新UI的。
運(yùn)行程序發(fā)現(xiàn)程序崩潰了

從日志中我們可以看出是由于在子線程中更新UI導(dǎo)致的。
對(duì)于這種情況,Android提供了一套異步消息處理機(jī)制,完美的解決了在子線程中進(jìn)行UI操作的問題
3. 解析異步消息處理機(jī)制
首先來學(xué)習(xí)一下使用方法
-
修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private TextView text;
public static final int UPDATE_TEXT = 1;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case UPDATE_TEXT:
//在這里進(jìn)行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable(){
@Override
public void run(){
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);//將Message對(duì)象發(fā)送出去
}
}).start();
break;
default:
break;
}
}
}
這里我們先是定義了一個(gè)整型常量UPDATE_TEXT,用于表示更新TextView這個(gè)動(dòng)作。然后新增一個(gè)Handler對(duì)象,并重寫父類的handleMessage()方法,在這里對(duì)具體的Message進(jìn)行處理。如果發(fā)現(xiàn)Message的what字段的值等于UPDATE_TEXT,就將 TextView顯示的內(nèi)容改成Nice to meet you
下面再來看一下 Change Text按鈕的點(diǎn)擊事件中的代碼??梢钥吹?,這次我們并沒有在子線程里直接進(jìn)行UI操作
- 而是創(chuàng)建了一個(gè)
Message(android.os.Message)對(duì)象,并將它的what字段的值指定為UPDATE_TEXT - 然后調(diào)用Handler的
sendMessage()方法將這條Message發(fā)送出去。很快,Handler就會(huì)收到這條Message,并在handleMessage()方法中對(duì)它進(jìn)行處理。注意此時(shí)handleMessage()方法中的代碼就是在主線程當(dāng)中運(yùn)行的了,所以我們可以放心地在這里進(jìn)行UI操作。 - 接下來對(duì)Message攜帶的
what字段的值進(jìn)行判斷,如果等于UPDATE_TEXT,就將 TextView顯示的內(nèi)容改成 Nice to meet you。
現(xiàn)在重新運(yùn)行程序,發(fā)現(xiàn)UI確實(shí)更新了

下面來分析一下Android異步消息處理機(jī)制是如何工作的
Android異步消息處理主要由4個(gè)部分組成:Message、Handle、MessageQueue和Looper。
1.Message
Message是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。前面我們使用到了Message的what字段,除此之外還可以使用arg1和arg2字段來攜帶一些整型數(shù)據(jù),使用obj字段攜帶一個(gè)Object對(duì)象。
2.Handler
Handler顧名思義也就是處理者的意思,它主要用于發(fā)送和處理消息的。發(fā)送消息一般是使用Handler的sendMessage()方法,而發(fā)出的消息經(jīng)過一系列的輾轉(zhuǎn)處理后,最終會(huì)傳遞到Handler的handleMessage()方法中。
3.MessageQueue
MessageQueue是消息隊(duì)列的意思,它主要用于存放所有通過Handler發(fā)送的消息。這部分消息會(huì)一直存在于消息隊(duì)列中,等待被處理。每個(gè)線程中只會(huì)有一個(gè)MessageQueue對(duì)象。
4.Looper
Looper是每個(gè)線程中的MessageQueue的管家,調(diào)用Looper的loop()方法后,就會(huì)進(jìn)入到一個(gè)無限循環(huán)當(dāng)中,然后每當(dāng)發(fā)現(xiàn)MessageQueue中存在一條消息,就會(huì)將它取出,并傳遞到Handler的handleMessage()方法中。每個(gè)線程中也只會(huì)有一個(gè)Looper對(duì)象。
了解了 Message、 Handler、 Messagequeue以及 Looper的基本概念后,我們?cè)賮戆旬惒较⑻幚淼恼麄€(gè)流程梳理一遍。
- 首先需要在主線程當(dāng)中創(chuàng)建一個(gè)Handler對(duì)象,并重寫handMessage()方法。
- 然后當(dāng)子線程中需要進(jìn)行UI操作時(shí),就創(chuàng)建一個(gè) Message對(duì)象,并通過Handler將這條消息發(fā)送出去。
- 之后這條消息會(huì)被添加到 MessageQueue的隊(duì)列中等待被處理,而Looper則會(huì)一直嘗試從MessageQueue中取出待處理消息,最后分發(fā)回Handler的handleMessage()方法中。
由于Handler是在主線程中創(chuàng)建的,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行,于是我們?cè)谶@里就可以安心地進(jìn)行UI操作了。
整個(gè)異步消息處理機(jī)制的流程示意圖如圖所示:

4. 使用AsyncTask
為了更加方便在子線程中對(duì)UI進(jìn)行更新,Android提供了一些好用的工具,比如AsyncTask。
借助AsyncTask,即使對(duì)異步消息處理機(jī)制完全不了解,也可以很簡(jiǎn)單的從子線程切換到主線程。
AsyncTask的基本用法
由于AsyncTask是一個(gè)抽象類,所以如果我們想使用它,就必須創(chuàng)建一個(gè)子類去繼承它。
在繼承時(shí)我們可以為AsyncTask類指定3個(gè)泛型參數(shù)
Params:在執(zhí)行AsyncTask時(shí)需要傳入的參數(shù),可用于在后臺(tái)任務(wù)中使用
Progress:后臺(tái)任務(wù)執(zhí)行時(shí),如果需要在界面上顯示當(dāng)前進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位
Result:當(dāng)任務(wù)執(zhí)行完畢后,如果需要對(duì)結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類型
因此一個(gè)最簡(jiǎn)單的自定義AsyncTask就可以寫成如下方式:
class DownloadTask extends AsyncTask<Void , Integer , Boolean>{
...
}
這里我們把AsyncTask的
第一個(gè)泛型參數(shù)指定為Void,表示在執(zhí)行AsyncTask的時(shí)候不需要傳人參數(shù)給后臺(tái)任務(wù)。
第二個(gè)泛型參數(shù)指定為Integer,表示使用整型數(shù)據(jù)來作為進(jìn)度顯示單位。
第三個(gè)泛型參數(shù)指定為Boolean,則表示使用布爾型數(shù)據(jù)來反饋執(zhí)行結(jié)果。
當(dāng)然,目前我們自定義的DownloadTask還是一個(gè)空任務(wù),并不能進(jìn)行任何實(shí)際的操作,我們還需要去重寫 AsyncTask中的幾個(gè)方法才能完成對(duì)任務(wù)的定制。
經(jīng)常需要去重寫的方法有以下4個(gè)。
-
onPreExecute()
這個(gè)方法會(huì)在后臺(tái)任務(wù)開始執(zhí)行之前調(diào)用,用于進(jìn)行一些界面上的初始化操作,比如顯示個(gè)進(jìn)度條對(duì)話框等。
-
doInBackground(Params ..)
這個(gè)方法中的所有代碼都會(huì)在子線程中運(yùn)行,我們應(yīng)該在這里去處理所有的耗時(shí)任務(wù)。任務(wù)一旦完成就可以通過return語句來將任務(wù)的執(zhí)行結(jié)果返回,如果AsyncTask的第三個(gè)泛型參數(shù)指定的是Void,就可以不返回任務(wù)執(zhí)行結(jié)果。注意,在這個(gè)方法中是不可以進(jìn)行UI操作的,如果需要更新UI元素,比如說反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度,可以調(diào)用publishProgress(Progress..)方法來完成
-
onProgressUpdate(Progress ...)
當(dāng)在后臺(tái)任務(wù)中調(diào)用了publishProgress(Progress...)方法后,onProgressUpdate(Progress...)方法就會(huì)很快被調(diào)用,該方法中攜帶的參數(shù)就是在后臺(tái)任務(wù)中傳遞過來的。在這個(gè)方法中可以對(duì)UI進(jìn)行操作,利用參數(shù)中的數(shù)值就可以對(duì)界面元素進(jìn)行相應(yīng)的更新。
-
onPostExecute(Result)
當(dāng)后臺(tái)任務(wù)執(zhí)行完畢并通過return語句進(jìn)行返回時(shí),這個(gè)方法就很快會(huì)被調(diào)用。返回的數(shù)據(jù)會(huì)作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來進(jìn)行一些UI操作,比如說提醒任務(wù)執(zhí)行的結(jié)果,以及關(guān)閉掉進(jìn)度條對(duì)話框等。
因此,一個(gè)比較完整的自定義AsyncTask就可以寫成如下方式:
class DownloadTask extends AsyncTask<Void ,Integer,Boolean> {
@Override
protected void onPreExecute(){
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params){
try{
while (true){
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if(downloadPercent >= 100){
break;
}
}
}catch (Exception e){
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values){
//這里更新下載進(jìn)度
progressDialog.setMessage("Downloaded "+values[0]+"%");
}
@Override
protected void onPostExecute(Boolean result){
progressDialog.dismiss();
if(result){
Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
}
}
}
在這個(gè)DownloadTask中
我們?cè)?code>doInBackground()方法里去執(zhí)行具體的下載任務(wù)。這個(gè)方法里的代碼都是在子線程中運(yùn)行的,因而不會(huì)影響到主線程的運(yùn)行。
注意這里虛構(gòu)了一個(gè)doDownload()方法,這個(gè)方法用于計(jì)算當(dāng)前的下載進(jìn)度并返回,我們假設(shè)這個(gè)方法已經(jīng)存在了。
在得到了當(dāng)前的下載進(jìn)度后,下面就該考慮如何把它顯示到界面上了,由于 doInBackground()方法是在子線程中運(yùn)行的,在這里肯定不能進(jìn)行UI操作,所以我們可以調(diào)用publishProgress()方法并將當(dāng)前的下載進(jìn)度傳進(jìn)來,這樣onProgressUpdate()方法就會(huì)很快被調(diào)用,在這里就可以進(jìn)行UI操作了。
當(dāng)下載完成后,doInBackground()方法會(huì)返回一個(gè)布爾型變量,這樣onPostExecute()方法就會(huì)很快被調(diào)用,這個(gè)方法也是在主線程中運(yùn)行的。然后在這里我們會(huì)根據(jù)下載的結(jié)果來彈出相應(yīng)的Toast提示,從而完成整個(gè)DownloadTask任務(wù)。
簡(jiǎn)單來說,使用AsyncTask的訣竅就是,在doInBackground()方法中執(zhí)行具體的耗時(shí)任務(wù),在onProgressUpdate()方法中進(jìn)行UI操作,在onPostExecute()方法中執(zhí)行一些任務(wù)的收尾工作。
如果想要啟動(dòng)這個(gè)任務(wù),只需編寫以下代碼即可
new DownloadTask().execute();
以上就是AsyncTask的基本用法,怎么樣,是不是感覺簡(jiǎn)單方便了許多?我們并不需要去考慮什么異步消息處理機(jī)制,也不需要專門使用一個(gè)Handler來發(fā)送和接收消息,只需要調(diào)用一下publishProgress()方法,就可以輕松地從子線程切換到UI線程了。