不管怎么說奸例,后臺功能屬于四大組件之一查吊,其重要程度不言而喻
10.1 服務(wù)是什么
服務(wù)(Service
)是Android
中實(shí)現(xiàn)程序后臺運(yùn)行的解決方案,它非常適合去執(zhí)行那些不需要和用戶交互而且還要求長期運(yùn)行的任務(wù)评也。服務(wù)的運(yùn)行不依賴于任何用戶界面,即使程序被切換到后臺罚缕,或者用戶打開了另外一個(gè)應(yīng)用程序,服務(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ù)也會停止運(yùn)行。
另外腰吟,也不要被服務(wù)的后臺概念所迷惑,實(shí)際上服務(wù)并不會自動開啟線程灵疮,所有的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的。也就是說蒿赢,我們需要在服務(wù)的內(nèi)部手動創(chuàng)建子線程,并在這里執(zhí)行具體的任務(wù)晾腔,否則就有可能出現(xiàn)主線程被阻塞住的情況。
10.2.1 線程的基本用法
定義一個(gè)線程只需要新建一個(gè)類繼承自Thread
剔应,然后重寫父類的run()
方法,并在里面編寫耗時(shí)邏輯即可纤控。
class MyThread extends Thread {
@Override
public void run() {
//處理具體的邏輯
}
}
只需要new
出MyThread
的實(shí)例刻撒,然后調(diào)用它的start()
方法,這樣run()
方法中的代碼就會在子線程當(dāng)中運(yùn)行了醋火。
new MyThread().start();
當(dāng)然,使用繼承的方式耦合性有點(diǎn)高晚树,更多的時(shí)候我們都會選擇使用實(shí)現(xiàn)Runnable
接口的方式來定義一個(gè)線程。
class MyThread implements Runnable {
@Override
public void run() {
//處理具體的邏輯
}
}
如果使用了這種寫法,啟動線程的方法也需要進(jìn)行相應(yīng)的改變愚铡。
MyThread myThread = new MyThread();
new Thread(myThread).start();
Thread
的構(gòu)造函數(shù)接收一個(gè)Runnable
參數(shù),而我們new
出的MyThread
正是一個(gè)實(shí)現(xiàn)了Runnable
接口的對象邑雅,所以可以直接將它傳入到Thread
的構(gòu)造函數(shù)中。接著調(diào)用Thread
的start()
方法,run()
方法中的代碼就會在子線程當(dāng)中運(yùn)行了洞难。
當(dāng)然,如果你不想專門再定義一個(gè)類去實(shí)現(xiàn)Runnable
接口露筒,也可以使用匿名類的方式,這種寫法更為常見。
new Thread(new Runnable() {
@Override
public void run() {
//處理具體的邏輯
}
}).start();
10.2.2 在子線程中更新UI
和其他的GUI
庫一樣掌眠,Android
的UI
也是線程不安全的。也就是說渺尘,如果想要更新應(yīng)用程序里的UI
元素,則必須在主線程中進(jìn)行,否則就會出現(xiàn)異常腋逆。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView text;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text2);
button = (Button) findViewById(R.id.change_text);
button.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;
}
}
}
觀察logcat
中的錯(cuò)誤日志俏蛮,可以看出是由于在子線程中更新UI
所導(dǎo)致的
Process: com.example.androidthreadtest, PID: 10616
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
由此證實(shí)了Android
確實(shí)是不允許在子線程中進(jìn)行UI
操作的争涌。
有些時(shí)候模软,我們必須在子線程里去執(zhí)行一些耗時(shí)任務(wù)燃异,然后根據(jù)任務(wù)的執(zhí)行結(jié)果來更新相應(yīng)的UI
控件。對于這種情況,Android
提供了一套異步消息處理機(jī)制忘瓦,完美地解決了在子線程中進(jìn)行UI
操作的問題。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView text;
private Button button;
private static final int UODATE_TEXT = 1;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text2);
button = (Button) findViewById(R.id.change_text);
button.setOnClickListener(this);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UODATE_TEXT:
//在這里可以進(jìn)行UI操作
text.setText("Nice to meet you !");
break;
default:
break;
}
}
};
}
@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 = UODATE_TEXT;
handler.sendMessage(message);//將Message對象發(fā)送出去
}
}).start();
break;
default:
break;
}
}
}
我們先是定義了一個(gè)整形常量UPDATE_TEXT
李丰,用于表示更新TextView
這個(gè)動作舟舒。然后新增一個(gè)Handler
對象,并重寫父類的handleMessage()
方法夺鲜,在這里對具體的Message
進(jìn)行處理。如果發(fā)現(xiàn)Message
的what
字段的值等于UPDATE_TEXT
,就將TextView
顯示的內(nèi)容改成Nice to meet you
。
可以看到每辟,這次我們并沒有在子線程里直接進(jìn)行UI
操作渠欺,而是創(chuàng)建了一個(gè)Message(android.os.Message)
對象讹开,并將它的what
字段的值指定為UPDATE_TEXT
,然后調(diào)用Handler
的sendMessage()
方法將這條Message
發(fā)送出去。很快赏半,Handler
就會收到這條Message
,并在handleMessage()
方法中對它進(jìn)行處理。注意此時(shí)handleMessage()
方法中的代碼就是在主線程當(dāng)中運(yùn)行的了埃撵,所以我們可以放心地在這里進(jìn)行UI
操作。接下來對Message
攜帶的what
字段的值進(jìn)行判斷,如果等于UPDATE_TEXT
,就將TextView
顯示的內(nèi)容改成Nice to meet you
.
10.2.3 解析異步消息處理機(jī)制
Android
中的異步消息處理主要由四部分組成:Message
,Handler
辅鲸,MessageQueue
和Looper
例书。
1.Message
Message
是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。上面我們使用到了Message
的what
字段凉敲,除此之外還可以使用arg1
和arg2
字段來攜帶一些整形數(shù)據(jù)阻塑,使用obj
字段攜帶一個(gè)Object
對象渤昌。
2.Handler
Handler
顧名思義也就是處理者的意思,它主要是用于發(fā)送和處理消息。發(fā)送消息一般是使用Handler
的sendMessage()
方法狂秘,而發(fā)出的消息經(jīng)過一系列輾轉(zhuǎn)處理后清女,最終會傳遞到Handler
的handleMessage()
方法中拴袭。
3.MessageQueue
MessageQueue
是消息隊(duì)列的意思怜瞒,它主要用于存放所有通過Handler
發(fā)送的消息。這部分消息會一直存在于消息隊(duì)列中,等待被處理近刘。每個(gè)線程中只會有一個(gè)MessageQueue
對象臀晃。
4.Looper
Looper
是每個(gè)線程中的MessageQueue
的管家,調(diào)用Looper
的loop()
方法后介劫,就會進(jìn)入到一個(gè)無限循環(huán)當(dāng)中徽惋,然后每當(dāng)發(fā)現(xiàn)MessageQueue
中存在一條消息,就會將它取出险绘,并傳遞到Handler
的handleMessage()
方法中。每個(gè)線程中也只會有一個(gè)Looper
對象誉碴。
我們來把異步消息處理的整個(gè)流程梳理一遍宦棺。首先需要在主線程當(dāng)中創(chuàng)建一個(gè)Handler
對象,并重寫handleMessage()
方法黔帕。然后當(dāng)子線程中需要進(jìn)行UI
操作時(shí)代咸,就創(chuàng)建一個(gè)Message
對象,并通過Handler
將這條消息發(fā)送出去成黄。之后這條消息會被添加到MessageQueue
的隊(duì)列中等待被處理呐芥,而Looper
則會一直嘗試從MessageQueue
中取出待處理消息,最后分發(fā)回Handler
的handleMessage()
方法中奋岁。由于Handler
是在主線程中創(chuàng)建的思瘟,所以此時(shí)handleMessage()
方法中的代碼也會在主線程中運(yùn)行,于是我們在這里就可以安心地進(jìn)行UI
操作了闻伶。
一條Message
經(jīng)過這樣一個(gè)流程的輾轉(zhuǎn)調(diào)用后滨攻,也就從子線程進(jìn)入到了主線程,從不能更新UI
變成了可以更新UI
,整個(gè)異步消息處理的核心思想也就是如此光绕。
而我們前面使用到的runOnUiThread()
方法其實(shí)就是一個(gè)異步消息處理機(jī)制的接口封裝更鲁,它雖然表面上看起來用法更為簡單,但其實(shí)背后的實(shí)現(xiàn)原理和上圖的描述是一樣的奇钞。
10.2.4 使用AsyncTask
為了更加方便我們在子線程中對UI
進(jìn)行操作澡为,Android
還提供了另外一些好用的工具,比如AsyncTask
景埃。借助AsyncTask
媒至,即便你對異步消息處理機(jī)制完全不了解,也可以十分簡單地從子線程切換到主線程谷徙。當(dāng)然拒啰,AsyncTask
背后的實(shí)現(xiàn)原理也是基于異步消息處理機(jī)制的,只是Android
幫我們做了很好地封裝而已完慧。
AsyncTask
的基本用法谋旦,由于AsyncTask
是一個(gè)抽象類,所以如果我們想使用它屈尼,就必須要創(chuàng)建一個(gè)子類去繼承它册着。在繼承時(shí)我們可以為AsyncTask
類指定3個(gè)泛型參數(shù)。
Params: 在執(zhí)行AsyncTask
時(shí)需要傳入的參數(shù)脾歧,可用于在后臺任務(wù)中執(zhí)行甲捏。
Progress: 后臺任務(wù)執(zhí)行時(shí),如果需要在界面上顯示當(dāng)前的進(jìn)度鞭执,則使用這里指定的泛型作為進(jìn)度單位司顿。
Result: 當(dāng)任務(wù)執(zhí)行完畢后,如果需要對結(jié)果進(jìn)行返回兄纺,則使用這里指定的泛型作為返回值類型大溜。
因此,一個(gè)最簡單的自定義AsyncTask
就可以寫成如下方式:
class DownloadTask extends AsyncTask<Void,Integer,Boolean> {
....
}
我們把AsyncTask
的第一個(gè)泛型參數(shù)指定為Void
估脆,表示在執(zhí)行AsyncTask
的時(shí)候不需要傳入?yún)?shù)給后臺任務(wù)钦奋。第二個(gè)泛型參數(shù)指定為Integer
,表示使用整形數(shù)據(jù)來做為進(jìn)度顯示單位。第三個(gè)泛型參數(shù)指定為Boolean
,則表示使用布爾型數(shù)據(jù)來反饋執(zhí)行結(jié)果旁蔼。
目前我們自定義的DownloadTask
還是一個(gè)空任務(wù)锨苏,并不能進(jìn)行任何的操作聘鳞,我們還需要去重寫AsyncTask
中的幾個(gè)方法才能完成對任務(wù)的定制糯耍。
1.onPreExecute()
這個(gè)方法會在后臺任務(wù)開始之前調(diào)用谆膳,用于一些界面上的初始化操作巢株,比如顯示一個(gè)進(jìn)度條對話框等座每。
2.doInBackground(Params...)
這個(gè)方法中的所有代碼都會在子線程中運(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...)
方法來完成泞坦。
3.onProgressUpdate(Progress...)
當(dāng)在后臺任務(wù)中調(diào)用了publishProgress(Progress...)
方法后窖贤,onProgressUpdate(Progress...)
方法就會很快被調(diào)用,該方法中攜帶的參數(shù)就是在后臺任務(wù)中傳遞過來的贰锁。在這個(gè)方法中可以對UI
進(jìn)行操作赃梧,利用參數(shù)中的數(shù)值就可以對界面元素進(jìn)行相應(yīng)的更新。
4.onPostExecute(Result)
當(dāng)后臺任務(wù)執(zhí)行完畢通過return
語句進(jìn)行返回時(shí)豌熄,這個(gè)方法就很快被調(diào)用授嘀。返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來進(jìn)行一些UI
的操作锣险,比如說提醒任務(wù)執(zhí)行的結(jié)果蹄皱,以及關(guān)閉掉進(jìn)度對話框等。
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();//這是一個(gè)虛構(gòu)的方法
publishProgress(downloadPercent);
if (downloadPercent >= 100)
{
break;
}
}
} catch (Exception e)
{
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values)
{
//在這里進(jìn)行更新下載進(jìn)度
progressDialog.setMessage("Downloaded " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean aBoolean)
{
progressDialog.dismiss();//關(guān)閉進(jìn)度對話框
//在這里提示下載結(jié)果
if (aBoolean)
{
Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
}
}
}
在這個(gè)DownloadTask
中芯肤,我們在doInBackground()
方法中去執(zhí)行具體的下載任務(wù)巷折。這個(gè)方法里的代碼都是在子線程中運(yùn)行的,因而不會影響到主線程的運(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()
方法就會很快被調(diào)用世曾,在這里就可以進(jìn)行UI
操作了。
當(dāng)下載完成后谴咸,doInBackground()
方法會返回一個(gè)布爾型變量轮听,這樣onPostExecuet()
方法就會很快被調(diào)用,這個(gè)方法也是在主線程中運(yùn)行的岭佳。然后在這里我們會根據(jù)下載的結(jié)果來彈出相應(yīng)的Toast
提示血巍,從而完成整個(gè)DownloadTask
任務(wù)。
使用AsyncTask
的訣竅就是珊随,在doInBackground()
方法中執(zhí)行具體的耗時(shí)任務(wù)述寡,在onProgressUpdate()
方法中進(jìn)行UI
操作柿隙,在onPostExecute()
方法中執(zhí)行一些任務(wù)的收尾工作。
如果想要啟動這個(gè)任務(wù)鲫凶,只需編寫以下代碼:
new DownloadTask().execute()
服務(wù)的基本用法
定義一個(gè)服務(wù)
我們將服務(wù)命名為MyService
,Exported
屬性表示是否允許除了當(dāng)前程序以外的其他程序訪問這個(gè)服務(wù)禀崖,Enabled
屬性表示是否啟用這個(gè)服務(wù)。
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
MyService
是繼承自Service
類的螟炫,說明這是一個(gè)服務(wù)波附。目前MyService
中可以算是空空如也,但有一個(gè)onBind()
方法特別醒目昼钻。這個(gè)方法是Service
中唯一的一個(gè)抽象方法叶雹,所以必須要在子類里實(shí)現(xiàn)。
我們在服務(wù)中處理一些事情换吧,處理事情的邏輯寫在哪呢折晦?這時(shí)就可以重寫Service
中的另外一些方法了。
public class MyService extends Service {
```
@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();
}
}
我們又重寫了onCreate()
,onStartCommand()
和onDestroy()
這3個(gè)方法沾瓦,他們是每個(gè)服務(wù)中最常用到的3個(gè)方法了满着。其中onCreate()
方法會在服務(wù)創(chuàng)建的時(shí)候調(diào)用,onStartCommand()
方法會在每次服務(wù)啟動的時(shí)候調(diào)用贯莺,onDestroy()
方法會在服務(wù)銷毀的時(shí)候調(diào)用风喇。
通常情況下,如果我們希望服務(wù)一旦啟動就立刻去執(zhí)行某個(gè)動作缕探,就可以將邏輯寫在onStartCommand()
方法中魂莫。而當(dāng)服務(wù)銷毀時(shí),我們又應(yīng)該在onDestroy()
方法中回收那些不再使用的資源爹耗。
另外需要注意的是耙考,每一個(gè)服務(wù)都需要在AndroidManifest.xml
文件中進(jìn)行注冊才能生效。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
10.3.2 啟動和停止服務(wù)
啟動服務(wù)和停止服務(wù)的方法當(dāng)然你也不會陌生潭兽,主要是借助Intent
來實(shí)現(xiàn)的倦始。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);//啟動服務(wù)
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);//停止服務(wù)
break;
default:
break;
}
}
在onCreate()
方法中分別獲取到了Start Service
按鈕和Stop Service
按鈕的實(shí)例,并給他們注冊了點(diǎn)擊事件山卦。然后在Start Service
按鈕的點(diǎn)擊事件里我們構(gòu)建出了一個(gè)Intent
對象鞋邑,并調(diào)用startService()
方法來啟動MyService
服務(wù)。
在Stop Service
按鈕的點(diǎn)擊事件里我們同樣構(gòu)建出了一個(gè)Intent
對象账蓉,并調(diào)用stopService()
方法來停止MyService
服務(wù)枚碗。
startService()
和stopService()
方法都是定義在Context
類中的,所以我們在活動里可以直接調(diào)用這兩個(gè)方法铸本。注意肮雨,這里完全是由活動來決定服務(wù)何時(shí)停止的,如果沒有點(diǎn)擊Stop Service
按鈕归敬,服務(wù)就會一直處于運(yùn)行狀態(tài)酷含。那服務(wù)有沒有什么辦法讓自己停止下來呢鄙早?當(dāng)然可以,只需要在MyService
的任何一個(gè)位置調(diào)用stopSelf()
方法就能讓這個(gè)服務(wù)停止下來了椅亚。
onCreate()
方法是在服務(wù)第一次創(chuàng)建的時(shí)候調(diào)用的限番,而onStartCommand()
方法則在每次啟動服務(wù)的時(shí)候都會調(diào)用,由于剛才我們是第一次點(diǎn)擊Start Service
按鈕呀舔,服務(wù)此時(shí)還未創(chuàng)建過所以兩個(gè)方法都會執(zhí)行弥虐,之后如果你再連續(xù)多點(diǎn)擊幾次Start Service
按鈕,你就會發(fā)現(xiàn)只有onStartCommand()
方法可以得到執(zhí)行了媚赖。
10.3.3 活動和服務(wù)進(jìn)行通信
上一節(jié)中雖然服務(wù)是在活動里啟動的霜瘪,但在啟動了服務(wù)之后,活動和服務(wù)基本就沒有什么關(guān)系了惧磺。
服務(wù)會一直處于運(yùn)行狀態(tài)颖对,但具體是運(yùn)行的什么邏輯,活動就控制不了了磨隘。這就類似于活動通知了服務(wù)一下:“你可以啟動了”缤底,然后服務(wù)就去忙自己的事情了,但活動并不知道服務(wù)到底去做了什么事情番捂,以及完成的如何个唧。
創(chuàng)建一個(gè)專門的Binder
對象來對下載功能進(jìn)行管理。
public class MyService extends Service {
private static final String TAG = "MyService";
······
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d(TAG, "startDownload executed");
}
public int getProgress() {
Log.d(TAG, "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
我們新建了一個(gè)DownloadBinder
類设预,并讓它繼承自Binder
徙歼,然后在它的內(nèi)部提供了開始下載以及查看下載進(jìn)度的方法。當(dāng)然這只是兩個(gè)模擬方法鳖枕,并沒有實(shí)現(xiàn)真正的功能魄梯,我們在這兩個(gè)方法中分別打印了一行日志。
當(dāng)一個(gè)活動和服務(wù)綁定之后耕魄,就可以調(diào)用該服務(wù)里的Binder
提供的方法了画恰。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button startService,stopService;
private Button bindService,unbindService;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name)
{
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService = (Button) findViewById(R.id.start_service);
stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
bindService = (Button) findViewById(R.id.bind_service);
unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.start_service:
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);//啟動服務(wù)
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);//停止服務(wù)
break;
case R.id.bind_service:
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);//綁定服務(wù)
break;
case R.id.unbind_service:
unbindService(connection);//解綁服務(wù)
break;
default:
break;
}
}
}
我們首先創(chuàng)建了一個(gè)ServiceConnection
的匿名類,在里面重寫了onServiceConnected()
方法和onServiceDisconnected()
方法吸奴,這兩個(gè)方法分別會在活動與服務(wù)成功綁定以及解除綁定的時(shí)候調(diào)用。在onServiceConnected()
方法中缠局,我們又通過向下轉(zhuǎn)型得到了DownloadBinder
的實(shí)例则奥,有了這個(gè)實(shí)例,活動和服務(wù)之間的關(guān)系就變得非常緊密了∠猎埃現(xiàn)在我們可以在活動中根據(jù)具體的場景來調(diào)用DownloadBinder
中的任何public
方法读处,即實(shí)現(xiàn)了指揮服務(wù)干什么服務(wù)就去干什么的功能。
我們這里仍然是構(gòu)建出了一個(gè)Intent
對象唱矛,然后調(diào)用bindService()
方法將MainActivity
和MyService
進(jìn)行綁定罚舱。bindService()
方法接收三個(gè)參數(shù)井辜,第一個(gè)參數(shù)就是剛剛構(gòu)建出的Intent
對象,第二個(gè)參數(shù)是前面創(chuàng)建出的ServiceConnection
的實(shí)例管闷,第三個(gè)參數(shù)則是一個(gè)標(biāo)志位粥脚,這里傳入BIND_AUTO_CREATE
表示在活動和服務(wù)進(jìn)行綁定后自動創(chuàng)建服務(wù)。這會使得MyService
中的onCreate()
方法得到執(zhí)行包个,但是onStartCommand()
方法不會執(zhí)行刷允。
解除服務(wù)和活動之間的綁定,調(diào)用一下unbindService()
方法就可以了碧囊。
另外需要注意树灶,任何一個(gè)服務(wù)在整個(gè)應(yīng)用程序范圍內(nèi)都是通用的,即MyService
不僅可以和MainActivity
綁定糯而,還可以和任何一個(gè)其他的活動進(jìn)行綁定天通,而且在綁定完成后他們都可以獲取到相同的DownloadBinder
實(shí)例。
10.4 服務(wù)的生命周期
服務(wù)有自己的生命周期熄驼。
一旦在項(xiàng)目的任何位置調(diào)用了Context
的startService()
方法土砂,相應(yīng)的服務(wù)就會啟動起來,并回調(diào)onStartCommand()
方法谜洽。如果這個(gè)服務(wù)之前還沒有創(chuàng)建過萝映,onStart()
方法會先于onStartCommand()
方法執(zhí)行。服務(wù)啟動了之后會一直保持運(yùn)行狀態(tài)阐虚,直到stopService()
或stopSelf()
方法被調(diào)用序臂。注意,雖然每調(diào)用一次startService()
方法实束,onStartCommand()
就會執(zhí)行一次奥秆,但實(shí)際上每個(gè)服務(wù)都只會存在一個(gè)實(shí)例。所以不管你調(diào)用了多少次startService()
方法咸灿,只需調(diào)用一次stopService()
或stopSelf()
方法构订,服務(wù)就會停止下來了。
另外避矢,還可以調(diào)用Context
的bindService()
來獲取一個(gè)服務(wù)的持久連接悼瘾,這時(shí)就會回調(diào)服務(wù)中的onBind()
方法。類似里审胸,如果這個(gè)服務(wù)之前還沒有創(chuàng)建過亥宿,onCreate()
方法會先于onBind()
方法執(zhí)行。之后砂沛,調(diào)用方可以獲取到onBind()
方法里返回的IBinder
對象的實(shí)例烫扼,這樣就能自由地和服務(wù)進(jìn)行通信了。只要調(diào)用方和服務(wù)之間的連接沒有斷開碍庵,服務(wù)就會一直保持運(yùn)行狀態(tài)映企。
當(dāng)調(diào)用了startService()
方法后悟狱,又去調(diào)用stopService()
方法,這時(shí)服務(wù)中的onDestroy()
方法就會執(zhí)行堰氓,表示服務(wù)已經(jīng)銷毀了挤渐。類似地,當(dāng)調(diào)用了bindService()
方法后豆赏,又去調(diào)用unbindService()
方法挣菲,onDestroy()
方法也會執(zhí)行。這兩種情況都很好理解掷邦。但是需要注意白胀,我們是完全有可能對一個(gè)服務(wù)即調(diào)用了startService()
方法,又調(diào)用了bindService()
方法抚岗,這種下情況該如何讓服務(wù)銷毀呢或杠?根據(jù)Android
系統(tǒng)的機(jī)制,一個(gè)服務(wù)只要被啟動或者被綁定了之后宣蔚,就會一直處于運(yùn)行狀態(tài)向抢,必須要讓以上兩種條件同時(shí)不滿足,服務(wù)才能被銷毀胚委。所以挟鸠,這種情況下要同時(shí)調(diào)用stopService()
和unbindService()
方法,onDestroy()
方法才能執(zhí)行亩冬。
這樣你就已經(jīng)把服務(wù)的生命周期完整里走了一遍艘希。
10.5 服務(wù)的更多技巧
10.5.1 使用前臺服務(wù)
服務(wù)幾乎都是在后臺運(yùn)行的,一直以來它都是默默地做著辛苦的工作硅急。但是服務(wù)的系統(tǒng)優(yōu)先級還是比較低的覆享,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的情況時(shí),就有可能回收掉正在后臺運(yùn)行的服務(wù)营袜。如果你希望服務(wù)可以一直保持運(yùn)行狀態(tài)撒顿,而不會由于系統(tǒng)內(nèi)存不足的原因?qū)е卤换厥眨涂梢钥紤]使用前臺服務(wù)荚板。
前臺服務(wù)和普通服務(wù)最大的區(qū)別就在于凤壁,它會有一個(gè)正在運(yùn)行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示,下拉狀態(tài)欄后可以看到更加詳細(xì)的信息啸驯,非常類似于通知的效果客扎。當(dāng)然有時(shí)候你也可能不僅僅是為了防止服務(wù)被回收掉才使用前臺服務(wù)的,有些項(xiàng)目由于特殊的需求會要求必須使用前臺服務(wù)罚斗,比如說彩云天氣這款天氣預(yù)報(bào)應(yīng)用,它的服務(wù)在后臺更新天氣數(shù)據(jù)的同時(shí)宅楞,還會在系統(tǒng)狀態(tài)欄一直顯示當(dāng)前的天氣信息针姿。
public class MyService extends Service {
······
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate executed");
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new Notification.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pendingIntent)
.build();
startForeground(1,notification);
}
``````
}
這次在構(gòu)建出Notification
對象后并沒有使用NotificationManager
來將通知顯示出來袱吆,而是調(diào)用了startForeground()
方法。這個(gè)方法接收兩個(gè)參數(shù)距淫,第一個(gè)參數(shù)是通知的id
,類似于notify()
方法的第一個(gè)參數(shù)绞绒,第二個(gè)參數(shù)則是構(gòu)建出的Notification
對象。調(diào)用startForeground()
方法后就會讓MyService
變成一個(gè)前臺服務(wù)榕暇,并在系統(tǒng)狀態(tài)顯示出來蓬衡。
10.5.2 使用IntentService
服務(wù)中的代碼都是默認(rèn)運(yùn)行在主線程當(dāng)中的,如果直接在服務(wù)里去處理一些耗時(shí)的邏輯彤枢,就很容易出現(xiàn)
ANR(Application Not Responding)
的情況狰晚。
所以這個(gè)時(shí)候就需要用到Android
多線程編程的技術(shù)了,我們應(yīng)該在服務(wù)的每個(gè)具體的方法里開啟一個(gè)子線程缴啡,然后在這里去處理那些耗時(shí)的邏輯壁晒。因此,一個(gè)比較標(biāo)準(zhǔn)的服務(wù)就可以寫成如下形式:
public class MyService extends Service {
······
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//處理具體的邏輯
}
}).start();
return super.onStartCommand(intent,flags,startId);
}
}
但是业栅,這個(gè)服務(wù)一旦啟動之后秒咐,就會一直處于運(yùn)行狀態(tài),必須調(diào)用stopService()
或者stopSelf()
方法才能讓服務(wù)停止下來碘裕。所以携取,如果想要實(shí)現(xiàn)讓一個(gè)服務(wù)在執(zhí)行完畢后自動停止的功能,就可以這樣寫:
public class MyService extends Service {
······
@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);
}
}
為了可以簡單里創(chuàng)建一個(gè)異步的帮孔,會自動停止的服務(wù)雷滋,Android
專門提供了一個(gè)IntentService
類。
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");//調(diào)用父類的有參構(gòu)造函數(shù)
}
@Override
protected void onHandleIntent(Intent intent) {
//打印當(dāng)前線程的id
Log.d(TAG, "Thread id is " +Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy executed");
}
}
這里首先要提供一個(gè)無參的構(gòu)造函數(shù)你弦,并且必須在其內(nèi)部調(diào)用父類的有參構(gòu)造函數(shù)惊豺。然后要在子類中去實(shí)現(xiàn)onHandleIntent()
這個(gè)抽象方法,在這個(gè)方法中可以去處理一些具體的邏輯禽作,而且不用擔(dān)心 ANR
的問題尸昧,因?yàn)檫@個(gè)方法已經(jīng)是在子線程中運(yùn)行的了。這里為了證實(shí)一下旷偿,我們在onHandleIntent()
方法中打印了當(dāng)前線程的id
烹俗。另外根據(jù)IntentService
的特性,這個(gè)服務(wù)在運(yùn)行結(jié)束后應(yīng)該是會自動停止的萍程,所以我們又重寫了onDestroy()
方法幢妄,在這里也打印了一行日志,以證實(shí)服務(wù)是不是停止掉了茫负。
case R.id.start_intent_service:
//打印主線程id
Log.d(TAG, "Thread id is " +Thread.currentThread().getId());
Intent intentService = new Intent(this,MyIntentService.class);
startService(intentService);
break;
在按鈕的點(diǎn)擊事件里面去啟動MyIntentService
這個(gè)服務(wù)蕉鸳,并在這里打印了一下主線程的id
,稍后用于和IntentService
進(jìn)行對比。你會發(fā)現(xiàn)潮尝,其實(shí)IntentService
的用法和普通的服務(wù)沒什么兩樣榕吼。
最后不要忘記,服務(wù)都是需要在AndroidManifest.xml
里注冊的勉失。
<service android:name=".MyIntentService"/>
第一部分
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
private Button startService,pauseService,cancelService;
private DownloadService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
downloadBinder = (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name)
{
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService = (Button) findViewById(R.id.start_download);
pauseService = (Button) findViewById(R.id.pause_download);
cancelService = (Button) findViewById(R.id.cancel_download);
startService.setOnClickListener(this);
pauseService.setOnClickListener(this);
cancelService.setOnClickListener(this);
Intent intent = new Intent(this,DownloadService.class);
startService(intent);//啟動服務(wù)
bindService(intent,connection,BIND_AUTO_CREATE);//綁定服務(wù)
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission
.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission
.WRITE_EXTERNAL_STORAGE},1);
}
}
@Override
public void onClick(View v)
{
if (downloadBinder == null)
{
return;
}
switch (v.getId())
{
case R.id.start_download:
String url = "https://raw.githubusercontent.com/guolindev/eclipse/" +
"master/eclipse-inst-win64.exe";
downloadBinder.startDownload(url);
break;
case R.id.pause_download:
downloadBinder.pauseDownload();
break;
case R.id.cancel_download:
downloadBinder.cancelDownload();
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[]
permissions, @NonNull int[] grantResults)
{
switch (requestCode)
{
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED)
{
Toast.makeText(MainActivity.this, "拒絕權(quán)限將無法使用程序!",
Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
@Override
protected void onDestroy()
{
super.onDestroy();
unbindService(connection);
}
}
這里我們首先創(chuàng)建一個(gè)ServiceConnection的匿名類羹蚣,然后在onServiceConnected()方法中獲取到DownloadBinder的實(shí)例,有了這個(gè)實(shí)例乱凿,我們就可以在活動中調(diào)用服務(wù)提供的各種方法了顽素。
在onCreate()方法中,對按鈕進(jìn)行了初始化操作并設(shè)置了點(diǎn)擊事件徒蟆,然后分別調(diào)用了startService()和bindService()方法來啟動和綁定服務(wù)胁出。這一點(diǎn)至關(guān)重要,因?yàn)閱臃?wù)可以保證DownloadService一直在后臺運(yùn)行后专,綁定服務(wù)則可以讓MainActivity和DownloadService進(jìn)行通信划鸽,因此兩個(gè)方法調(diào)用都必不可少。在onCreate()方法的最后戚哎,我們還進(jìn)行了WRITE_EXTERNAL_STORAGE的運(yùn)行時(shí)權(quán)限申請裸诽,因?yàn)橄螺d文件是要下載到SD卡的Download目錄下的,如果沒有這個(gè)權(quán)限的話型凳,我們整個(gè)程序就都無法正常工作丈冬。
在onClick()方法中我們對點(diǎn)擊事件進(jìn)行判斷,如果點(diǎn)擊了開始按鈕就調(diào)用DownloadBinder的startDownload()方法甘畅,如果點(diǎn)擊了暫停按鈕就調(diào)用pauseDownload()方法埂蕊,如果點(diǎn)擊了取消按鈕就調(diào)用cancelDownload()方法。startDownload()方法中你可以傳入任意的下載地址疏唾。
另外還有一點(diǎn)需要注意蓄氧,如果活動被銷毀了,那么一定要記得對服務(wù)進(jìn)行解綁槐脏,不然就有可能會造成內(nèi)存泄露喉童。
第二部分
public class DownloadService extends Service
{
private DownloadTask downloadTask;
private String downloadUrl;
private DownloadListener listener = new DownloadListener()
{
@Override
public void onProgress(int progress)
{
getNotificationManager().notify(1,getNotification("Downloading...",progress));
}
@Override
public void onSuccess()
{
downloadTask = null;
//下載成功時(shí)將前臺服務(wù)通知關(guān)閉,并創(chuàng)建一個(gè)下載成功的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Downloading Success",-1));
Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed()
{
downloadTask = null;
//下載失敗時(shí)將前臺服務(wù)通知關(guān)閉顿天,并創(chuàng)建一個(gè)下載失敗的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Failed",-1));
Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
}
@Override
public void onPaused()
{
downloadTask = null;
Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled()
{
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
}
};
private DownloadBinder mBinder = new DownloadBinder();
@Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
class DownloadBinder extends Binder
{
public void startDownload(String url)
{
if (downloadTask == null)
{
downloadUrl = url;
downloadTask = new DownloadTask(listener);
downloadTask.execute(downloadUrl);
startForeground(1,getNotification("Downloading...",0));
Toast.makeText(DownloadService.this, "Downloading...",
Toast.LENGTH_SHORT).show();
}
}
public void pauseDownload()
{
if (downloadTask != null)
{
downloadTask.pauseDownload();
}
}
public void cancelDownload()
{
if (downloadTask != null)
{
downloadTask.cancelDownload();
}
else
{
if (downloadUrl != null)
{
String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment
.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + filename);
if (file.exists())
{
file.delete();
}
getNotificationManager().cancel(1);
stopForeground(true);
Toast.makeText(DownloadService.this, "canceled", Toast.LENGTH_SHORT).show();
}
}
}
}
private NotificationManager getNotificationManager()
{
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
return manager;
}
private Notification getNotification(String title,int progress)
{
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher));
builder.setContentIntent(pendingIntent);
builder.setContentTitle(title);
if (progress > 0)
{
//當(dāng)progress大于或等于0時(shí)才需顯示下載進(jìn)度
builder.setContentText(progress + "%");
builder.setProgress(100,progress,false);
}
return builder.build();
}
}
首先這里創(chuàng)建了一個(gè)DownloadListener的匿名類實(shí)例堂氯,并在匿名類中實(shí)現(xiàn)了onProgress(),onSuccess(),onFailed(),onPaused()和onCanceled()這5個(gè)方法。在onProgress()方法中牌废,我們調(diào)用getNotification()構(gòu)建了一個(gè)用于顯示下載進(jìn)度的通知咽白,然后調(diào)用NotificationManager的notify()方法去觸發(fā)這個(gè)通知,這樣就可以在下拉狀態(tài)欄中實(shí)時(shí)看到當(dāng)前的下載進(jìn)度了鸟缕。在onSuccess()方法中晶框,我們首先是將正在下載的前臺通知關(guān)掉,然后創(chuàng)建了一個(gè)新的通知用于告訴用戶下載成功了。
接下來為了讓DownloadService可以和活動進(jìn)行通信三妈,我們又創(chuàng)建了一個(gè)DownloadBinder畜埋。DownloadBinder中提供了startDownload(),pauseDownload()和cancelDownload()這三個(gè)方法莫绣,在startDownload()方法中畴蒲,我們創(chuàng)建了一個(gè)DownloadTask的實(shí)例,把剛才的DownloadListener作為參數(shù)傳入对室,然后調(diào)用execute()方法開啟下載模燥,并將下載文件的URL地址傳入到execute()方法中。同時(shí)為了讓這個(gè)下載服務(wù)成為一個(gè)前臺服務(wù)掩宜,我們還調(diào)用了startForeground()方法蔫骂,這樣就會在系統(tǒng)狀態(tài)欄中創(chuàng)建一個(gè)持續(xù)運(yùn)行的通知了。
pausedDownload()方法中的代碼就非常簡單了牺汤,就是簡單地調(diào)用了一下DownloadTask中的pauseDownload()方法辽旋。cancelDownload()方法中的邏輯也基本類似,但是要注意檐迟,取消下載的時(shí)候我們需要將正在下載的文件刪除掉补胚,這一點(diǎn)和暫停下載時(shí)不同的。
DownloadService類中所有使用到的通知都是調(diào)用getNotification()方法進(jìn)行構(gòu)建的追迟,這個(gè)方法中的代碼我們我們之前基本都是學(xué)過的溶其,只有一個(gè)setProgress()方法沒有見過。
setProgress()方法接收3個(gè)參數(shù)敦间,第一個(gè)參數(shù)傳入通知的最大進(jìn)度瓶逃,第二個(gè)參數(shù)傳入通知的當(dāng)前進(jìn)度,第三個(gè)參數(shù)表示是否使用模糊進(jìn)度條廓块,這里傳入false厢绝。設(shè)置完setProgress()方法,通知上就會有進(jìn)度條顯示出來了带猴。
第三部分
public class DownloadTask extends AsyncTask<String,Integer,Integer>
{
public static final int TYPE_SUCCESS = 0;
public static final int TYPE_FAILED = 1;
public static final int TYPE_PAUSED = 2;
public static final int TYPE_CANCELED = 3;
private DownloadListener listener;
private boolean isCanceled = false;
private boolean isPaused = false;
private int lastProgress;
public DownloadTask(DownloadListener listener)
{
this.listener = listener;
}
@Override
protected Integer doInBackground(String... params)
{
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try
{
long downloadLength = 0;
String downloadUrl = params[0];
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getPath();
file = new File(directory + fileName);
if (file.exists())
{
downloadLength = file.length();
}
long contentLength = getContentLength(downloadUrl);
if (contentLength == 0)
{
return TYPE_FAILED;
}
else if (contentLength == downloadLength)
{
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//斷點(diǎn)下載昔汉,指定從哪個(gè)字節(jié)開始下載
.addHeader("RANGE","bytes = " + downloadLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null)
{
is = response.body().byteStream();
savedFile = new RandomAccessFile(file,"rw");
savedFile.seek(downloadLength);//跳過已下載的字節(jié)
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1)
{
if (isCanceled)
{
return TYPE_CANCELED;
}
else if (isPaused)
{
return TYPE_PAUSED;
}
else
{
total += len;
savedFile.write(b,0,len);
int progress = (int) ((total + downloadLength) * 100 / contentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}
} catch (Exception e)
{
e.printStackTrace();
} finally
{
try
{
if (is != null)
{
is.close();
}
if (savedFile != null)
{
savedFile.close();
}
if (isCanceled && file != null)
{
file.delete();
}
} catch (Exception e)
{
e.printStackTrace();
}
}
return TYPE_FAILED;
}
@Override
protected void onProgressUpdate(Integer... values)
{
int progress = values[0];
if (progress > lastProgress)
{
listener.onProgress(progress);
lastProgress = progress;
}
}
@Override
protected void onPostExecute(Integer integer)
{
switch (integer)
{
case TYPE_SUCCESS:
listener.onSuccess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_PAUSED:
listener.onPaused();
break;
case TYPE_CANCELED:
listener.onCanceled();
break;
}
}
public void pauseDownload()
{
isPaused = true;
}
public void cancelDownload()
{
isCanceled = true;
}
private long getContentLength(String downloadUrl) throws IOException
{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful())
{
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
}
首先看一下AsyncTask的3個(gè)泛型參數(shù):第一個(gè)泛型參數(shù)指定為String,表示在執(zhí)行AsyncTask的時(shí)候需要傳入一個(gè)字符串參數(shù)給后臺任務(wù)浓利;第二個(gè)泛型參數(shù)指定為Integer挤庇,表示使用整形數(shù)據(jù)來作為進(jìn)度顯示單位;第三個(gè)泛型參數(shù)指定為Integer,則表示使用整形數(shù)據(jù)來反饋執(zhí)行結(jié)果贷掖。
接下來我們定義了4個(gè)整形常量用于表示下載的狀態(tài)嫡秕,TYPE_SUCCESS表示下載成功,TYEP_FAILED表示下載失敗苹威,TYEP_PAUSED表示暫停下載昆咽,TYPE_CANCELED表示取消下載。然后在DownloadTask的構(gòu)造函數(shù)中要求傳入一個(gè)剛剛定義的DownloadListener參數(shù),我們待會就會將下載的狀態(tài)通過這個(gè)參數(shù)進(jìn)行回調(diào)掷酗。
接著就是重寫doInBackground(),onProgressUpdate()和onPostExecute()這三個(gè)方法了调违,
doInBackground():用于在后臺執(zhí)行具體的下載邏輯
onProgressUpdate():用于在界面上更新當(dāng)前的下載進(jìn)度
onPostExecute():通知最終的下載結(jié)果。
先來看一下doInBackground()方法泻轰,首先我們從參數(shù)中獲取到了下載的URL地址技肩,并根據(jù)URL地址解析出了下載的文件名,然后指定將文件下載到Environment.DIRECTORY_DOWNLOADS目錄下浮声,也就是SD卡的Download目錄虚婿。我們還要判斷一下Download 目錄中是不是已經(jīng)存在要下載的文件了,如果已經(jīng)存在的話則讀取已下載的字節(jié)數(shù)泳挥,這樣就可以在后面啟用斷點(diǎn)續(xù)傳的功能了然痊。接下來先是調(diào)用getContentLength()方法來獲取待下載文件的總長度,如果文件長度等于0則說明文件有問題屉符,直接返回TYPE_FAILED,如果文件長度等于已下載文件長度剧浸,那么就說明已經(jīng)下載完了,直接返回TYPE_SUCCESS即可矗钟。緊接著使用OKhttp來發(fā)送一條網(wǎng)絡(luò)請求唆香,需要注意的是,這里在請求中加入了一個(gè)header真仲,用于告訴服務(wù)器我們想要從哪個(gè)字節(jié)下載袋马,因?yàn)橐严螺d過的部分就不要重新下載了。接下來讀取服務(wù)器響應(yīng)的數(shù)據(jù)秸应,并使用Java的文件流方式虑凛,不斷從網(wǎng)絡(luò)上讀取數(shù)據(jù),不斷寫入到本地软啼,一直到文件下載完成為止桑谍。在這個(gè)過程中,我們還要判斷用戶有沒有觸發(fā)暫突雠玻或者取消的操作锣披,如果有的話則返回TYPE_PAUSED或TYPE_CANCELED來中斷下載,如果沒有的話則實(shí)時(shí)計(jì)算當(dāng)前的下載進(jìn)度贿条,然后調(diào)用publishProgress()方法進(jìn)行通知雹仿。暫停和取消操作都是使用一個(gè)布爾型的變量來進(jìn)行控制的,調(diào)用pauseDownload()或cancelDownload()方法即可更改變量的值整以。
接下來看一下onProgressUpdate()方法胧辽,這個(gè)方法就簡單的多了,它首先從參數(shù)中獲取到當(dāng)前的下載進(jìn)度公黑,然后和上一次的下載進(jìn)度對比邑商,如果有變化的話則調(diào)用DownloadListener的onProgress()方法來通知下載進(jìn)度更新摄咆。
最后是onPostExecute()方法,也非常簡單人断,就是根據(jù)參數(shù)中傳入的下載狀態(tài)來進(jìn)行回調(diào)吭从,下載成功就調(diào)用DownloadListener的onSuccess()方法,下載失敗就調(diào)用onFailed()方法恶迈,暫停下載就調(diào)用onPaused()方法涩金,取消下載就調(diào)用onCeaceled()方法。