9.1服務(wù)是什么
服務(wù)( Service)是 Android 中實(shí)現(xiàn)程序后臺(tái)運(yùn)行的解決方案膝但,它適合用于去執(zhí)行不需要和用戶交互而且還要求長(zhǎng)期運(yùn)行的任務(wù)。服務(wù)的運(yùn)行不依賴于任何用戶界面谤草,當(dāng)程序被切換到后臺(tái)跟束,服務(wù)仍然能夠保持正常運(yùn)行。但服務(wù)并不是運(yùn)行在一個(gè)獨(dú)立的進(jìn)程當(dāng)中的咖刃,而是依賴于創(chuàng)建服務(wù)時(shí)所在的應(yīng)用程序進(jìn)程泳炉。當(dāng)某個(gè)應(yīng)用程序進(jìn)程被殺掉時(shí),所有依賴于該進(jìn)程的服務(wù)也會(huì)停止運(yùn)行嚎杨。另外花鹅,服務(wù)并不會(huì)自動(dòng)開啟線程,所有的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的枫浙。也就是說刨肃,我們需要在服務(wù)的內(nèi)部手動(dòng)創(chuàng)建子線程,并在這里執(zhí)行具體的任務(wù)箩帚,否則就有可能出現(xiàn)主線程被阻塞住的情況真友。
Android多線程編程
Android多線程編程與Java類似.新建一個(gè)類繼承Thread,重寫run(),其中編寫耗時(shí)邏輯.啟動(dòng)時(shí)只需new出實(shí)例,調(diào)用start()方法.或者實(shí)現(xiàn)Runable接口,實(shí)現(xiàn)run()方法.啟動(dòng)時(shí)通過
new Thread(myThread).start();
實(shí)現(xiàn).或者通過匿名類實(shí)現(xiàn)
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
}
}).start();
9.2.2在子線程中更新UI
在子線程中更新UI是不安全的,因此必須在主線程中進(jìn)行.
子線程更新UI一次:
Process: com.wjoker.androidthreadtest, PID: 8037
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
Android提供了異步消息處理機(jī)制,用于解決子線程中進(jìn)行UI操作的問題.
9.2.3解析異步消息處理機(jī)制
Android 中的異步消息處理主要由四個(gè)部分組成, Message紧帕、 Handler盔然、 MessageQueue 和Looper桅打。
- Message 是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息愈案,用于在不同線程之間交換數(shù)據(jù)挺尾。上一小節(jié)中我們使用到了 Message 的 what 字段,除此之外還可以使用 arg1 和 arg2 字段來攜帶一些整型數(shù)據(jù)站绪,使用 obj 字段攜帶一個(gè) Object 對(duì)象遭铺。
- Handler 顧名思義也就是處理者的意思,它主要是用于發(fā)送和處理消息的恢准。發(fā)送消息一般是使用 Handler 的 sendMessage()方法魂挂,而發(fā)出的消息經(jīng)過一系列地輾轉(zhuǎn)處理后,最終會(huì)傳遞到 Handler 的 handleMessage()方法中馁筐。
- MessageQueue 是消息隊(duì)列的意思涂召,它主要用于存放所有通過 Handler 發(fā)送的消息。這部分消息會(huì)一直存在于消息隊(duì)列中眯漩,等待被處理芹扭。每個(gè)線程中只會(huì)有一個(gè) MessageQueue對(duì)象。
- 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ì)象要尔。
首先需要在主線程當(dāng)中創(chuàng)建一個(gè) Handler 對(duì)象舍杜,并重寫handleMessage()方法。
然后當(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 操作了。
9.2.4使用AsyncTask
Android提供AsyncTask在子線程中對(duì)UI進(jìn)行操作,其原理是基于異步消息處理機(jī)制.
由于 AsyncTask 是一個(gè)抽象類锣光,所以如果我們想使用它笆怠,就必須要?jiǎng)?chuàng)建一個(gè)子類去繼承它。在繼承時(shí)我們可以為 AsyncTask 類指定三個(gè)泛型參數(shù)誊爹,這三個(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)行返回,則使用這里指定的泛型作為返回值類型状答。
示例:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
這里我們把 AsyncTask 的第一個(gè)泛型參數(shù)指定為 Void冷守,表示在執(zhí)行 AsyncTask 的時(shí)候不需要傳入?yún)?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)常需要去重寫的方法有以下四個(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...)方法后襟齿,這個(gè)方法就會(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ì)話框等笋妥。
示例:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show(); // 顯示進(jìn)度對(duì)話框
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload(); // 這是一個(gè)虛構(gòu)的方法
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(); // 關(guān)閉進(jìn)度對(duì)話框
// 在這里提示下載結(jié)果
if (result) {
Toast.makeText(context, "Download succeeded",Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, " Download failed",Toast.LENGTH_SHORT).show();
}
}
}
在這個(gè) DownloadTask 中,我們?cè)?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();
9.3服務(wù)的基本用法
9.3.1定義一個(gè)服務(wù)
定義一個(gè)類,繼承Service.實(shí)現(xiàn)onBind,onCreate,onStartCommand//啟動(dòng),onDestroy方法,
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate({
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
要注冊(cè)服務(wù)后才可使用:
9.3.2啟動(dòng)和停止服務(wù).
啟動(dòng):
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 啟動(dòng)服務(wù)
停止:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服務(wù)
9.3.3活動(dòng)和服務(wù)進(jìn)行通信
活動(dòng)和服務(wù)之間的通信可以通過onBind方法進(jìn)行
首先創(chuàng)建了一個(gè) ServiceConnection 的匿名類沐鼠,在里面重寫onServiceConnected()方法和 onServiceDisconnected()方法挚瘟,這兩個(gè)方法分別會(huì)在活動(dòng)與服務(wù)成功綁定以及解除綁定的時(shí)候調(diào)用。
在 onServiceConnected()方法中饲梭,我們又通過向下轉(zhuǎn)型得到了 DownloadBinder 的實(shí)例乘盖,有了這個(gè)實(shí)例, 活動(dòng)和服務(wù)之間的關(guān)系就變得非常緊密了°旧妫現(xiàn)在我們可以在活動(dòng)中根據(jù)具體的場(chǎng)景來調(diào)用 DownloadBinder 中的任何 public 方法订框,即實(shí)現(xiàn)了指揮服務(wù)干什么,服務(wù)就去干什么的功能兜叨。 這里仍然只是做了個(gè)簡(jiǎn)單的測(cè)試穿扳, 在onServiceConnected()方法中調(diào)用了 DownloadBinder 的 startDownload()和 getProgress()方法衩侥。當(dāng)然,現(xiàn)在活動(dòng)和服務(wù)其實(shí)還沒進(jìn)行綁定呢矛物,這個(gè)功能是在 Bind Service 按鈕的點(diǎn)擊事件里完成的茫死。可以看到履羞,這里我們?nèi)匀皇菢?gòu)建出了一個(gè) Intent 對(duì)象峦萎,然后調(diào)用 bindService()方法將 MainActivity 和 MyService 進(jìn)行綁定。 bindService()方法接收三個(gè)參數(shù)忆首,第一個(gè)參數(shù)就是剛剛構(gòu)建出的 Intent 對(duì)象骨杂,第二個(gè)參數(shù)是前面創(chuàng)建出的 ServiceConnection 的實(shí)例,第三個(gè)參數(shù)則是一個(gè)標(biāo)志位雄卷,這里傳入 BIND_AUTO_CREATE 表示在活動(dòng)和服務(wù)進(jìn)行綁定后自動(dòng)創(chuàng)建服務(wù)。 這會(huì)使得 MyService 中的 onCreate()方法得到執(zhí)行蛤售,但 onStartCommand()方法不會(huì)執(zhí)行丁鹉。然后如果我們想解除活動(dòng)和服務(wù)之間的綁定該怎么辦呢?調(diào)用一下 unbindService()方法就可以了悴能,這也是 Unbind Service 按鈕的點(diǎn)擊事件里實(shí)現(xiàn)的功能揣钦。
9.4服務(wù)的生命周期
前面我們使用到的 onCreate()、 onStartCommand()漠酿、 onBind()和 onDestroy()等方法都是在服務(wù)的生命周期內(nèi)可能回調(diào)的方法冯凹。
一旦在項(xiàng)目的任何位置調(diào)用了 Context 的 startService()方法,相應(yīng)的服務(wù)就會(huì)啟動(dòng)起來炒嘲,并回調(diào) onStartCommand()方法宇姚。如果這個(gè)服務(wù)之前還沒有創(chuàng)建過, onCreate()方法會(huì)先于onStartCommand()方法執(zhí)行夫凸。服務(wù)啟動(dòng)了之后會(huì)一直保持運(yùn)行狀態(tài)浑劳,直到 stopService()或stopSelf()方法被調(diào)用塘慕。注意雖然每調(diào)用一次 startService()方法树碱, onStartCommand()就會(huì)執(zhí)行一次南蓬,但實(shí)際上每個(gè)服務(wù)都只會(huì)存在一個(gè)實(shí)例屡拨。所以不管你調(diào)用了多少次 startService()方法乞巧,只需調(diào)用一次 stopService()或 stopSelf()方法谭网,服務(wù)就會(huì)停止下來了裕坊。
另外滋饲,還可以調(diào)用 Context 的 bindService()來獲取一個(gè)服務(wù)的持久連接桶现,這時(shí)就會(huì)回調(diào)服務(wù)中的 onBind()方法躲雅。類似地,如果這個(gè)服務(wù)之前還沒有創(chuàng)建過巩那, onCreate()方法會(huì)先于onBind()方法執(zhí)行吏夯。之后此蜈,調(diào)用方可以獲取到 onBind()方法里返回的 IBinder 對(duì)象的實(shí)例,這樣就能自由地和服務(wù)進(jìn)行通信了噪生。只要調(diào)用方和服務(wù)之間的連接沒有斷開裆赵,服務(wù)就會(huì)一直保持運(yùn)行狀態(tài)。
當(dāng)調(diào)用了 startService()方法后跺嗽,又去調(diào)用 stopService()方法战授,這時(shí)服務(wù)中的 onDestroy()方法就會(huì)執(zhí)行,表示服務(wù)已經(jīng)銷毀了桨嫁。類似地植兰,當(dāng)調(diào)用了 bindService()方法后,又去調(diào)用unbindService()方法璃吧, onDestroy()方法也會(huì)執(zhí)行楣导,這兩種情況都很好理解。但是需要注意畜挨,我們是完全有可能對(duì)一個(gè)服務(wù)既調(diào)用了 startService()方法筒繁,又調(diào)用了 bindService()方法的,這種情況下該如何才能讓服務(wù)銷毀掉呢巴元?根據(jù) Android 系統(tǒng)的機(jī)制毡咏,一個(gè)服務(wù)只要被啟動(dòng)或者被綁定了之后,就會(huì)一直處于運(yùn)行狀態(tài)逮刨,必須要讓以上兩種條件同時(shí)不滿足呕缭,服務(wù)才能被銷毀。所以修己,這種情況下要同時(shí)調(diào)用 stopService()和 unbindService()方法恢总, onDestroy()方法才會(huì)執(zhí)行。
9.5服務(wù)的更多技巧
9.5.1使用前臺(tái)服務(wù)
大部分服務(wù)都在后臺(tái)運(yùn)行,但服務(wù)的系統(tǒng)優(yōu)先級(jí)較低,當(dāng)內(nèi)存不足時(shí)可能回收后臺(tái)運(yùn)行的服務(wù),如果需要服務(wù)一直保持運(yùn)行,可以使用前臺(tái)服務(wù).
前臺(tái)服務(wù)會(huì)有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示,下拉后可以看到更加詳細(xì)的信息.
在Service的onCreate()中使用通知,最后使用startForeground()方法.該方法接受兩個(gè)參數(shù),一個(gè)是通知的ID,另一個(gè)接受構(gòu)建的Notification對(duì)象.
public void onCreate(){
Log.e("wyxjoker", "onCreate executed");
super.onCreate();
Intent notificationIntent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);
Notification notification = new Notification.Builder(this)
.setAutoCancel(true)
.setContentTitle("This is a title")
.setContentText("This is content")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.build();
startForeground(1,notification);
}
9.5.2使用IntentService
服務(wù)中的嗲嗎都是默認(rèn)運(yùn)行在主線程中,如果服務(wù)里處理耗時(shí)操作,容易出現(xiàn)ANR(Application Not Responding).因此需要服務(wù)的每個(gè)具體方法里開啟子線程,處理耗時(shí)操作.
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
Android提供了IntentService類用于創(chuàng)建異步,會(huì)自動(dòng)停止的服務(wù).
新建類,繼承IntentService在onHandleIntent(Intent intent)中寫邏輯.
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); // 調(diào)用父類的有參構(gòu)造函數(shù)
}
@Override
protected void onHandleIntent(Intent intent) {
// 打印當(dāng)前線程的id
Log.d("MyIntentService", "Thread id is " + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}