參考書籍:《第一行代碼》 第二版 郭霖
如有錯漏,請批評指出!
Android多線程
在開發(fā)過程中,我們經(jīng)常需要進(jìn)行網(wǎng)絡(luò)請求,而這一過程顯然是一個耗時操作。如果直接在主線程(UI線程)中進(jìn)行耗時操作,可能會導(dǎo)致主線程被阻塞,造成ANR(Application Not Responding)異常,因此,我們需要在子線程中進(jìn)行耗時操作。那么,在Android中有哪些開辟子線程的方式呢?
1. 繼承Thread類,重寫run()方法
public class MyThread extends Thread{
MyThread() {
}
@Override
public void run() {
//TODO
}
}
使用方法:
MyThread thread = new MyThread();
thread.start();
2. 實現(xiàn)Runnable接口,并實現(xiàn)其run()方法
public class MyThread implements Runnable{
MyThread() {
}
@Override
public void run() {
//TODO
}
}
使用方法:
MyThread myThread = new MyThread();
new Thread(myThread).start();
上面兩種方式都需要去定義一個類,不過在第二種種方式下,我們可以使用匿名類的方式來實現(xiàn)(這種方式也更加常見):
new Thread(new Runnable() {
@Override
public void run() {
//TODO
}
}).start();
3. 使用Handler
- Handler配合sendMessage()使用
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//這里可以進(jìn)行UI操作
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}
}).start();
}
}
- Handler配合post()方法使用
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.post(new Runnable() {
@Override
public void run() {
//這里可進(jìn)行UI操作
}
});
}
}
這兩種方式都會將消息加入消息隊列,如果是post() / sendMessage(),即將消息加入到消息隊列末尾,當(dāng)消息隊列沒有消息時會立即執(zhí)行;如果是postDelayed() / sendMessageDelayed()或postAtTime() / sendMessageAtTime(),則會在延遲時間后執(zhí)行。這兩種方式中,方式二的優(yōu)先級高于方式一。
解析異步消息處理機(jī)制
Android中的異步消息處理主要由4個部分組成:Message、Handler、MessageQueue、Looper。
1. Message
Message 是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。Message中包含四個字段,what、arg1、arg2這三個字段可以攜帶整型數(shù)據(jù),obj字段可以攜帶一個Object對象。
2. Handler
Handler主要用于發(fā)送和處理消息,發(fā)送消息一般是使用Handler的sendMessage()方法,最終消息會傳遞到Handler的handleMessage()方法中。
3. MessageQueen
MessageQueen(即消息隊列),主要用于存放所有通過Handler發(fā)送的消息。這部分消息會一直存在于消息隊列中,等待被處理。每個線程中只會有一個MessageQueen對象。
4. Looper
Looper是每個線程中的MessageQueen的管家,調(diào)用Looper的loop()方法后,就會進(jìn)入一個無限循環(huán),每當(dāng)發(fā)現(xiàn)MessageQueen中存在消息,就會依次取出,并傳遞到Handler的handleMessage()方法中。每個線程也只會有一個Looper對象。
以上是基本概念,接下來梳理一下異步消息處理的流程:首先在主線程中創(chuàng)建一個Handler對象,并重寫handleMessage方法。當(dāng)子線程中需要進(jìn)行UI操作時,就創(chuàng)建一個Message對象,并通過Handler將這條消息發(fā)送出去。之后這條消息會被添加到MessageQueen的隊列中等待被處理,而Looper會一直嘗試從MessageQueen中取出待處理消息,然后分發(fā)回Handler的handleMessage()方法中,所以此時handleMessage()方法中的代碼也會在主線程中運行。也就是說我們的UI操作實際上還是在主線程中進(jìn)行的。
下面是異步消息處理機(jī)制流程圖:

AsyncTask 的使用
AsyncTask(異步任務(wù)),是Android提供的一個處理異步任務(wù)的類,它的實現(xiàn)原理也是基于異步消息處理機(jī)制的,只是Android對其進(jìn)行了很好的封裝。下面來看看關(guān)于AsyncTask的基本用法。
AsyncTask是一個抽象類,因此,我們需要創(chuàng)建一個類來繼承它。在繼承時我們可以為AsyncTask類指定3個泛型參數(shù),這三個參數(shù)的用途如下:
1. Params:在執(zhí)行AsyncTask時需要傳入的參數(shù),可用于在后臺任務(wù)中使用。
2. Progress:后臺任務(wù)執(zhí)行中,如果需要在界面上顯示當(dāng)前的進(jìn)度,則使用這里指定的泛型參數(shù)作為進(jìn)度單位。
3. Result:當(dāng)任務(wù)執(zhí)行完成后,如果需要對結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類型。
因此,一個簡單的自定義AsyncTask可以寫成下面這樣:
public class DownloadTask extends AsyncTask<Void, Integer, Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Boolean doInBackground(Void... voids) { return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); } }
這里我們將AsyncTask 的三個泛型參數(shù)依次指定為 Void、Integer、Boolean,因為后面我們會實現(xiàn)一個下載功能,因此我們需要返回實時進(jìn)度(用整型作為進(jìn)度顯示單位),最后還要返回下載結(jié)果(用布爾型表示)。
為了實現(xiàn)這樣的一個功能,我們還需要重寫上面這四個方法。
1. onPreExecute():這個方法一般用來進(jìn)行一些初始化操作,比如初始化對象,顯示dialog等。
2. doInBackground(Params...):這個方法會在子線程中執(zhí)行,因此,我們要在這里執(zhí)行耗時任務(wù),任務(wù)完成后就將執(zhí)行結(jié)果返回,如果AsyncTask的第三個泛型參數(shù)指定為Void類型,就不需要返回。由于這個方法是在子線程中執(zhí)行,因此我們不能在這里進(jìn)行UI操作(比如更新下載進(jìn)度),不過我們可以通過下面這個方法來實現(xiàn)。
3. onProgressUpdate(Progress...):在后臺任務(wù)中調(diào)用 publishProgress(Progress...) 這個方法后,onProgressUpdate(Progress...) 方法就會被調(diào)用,這個方法中的參數(shù),就是在 doInBackground(Params...) 方法中調(diào)用 publishProgress(Progress...) 方法時傳遞的參數(shù),利用參數(shù)中的數(shù)值可以進(jìn)行更新界面中的進(jìn)度條。
4. onPostExecute(Result):當(dāng)后臺任務(wù)執(zhí)行完并通過return語句返回數(shù)據(jù)時,這個方法就會被調(diào)用。返回的數(shù)據(jù)會作為參數(shù)傳遞到這個方法中,可以在這里進(jìn)行一些關(guān)于后臺任務(wù)執(zhí)行結(jié)果的處理,比如說提示任務(wù)執(zhí)行情況,關(guān)閉dialog等。
上面的自定義AsyncTask我們會在后面進(jìn)行完善。下面先來看一下關(guān)于Service的內(nèi)容。
服務(wù)的基本用法
作為Android四大組件之一,Service 是一個可以在后臺執(zhí)行長時間運行操作而不提供用戶界面的應(yīng)用組件。服務(wù)可由其他應(yīng)用組件啟動,而且即使用戶切換到其他應(yīng)用,服務(wù)仍將在后臺繼續(xù)運行。 此外,組件可以綁定到服務(wù),以與之進(jìn)行交互,甚至是執(zhí)行進(jìn)程間通信 (IPC)。 例如,服務(wù)可以處理網(wǎng)絡(luò)事務(wù)、播放音樂,執(zhí)行文件 I/O 或與內(nèi)容提供程序交互,而所有這一切均可在后臺進(jìn)行。
1.服務(wù)的定義
AndroidStudio提供了自動創(chuàng)建Service的方法, 打開項目之后,點擊 File->New->Service->Service,就可以創(chuàng)建自己的Service了。其中有兩個屬性:
Exported 表示是否允許除了當(dāng)前程序之外的其他程序訪問這個服務(wù);
Enabled 表示是否啟用這個服務(wù)。
這兩個屬性是默認(rèn)勾選的,保持不變就行。
創(chuàng)建完成后,我們會發(fā)現(xiàn),其實就是定義了一個類,繼承了Service類,但是還有一點,那就是AS自動在AndroidManifest文件中對這個Service進(jìn)行了注冊:

這是Android四大組件的共性,都需要在AndroidManifest文件中進(jìn)行注冊,也是我們手動創(chuàng)建Service時需要注意的事情。
在我們創(chuàng)建的MyService里面,系統(tǒng)已經(jīng)自動為我們添加了一個構(gòu)造方法和一個onBind() 方法,因為onBind()方法是Service類中的一個抽象方法,因此我們必須重寫(這里只是創(chuàng)建了一個空的服務(wù),什么邏輯度沒有寫)。下面先來看看關(guān)于Service生命周期的內(nèi)容,后面再看Service如何使用。
2. Service的生命周期
先來看一張圖:
根據(jù)上圖可以看到,服務(wù)基本上分兩種方式,并且它們的的生命周期是不同的。
-
啟動
當(dāng)應(yīng)用組件(如 Activity)通過調(diào)用 startService() 啟動服務(wù)時,服務(wù)即處于“啟動”狀態(tài)。一旦啟動,服務(wù)即可在后臺無限期運行,即使啟動服務(wù)的組件已被銷毀也不受影響。 直到 stopService() 方法(組件調(diào)用)或 stopSelf() 方法(Service自身調(diào)用)被調(diào)用,服務(wù)停止并回調(diào)onDestroy()方法銷毀,或由于系統(tǒng)資源不足而被回收。需要注意的是,服務(wù)只會存在一個實例,因此無論調(diào)用多少次 startService() 方法,只需要調(diào)用一次 stopService() 方法或 stopSelf() 方法,服務(wù)就會停止。
調(diào)用 startService() 方法啟動服務(wù)時,生命周期是上圖中的第一種,并且 onCreate() 方法只會回調(diào)一次,在服務(wù)已經(jīng)被創(chuàng)建的情況下,再次調(diào)用 startService() 方法,會回調(diào) onStartCommand() 方法。下面來打印生命周期看看:
在前面創(chuàng)建的MyService中重寫所有的生命周期,添加Log打印。public class MyService extends Service { public MyService() { } @Override public IBinder onBind(Intent intent) { Log.d("jh", "onBind()"); throw new UnsupportedOperationException("Not yet implemented"); } @Override public boolean onUnbind(Intent intent) { Log.d("jh", "onUnbind()"); return super.onUnbind(intent); } @Override public void onCreate() { super.onCreate(); Log.d("jh", "onCreate()"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("jh", "onStartCommand()"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d("jh", "onDestroy()"); } }然后在Activity中添加兩個Button并添加點擊事件,啟動和停止服務(wù):
運行demo,點擊三次 StartService Button,點擊兩次 StopService Button,下面是log:case R.id.but_start: intent = new Intent(this, MyService.class); startService(intent); break; case R.id.but_stop: intent = new Intent(this, MyService.class); stopService(intent); break;由此可以知道,當(dāng)我們首次創(chuàng)建Service時,會回調(diào)onCreate()方法和onStartCommand()方法,在Service運行過程中,再次調(diào)用startService() 方法,只會回調(diào)onStartCommand()方法。當(dāng)我們調(diào)用 stopService()方法或stopSelf() 方法時,會回調(diào)onDestroy()方法銷毀服務(wù)。
-
綁定
當(dāng)應(yīng)用組件通過調(diào)用 bindService() 綁定到服務(wù)時,服務(wù)即處于“綁定”狀態(tài)。綁定服務(wù)提供了一個客戶端-服務(wù)器接口,允許組件與服務(wù)進(jìn)行交互、發(fā)送請求、獲取結(jié)果,甚至是利用進(jìn)程間通信 (IPC) 跨進(jìn)程執(zhí)行這些操作。 僅當(dāng)與另一個應(yīng)用組件綁定時,綁定服務(wù)才會運行。 多個組件可以同時綁定到該服務(wù),但全部取消綁定(unBindService()方法)后,該服務(wù)即會被銷毀。
僅僅通過startService() 方法啟動服務(wù)后,Activity與服務(wù)之間是基本沒有什么聯(lián)系的,自然也無法進(jìn)行通信。如果要實現(xiàn)Activity與Service之間進(jìn)行通信,我們就需要借助 onBind() 方法將Activity綁定到Service了。下面我們通過前面提到過的下載功能的思路來實現(xiàn)Activity與Service之間的通信。想要實現(xiàn)Activity與Service之間的通信,我們需要借助Binder對象。- 首先,在服務(wù)MyService中創(chuàng)建一個DownLoadBinder內(nèi)部類,并繼承Binder類。
public class MyService extends Service { private DownloadBinder mBinder; public MyService() { mBinder = new DownloadBinder(); } @Override public IBinder onBind(Intent intent) { Log.d("jh", "onBind()"); return mBinder; } ··· public class DownloadBinder extends Binder { public void startDownliad() { Log.d("jh", "startDownload()"); } public int getProgress() { Log.d("jh", "getProgress()"); return 0; } } }我們可以在Activity中獲取到這個DownloadBinder對象,通過這個 DownloadBinder 對象來對Service中的任務(wù)進(jìn)行控制。下面來看Activity的代碼:
public class SerMainActivity extends AppCompatActivity { private MyService.DownloadBinder mDownloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("jh", "onServiceConnected()"); mDownloadBinder = (MyService.DownloadBinder) service; mDownloadBinder.startDownliad(); mDownloadBinder.getProgress(); } @Override public void onServiceDisconnected(ComponentName name) { Log.d("jh", "onServiceDisconnected()"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ser_activity_main); ButterKnife.bind(this); } @OnClick({R.id.but_start, R.id.but_stop, R.id.but_bind, R.id.but_unbind}) public void click(View v) { Intent intent; switch (v.getId()){ ··· case R.id.but_bind: intent = new Intent(this, MyService.class); bindService(intent, connection, BIND_AUTO_CREATE); break; case R.id.but_unbind: unbindService(connection); break; default: break; } } }從上面的代碼可以看到,當(dāng)我們bind服務(wù)的時候,需要傳遞一個ServiceConnection對象。方便起見,我們創(chuàng)建一個ServiceConnection的匿名類,在里面重寫 onServiceConnected() 和 onServiceDisconnected() 方法。onServiceConnection() 方法會在服務(wù)成功綁定的時候被調(diào)用。這里需要注意:只有當(dāng)onBind()方法返回IBinder對象時,這個方法才會被調(diào)用,如果onBind()方法返回空,這個方法就不會被調(diào)用。這個方法有一個IBinder參數(shù),也就是unBind()方法返回的那個IBinder對象,所以在這里我們需要向下轉(zhuǎn)型,將這個IBinder對象類型強制轉(zhuǎn)換為我們定義的DownloadBinder對象,然后在這個方法里面對Service中執(zhí)行的任務(wù)進(jìn)行控制,比如說開始下載任務(wù)、獲取下載進(jìn)度等(當(dāng)然,這里定義的這兩個方法都是空方法,還沒有實現(xiàn))。
bindService() 方法的第三個參數(shù)是一個標(biāo)志位,這里我們傳入 BIND_AUTO_CREATE ,表示在活動和服務(wù)進(jìn)行綁定后自動創(chuàng)建服務(wù),這會使得MyService 中的 onCreate() 方法得到執(zhí)行,而 onStartCommand() 方法不會執(zhí)行。
下面運行看看:
bindService() -> unBindService()從上面的log可以看出,當(dāng)我們將一個Activity綁定到Service時,如果Service還未創(chuàng)建,會先回調(diào)onCreate() 方法創(chuàng)建服務(wù),然后回調(diào)onBind()方法,如果這個onBind()方法返回一個IBinder對象,在綁定完成后就會調(diào)用onServiceConnected()方法,這個方法是在Activity中實現(xiàn)的,可以通過這個方法來管理Service;然后,當(dāng)任務(wù)執(zhí)行完后,我們調(diào)用unBindService()方法,會依次回調(diào)onUnbind()方法解綁,回調(diào)onDestroy()方法銷毀服務(wù)。
startService() -> bindService() -> unBindService() -> stopService()
這里我們創(chuàng)建啟動Service后,又調(diào)用bindService()方法將Activity綁定到Service,一直到這里,生命周期并沒有什么特殊,但是當(dāng)我們unBindService()時,只是回調(diào)onUnbind()方法將Service解綁,并沒有銷毀服務(wù),直到我們stopService(),服務(wù)才被銷毀,其實就是 onStartCommand()方法被回調(diào)的原因,只要回調(diào)了onStartCommand()方法,服務(wù)就會一直在后臺運行,除非我們調(diào)用onStopService()方法或stopSelf()方法將其銷毀或者服務(wù)被系統(tǒng)殺死。
bindService() -> startService() -> stopService() -> unBindService()這個執(zhí)行順序和上面又有一些不同,當(dāng)我們綁定服務(wù)并且回調(diào)onStartCommand()方法后,直接調(diào)用onStopService()方法,會發(fā)現(xiàn)沒有哪個生命周期方法回調(diào),這說明服務(wù)還在后臺運行,其實這也很好理解,因為有一個Activity還與Service綁定著,所以服務(wù)會等待這個Activity去和它解綁,當(dāng)我們調(diào)用unBindService()方法解綁時,服務(wù)和Activity解綁后還會回調(diào)onDestroy()方法銷毀服務(wù)。
個人理解:onStartCommand()方法會使服務(wù)一直在后臺運行,并且只有onStopService()方法和stopSelf()方法可以讓服務(wù)停下來(由于系統(tǒng)資源不足導(dǎo)致Service被kill的情況除外);而僅僅將服務(wù)與別的組件綁定時,一旦所有組件都與Service解綁,服務(wù)就會被銷毀。
3. 關(guān)于前臺服務(wù)
我們知道,服務(wù)幾乎都是在后臺運行的,但是服務(wù)的系統(tǒng)優(yōu)先級還是比較低的,因此,當(dāng)系統(tǒng)內(nèi)存不足時,就有可能會回收掉后臺運行的服務(wù)。但是有時候我們需要用服務(wù)執(zhí)行一些很重要的任務(wù),比如說播放音樂,這時就可以使用前臺服務(wù)了。
前臺服務(wù)其實是通過構(gòu)建一個通知,顯示在下拉狀態(tài)欄,就像我們平時使用的音樂播放器,當(dāng)我們播放音樂時,就會在狀態(tài)欄創(chuàng)建一個通知,用來控制音樂的播放。下面來看看如何創(chuàng)建一個前臺服務(wù):
-
只需要重寫onCreate()方法就行了,構(gòu)建一個Notificaton對象后,沒有通過NotificationManager來將通知顯示出來,而是調(diào)用setForeground()方法,這個方法接收兩個參數(shù),第一個參數(shù)是通知的id,第二個參數(shù)就是Notification對象。這樣,我們的Service就是前臺服務(wù)了。看下效果:@Override public void onCreate() { super.onCreate(); Log.d("jh", "onCreate()"); Intent intent = new Intent(this, SerMainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); Notification notification = new NotificationCompat.Builder(this, "default") .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_huaji)) .setSmallIcon(R.mipmap.ic_android) .setContentTitle("第一行代碼") .setContentText("這是一個前臺服務(wù)") .setContentIntent(pi) .build(); startForeground(1, notification); }
4.使用IntentService
我們知道,服務(wù)中的代碼都是默認(rèn)運行在主線程中的,如果我們處理一些耗時的邏輯,就容易出現(xiàn)ANR異常,因此,我們需要在每個具體的方法中開啟一個子線程,用來執(zhí)行耗時操作,并且需要我們調(diào)用stopService()方法或者stopSelf()方法,來停止服務(wù)。為了可以簡單的創(chuàng)建一個異步的、會自動停止的服務(wù),Android提供了一個IntentSrvice類,下面我們來創(chuàng)建一個IntentService來看看它和Service到底有什么不同:
File -> New -> Service -> Service(IntentService)
創(chuàng)建一個MyIntentservice
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { Log.d("jh", "Thread id:" + Thread.currentThread().getId()); } @Override public void onDestroy() { super.onDestroy(); Log.d("jh", "onDestroy()"); } }
onHandleIntent()方法是一個抽象方法,我們必須實現(xiàn),這個方法是運行在子線程中的,因此,我們可以直接在這個方法里處理一些耗時邏輯,我們來打印這個方法所在線程的id,然后重寫onDestroy()方法并打印。
下面我們還需要添加一個Button用來開啟這個IntentService,并給它添加點擊事件:
-
我們在Activity中開啟這個IntentService,并且打印出Activity所在線程的id,用于和IntentService對比,下面來看運行之后的log:case R.id.but_start_intent_service: Log.d("jh", "Thread id:" + Thread.currentThread().getId()); intent = new Intent(this, MyIntentService.class); startService(intent); break;
可以看到,打印出來的兩個線程id是不同的,activity運行在id為1的主線程中,也就是說,IntentService中是默認(rèn)會開啟子線程的,并且當(dāng)任務(wù)執(zhí)行完成后,onDestroy()方法也自動回調(diào)了,這就說明IntentService確實集自動開啟子線程和自動停止于一身,并且用法和Service大同小異,可以說是Service的加強版了。
關(guān)于Service的基本內(nèi)容就到這里,后面會另寫一篇博客,實現(xiàn)前面提到的下載功能,作為服務(wù)的實踐。
上一篇:Android基礎(chǔ)回顧(八)| 使用HTTP協(xié)議訪問網(wǎng)絡(luò)





