====================================
====== 第十章:后臺默默的勞動者 — 探究服務(wù) ======
====================================
10.1 服務(wù)是什么
服務(wù)Service是Android中實現(xiàn)后臺程序運行的解決方案樱溉。
需要注意挤安,服務(wù)并不是運行在一個獨立的進(jìn)程當(dāng)中搜吧,而是依賴于創(chuàng)建服務(wù)時所在的應(yīng)用程序進(jìn)程肛冶。當(dāng)某個應(yīng)用程序進(jìn)程被殺掉時街氢,所有依賴于該進(jìn)程的服務(wù)也會停止運行。
實際上珊肃,服務(wù)并沒有自動開啟線程,所有的代碼都是默認(rèn)運行在主線中當(dāng)中嘶摊。也就是說叶堆,我們需要在服務(wù)的內(nèi)部手動創(chuàng)建子線程斥杜,并在這里執(zhí)行具體的任務(wù)蔗喂,否則就有可能出現(xiàn)主線程被阻塞的情況缰儿。
10.2 Android多線程編程乖阵。
10.2.1 線程的基本用法
1、定義一個線程只需要新建一個類繼承自Thread儒将,然后重寫父類的run()方法钩蚊,并在run方法里面執(zhí)行耗時邏輯即可
class MyThread extends Thread {
@Override
public void run() {
// 處理具體邏輯
}
}
啟動線程:new MyThread().start();
2砰逻、由于使用集成的方式來創(chuàng)建線程耦合性有點高诱渤,更多的時候我們會選擇使用Runnable接口的方式來定義一個線程:
class MyThread implements Runnable {
@Override
public void run() {
// 處理具體的邏輯
}
}
啟動線程方法:
MyThread myThread = new MyThread();
new Thread(myThread).start(); // Thread的構(gòu)造函數(shù)接收一個Runnable參數(shù)谈况,我們MyThread正是一個實現(xiàn)了Runnable接口的對象递胧。這樣缎脾,run()方法中的代碼就會在子線程中運行了。
也可以這樣寫:
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
}
}).start();
10.2.2 在子線程中更新UI
新建一個AndroidThreadTest項目:
1联喘、修改activity_main.xml
<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<Button
android:id=“@+id/change_text”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Change Text” />
<TextView
android:id=“@+id/text”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerInParent=“true”
android:text=“Hello World”
android:textSize=“20sp” />
</RelativeLayout>
2叭喜、修改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) {
swith (v.getId()) {
case R.id.change_text : {
new Thread(new Runnable() {
@Override
public void run() {
text.setText(“Nice to meet you”);
}
}).start();
default:
break;
}
}
}
}
我們運行一下捂蕴,發(fā)現(xiàn)崩潰了啥辨。因為UI操作必須要在主線程8戎腕够!
對于這種情況燕少,Android提供了一套異步消息處理機(jī)制:
修改MainActivity代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int UPDATE_TEXT = 1; // 用于表示更新TextView這個動作
private TextView text;
private Handle handler = new Handler() {
public void handlerMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在這里可以進(jìn)行UI操作
text.setText(“Nice to meet you”);
break;
default:
break;
}
});
…
@Override
public void onClick(View v) {
switch (v.getId()) {
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 將Message對象發(fā)送出去
}
}).start();
break;
default:
break;
}
}
}
10.2.3 解析異步消息處理機(jī)制
Android中的異步消息處理主要由四個部分組成:Message崇决、Handler恒傻、MessageQueue建邓、Looper
1官边、Message:
Message是在線程之間傳遞的消息,可以攜帶少量信息贫悄,除了what字段,還可以使用arg1、arg2攜帶整形數(shù)據(jù)山上,使用obj字段攜帶Object對象
2胶哲、Handler
Handler是處理者的意思鸯屿,主要用于發(fā)送和處理消息寄摆。發(fā)送信息使用sendMessage()方法婶恼,發(fā)出的消息經(jīng)過一些列輾轉(zhuǎn)之后勾邦,最終會傳遞到handleMessage()方法中眷篇。
3蕉饼、MessageQueue
MessageQueue是消息隊列的意思昧港,主要用于存放所有通過Handler發(fā)送的消息支子,這部分消息一直存在于消息隊列中,等待被處理叹侄。每個線程都只會有一個MessageQueue對象圈膏。
4、Looper
Looper是每個線程中的MessageQueue的管家糯俗,調(diào)用了Looper的loop()方法后得湘,就會進(jìn)入到一個無線循環(huán)當(dāng)中淘正,當(dāng)發(fā)現(xiàn)MessageQueue中存在一條消息鸿吆,就會將它取出惩淳,并傳遞到Handler的handleMessage()方法中思犁。每個線程都只會有一個Looper對象激蹲。
原理:我們在主線程創(chuàng)建了一個Handler對象学辱,并重寫了handlerMessage()方法项郊。然后當(dāng)子線程中需要進(jìn)行UI操作時着降,就創(chuàng)建一個Message對象任洞,并通過Handler這條消息發(fā)送出去交掏。之后這條消息會被添加到MessageQueue的隊列中等待被處理盅弛,而Looper則會一直嘗試從MessageQueue中取出待處理的消息,最后分發(fā)回Handler的handleMessage()方法中愉烙,由于Handler是在主線程中創(chuàng)建的步责,所以此時handlerMessage()方法中的代碼也會在主線程中運行蔓肯。
原理圖如p346
10.2.4 使用AsyncTask
AsyncTask可以十分簡單的從子線程切換到主線程省核。當(dāng)然气忠,AsyncTask背后的實現(xiàn)原理也是基于異步消息處理機(jī)制的旧噪,只不過Android幫我們做了很好的封裝淘钟。
AsyncTask是一個抽象類米母,我們想使用它铁瞒,就必須要創(chuàng)建一個類繼承它慧耍。繼承時我們可以為AsyncTask執(zhí)行三個泛型參數(shù):
1芍碧、Params:在執(zhí)行AsyncTask時需要傳入的參數(shù)泌豆,可用于在后臺任務(wù)中使用
2踪危、Progress:后臺任務(wù)執(zhí)行時陨倡,如果需要在界面上顯示當(dāng)前進(jìn)度兴革,則使用這里的泛型作為進(jìn)度單位
3杂曲、Result:當(dāng)任務(wù)執(zhí)行完畢時,如果需要對結(jié)果進(jìn)行返回棚饵,這里指定的泛型作為返回值類型
因此噪漾,一個簡單的自定義AsyncTask可以寫成如下:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
…
}
第一泛型參數(shù)指定為Void欣硼,表示執(zhí)行AsyncTask時候不需要傳入?yún)?shù)給后臺任務(wù)
第二泛型參數(shù)為Integer诈胜,表示使用整形數(shù)據(jù)作為進(jìn)度顯示單位
第三泛型參數(shù)為Boolean焦匈,表示使用布爾類型數(shù)據(jù)來反饋結(jié)果括授。
我們還需要重寫AsyncTask的幾個方法才能完成對任務(wù)的定制:需要重寫的方法有四個:
1荚虚、onPreExecute()
開始執(zhí)行前調(diào)用版述,用于進(jìn)行一些界面上的初始化操作晚伙,比如顯示一個進(jìn)度條對話框等
2咆疗、doInBackground(Params…)
這個方法中的所有代碼都會在子線程中執(zhí)行午磁,我們應(yīng)該在這里處理所有的耗時操作迅皇。
3登颓、onProgerssUpdate(Progress…)
當(dāng)在后臺任務(wù)doInBackground方法中調(diào)用了publishProgress(Progress…)方法后框咙,onProgressUpdate(Progress…)方法很快會被調(diào)用喇嘱,該方法中攜帶的參數(shù)就是在后臺任務(wù)重傳遞過來的婉称。在這個方法可以進(jìn)行UI操作。
4俗壹、onPostExecute(Result)
當(dāng)在后臺任務(wù)執(zhí)行完畢(onPreExecute())方法通過return語句進(jìn)行返回時绷雏,這個方法會被調(diào)用涎显。返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中期吓√智冢可以利用返回的數(shù)據(jù)進(jìn)行一些UI操作谱姓。
以下是一個比較完整的自定義AsyncTask:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show(); // 顯示進(jìn)度對話框
}
@Override
protected Boolean doInBackground(Void…params) {
try {
while(true) {
int downloadPercent = doDownload(); // 這是一個虛構(gòu)的方法
// 傳遞進(jìn)度條
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
// 當(dāng)執(zhí)行到return屉来,onPostExecute()方法會被調(diào)用
return false;
}
// 當(dāng)執(zhí)行到return奶躯,onPostExecute()方法會被調(diào)用
return true;
}
@Override
protected void onProgressUpdate(Integer… values) {
// 在這里更新下載進(jìn)度
progerssDialog.setMessage(“Downloaded ” + values[0] + “%”);
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); // 關(guān)閉進(jìn)度對話框
// 在這里提示下載結(jié)果
if (result) {
Toast.makeText(context, “Download succeeded”, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, “Download failed”, Toast.LENGTH_SHORT).show();
}
}
}
想要啟動這個任務(wù)账嚎,只需要 new DownloadTask().execute()即可郭蕉。
方便了很多旁振,我們并不需要去考慮什么異步消息處理機(jī)制拐袜,也不需要專門使用一個Handler來發(fā)送和接收消息蹬铺,只需要調(diào)用一下publishProgerss()方法甜攀,就可以輕松從子線程切換到UI線程。
10.3 服務(wù)的基本用法:
10.3.1 定義一個服務(wù):
新建一個項目ServiceTest
右鍵com.example.servicetest -> New -> Service -> Service谁撼,命名為MyService彤敛,Enable和Exported都勾選
Enable表示其否啟用
Exported表示是否允許除了當(dāng)前程序之外的其他程序訪問此服務(wù)
然后觀察MyService的代碼
public class MyService extends Service {
public MyService() {
}
// 此方法為抽象方法墨榄,必須要在子類中實現(xiàn)(下一小節(jié)再講解)
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportOperationException(“Not yet implement”);
}
// 現(xiàn)在重寫Service的另外一些方法
// 服務(wù)創(chuàng)建的時候調(diào)用
@Override
public void onCreate() {
super.onCreate();
}
// 每次服務(wù)啟動的時候調(diào)用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
Android四大組件的特點阵翎,每一個服務(wù)也都需要在AndroidManifest.xml文件中注冊才能生效郭卫。但是Android Studio已經(jīng)幫我們完成了這個操作。
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.servicetest” >
<application
android:allowBackup=“true”
android:icon=“@mipmap/ic_launcher”
android:label=“@string/app_name”
android:supportsRtl=“true”
android:theme=“@style/AppTheme” >
…
<service
android:name=“.MyService”
android:enabled=“true”
android:exported=“true” >
</service>
</application>
</manifest>
10.3.2 啟動和停止服務(wù):
啟動和停止服務(wù)主要是借助Intent來實現(xiàn):
1词疼、修改activity_main.xml代碼
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<Button
android:id=“@+id/start_service“
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Start Service” />
<Button
android:id=“@+id/stop_service”
android:layout_width=“match_parent”
androd:layout_height=“wrap_content”
android:text=“Stop Service” />
</LinearLayout>
2、修改MainActivity代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedIntanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.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);
break;
default:
break;
}
}
}
startService()方法和stopService()方法都是定義在Context類中的。
主要在MyService的任何一個位置調(diào)用stopSelf()方法秽晚,就可以讓這個服務(wù)停止下來爆惧。
10.3.3 活動與服務(wù)進(jìn)行通訊
啟動了服務(wù)之后,活動與服務(wù)基本就沒什么關(guān)系了:我們在活動里調(diào)用了startService()方法來啟動MyServiece這個服務(wù)熄阻,然后MyService的onCreate()和onStartCommand()方法就會得到執(zhí)行秃殉。之后服務(wù)就會一直處于運行狀態(tài)钾军,但是具體運行的是什么邏輯吏恭,活動就控制不了了哀九。
這時候就需要用到了onBind()方法了阅束。
現(xiàn)在我們需要在活動中可以決定何時開始下載,以及隨時查看下載進(jìn)度(需要創(chuàng)建一個專門的Binder對象來對下載功能進(jìn)行管理)
public class MyService extends Service {
private DownloadBiinder extends Binder {
public void startDownload() {
Log.d(“MyService”, “startDownload executed”);
}
public int getProgerss() {
Log.d(“MyService”, “getProgerss executed”);
return 0;
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
…
}
接著界牡,在MyService中創(chuàng)建了DownloadBinder的實例,然后在onBind()方法里反悔了這個實例纳令,這樣MyService中的工作就全部完成了。
修改activity_main.xml
<LinearLayout xmlns:android:”http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_height=“match_parent”
android:layout_height=“match_parent” >
…
<Button
android:id=“@+id/bind_service”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Bind Service” />
<Button
android:id=“@+id/unbind_service”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Unbind Service” />
<LinearLayout>
上面兩個按鈕代表綁定服務(wù)和解綁服務(wù)克胳。這個動作由活動去操作平绩。當(dāng)一個活動和服務(wù)綁定了之后,就可以調(diào)用該服務(wù)里的Binder提供的方法了漠另。修改MainActivity代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
// 匿名類捏雌,創(chuàng)建一個connection
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// 當(dāng)活動與服務(wù)解綁的時候調(diào)用
}
@Override
public void onServiceConnected(ComponentName name, IBInder service) {
// 當(dāng)活動與服務(wù)綁定的時候調(diào)用
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgerss();
}
};
@Overrde
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layou.activity_main);
…
Button bindService = (Button) findViewById(R.id.bind_service);
Button unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickLisener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
…
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); // 綁定服務(wù)笆搓,MyService的onCreate方法調(diào)用
break;
case R.id.unbind_service:
unbindService(connection); // 解綁服務(wù)
break;
default:
break;
}
}
10.4 服務(wù)的生命周期:
包含onCreate()性湿、onStartCommand()、onBind()满败、onDestroy()方法。
一旦在項目任何位置調(diào)用了Context的startService()方法算墨,相應(yīng)的服務(wù)就會啟動起來。并調(diào)用服務(wù)的onStartCommand()方法报咳。如果這個服務(wù)之前沒有創(chuàng)建過侠讯,onCreate()方法會先與onStartCommand()方法執(zhí)行。服務(wù)啟動之后就會保持運行狀態(tài)暑刃,直到stopService或者stopSelf()方法被調(diào)用。注意:雖然每調(diào)用一次startService()方法袁翁,onStartCommand()就會執(zhí)行一次。但是實際上每個服務(wù)都只會存在一個實例婿脸。所以不管你調(diào)用了多少次startService()方法粱胜,只需要調(diào)用一次stopService()或stopSelf()方法狐树,服務(wù)就會停止下來。
另外抑钟,可以調(diào)用Context的bindService()來獲取一個服務(wù)的持久連接,這時就會回調(diào)服務(wù)中的onBind(0方法幻件。類似的蛔溃,如果這個服務(wù)之前還沒有創(chuàng)建過,onCreate()會吸納與onBind()方法執(zhí)行贺待。之后,調(diào)用方可以獲取到onBind()方法里返回的IBinder對象的實例秃臣,這樣就能自由地和服務(wù)進(jìn)行通訊了哪工。只要調(diào)用方和服務(wù)之間的鏈接沒有斷開,服務(wù)就會一直保持運行狀態(tài)正勒。
當(dāng)調(diào)用了startService()方法后,又調(diào)用stopService()方法祥绞,這時服務(wù)中的onDestroy()方法會執(zhí)行,表示服務(wù)已經(jīng)銷毀了蜕径。類似的,當(dāng)調(diào)用了bindService()方法后梦染,又調(diào)用了unbindService方法,onDestroy()方法也會執(zhí)行朴皆。有一種情況:我們對一個服務(wù)調(diào)用了startService(0方法帕识,又調(diào)用了bindService()方法。這種時候遂铡,按照Android系統(tǒng)的機(jī)制,一個復(fù)蘇只要被啟動或者綁定之后扒接,就會一直處于運行狀態(tài),必須要讓以上兩種條件同時不滿足碱呼,服務(wù)才能銷毀宗侦。所以,這種情況下要同時調(diào)用stopService和unbindServicee()方法凝垛,onDestroy()方法才會執(zhí)行。
10.5.1 使用前臺服務(wù):
服務(wù)幾乎都是在后臺運行的,但是服務(wù)的優(yōu)先級比較低桃焕,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足情況观堂,就有可能會收掉正在后臺運行的服務(wù)。所以我們可以考慮使用前臺服務(wù)师痕。前臺服務(wù)特點是:它會一直有一個正在運行的圖標(biāo)在系統(tǒng)的狀態(tài)欄顯示,下拉狀態(tài)欄可以看到更加詳細(xì)的信息因篇,非常類似通知。有些項目比較特殊會要求必須使用前臺服務(wù)咐吼,比如天氣預(yù)報類應(yīng)用商佑。
創(chuàng)建一個前臺服務(wù):
1锯茄、修改MyService的代碼:
public class MyService extends Service {
…
@Override
public void onCreate() {
super.onCreate();
Log.d(“MyService”, “onCreate executed”);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificatioinCompat.Builder(this)
.setContentTitle(“This is content title”)
.setContentText(“This is content text”)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmpaFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1, notification);
}
…
}
上面構(gòu)建出一個Notification對象后,并沒有使用NotificaitonManager將通知顯示出來肌幽,而是調(diào)用了startForeground()方法抓半。第一個參數(shù)類似于通知id,第二個參數(shù)則是構(gòu)建出的Notificaiton對象煮岁,調(diào)用startForeground方法會讓Service變成一個前臺服務(wù)涣易,并在系統(tǒng)狀態(tài)欄顯示出來画机。
10.5.2 使用IntentService
服務(wù)中的代碼默認(rèn)都是運行在主線程當(dāng)中的新症,如果直接在服務(wù)里處理一些耗時的邏輯徒爹,就很容易出現(xiàn)ANR(Applicatiion Not Responding)的情況,所以這個時候就需要用到android的多線程編程技術(shù)了隆嗅。我們應(yīng)該在服務(wù)的每個具體的方法里開啟一個子線程。然后在這里去處理那些耗時的邏輯胖喳。
public class MyService extends Service {
…
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 處理具體的邏輯
// 處理完之后關(guān)閉
stopSelf()
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
}
這種服務(wù)一旦處于運行狀態(tài)丽焊,必須調(diào)用stopService()或者stopSelf()方法才能讓服務(wù)停止下來。
由于總有程序員會忘記開啟子線程技健,或者忘記調(diào)用stopSelf()方法雌贱。為了簡約的創(chuàng)建一個異步的偿短,會自動停止的服務(wù)删掀,Android專門提供了一個IntentService類。
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());
// 這里可以處理一些具體的邏輯纤子,并且不用擔(dān)心ANR的問題款票,因為這個方法已經(jīng)是子線程中運行的
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(“MyIntentService”, “onDestroy executed”);
}
}
上面的IntentService在運行結(jié)束之后是會自動停止的艾少。
接下來修改activity_main.xml的代碼,加入一個用于啟動MyIntentService這個服務(wù)的按鈕
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/androd”
android:orientation=“vertical”
android:layout_height=“match_parent”
android:layout_width=“match_parent” >
…
<Button
android:id=“@+id/start_intent_service”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Start IntentService” />
</LinearLayout>
修改MainActivity的代碼:
public class MainActivity extends AppCompatActivity implements View.OnClickLisener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
…
Button startIntentService = (Button) findViewById(R.id.start_intent_service);
startIntentService.setOnClickLisener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
…
case R.id.start_intent_service:
// 打印主線程的id
Log.d(“MainActivity”, “Thread id id ” + Thread.currentThread().getId(0);
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
break;
default:
break;
}
}
}
其實發(fā)現(xiàn)幔妨,IntentService的用法和普通的服務(wù)沒有什么兩樣
最后不要忘記误堡,服務(wù)都需要再AndroidManifest.xml中注冊:
<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.servicetest” >
<application
android:allowBackup=“true”
android:icon=“@mipmap/ic_launcher”
android:label=“@string/app_name”
android:supportsRtl=“true”
android:theme=“@style/AppTheme” >
…
<service android:name=“.MyIntentService” />
</application>
</manifest>
10.6 服務(wù)的最佳實踐 —> 完整版的下載示例
創(chuàng)建一個ServiceBestPractice項目
1雏吭、添加好依賴庫:編輯app/build.gradle文件
dependencies {
compile fileTree(dir: ‘lib’, include: [‘*.jar’])
compile ‘com.android.support:appcompat-v7:24.2.1’
testCompile ‘junit:junit:4.12’
compile ‘com.squareup.okhttp3:okhttp:3.4.1’ // 添加OkHttp的依賴
}
2、定義一個回調(diào)接口悉抵,用于對下載過程中的各種狀態(tài)進(jìn)行監(jiān)聽和回調(diào):新建一個DownloadListener接口
public interface DownloadListener {
void onProgerss(int progress);
void onSuccess();
void onFailed();
void onPause();
void onCancled();
}
3摘完、編寫下載功能描焰,使用AsyncTask來實現(xiàn):
public class DownloadTask extends AsyncTask<String, Integer, Integer> {
// 定義四個常量表示下載的狀態(tài)
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 lastProgerss;
// 構(gòu)造函數(shù)
public DonwloadTask(DownloadListener listener) {
this.listener = listener;
}
// 用于在后臺執(zhí)行具體的下載邏輯
@Override
protected Integer doInBackground(String… params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadedLength = 0; // 用來記錄已下載的文件長度
String downloadUrl = param[0]; // 從參數(shù)中獲取下載的URL地址
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf(“/”)); // 從參數(shù)中解析出下載的文件名
// 將文件下載在這個Environment.DIRECTORY_DOWNLOADS目錄下。也就是SD卡的download目錄
String directory = Environment.getExternalStoragePublicDirectory(Environmen.DIRECTORY_DOWNLOADS).getPath();
file = new FIle(directory + fileName);
if (file.exists()) {
// 如果已經(jīng)存在了這個文件,則讀取已下載的字節(jié)數(shù)步绸,這樣就可以斷點續(xù)傳了
downloadedLength = file.length();
}
long contentLength = getContentLength(downloadUrl);
if (contentLength = 0) {
// 如果文件大小為0說明有問題
return TYPE_FAILED;
} else if (contentLength == downloadedLength) {
// 已下載字節(jié)和文件總字節(jié)相等吃媒,說明已經(jīng)下載完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.addHeader(“RANGE”, “bytes=” + downloadedLength + “-”)
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
savedFile = new RandomAccessFile(file, “rw”);
savedFile.seek(downloadedLength); // 跳過已下載的字節(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_PAUSE;
} else {
total += len;
savedFile.write(b, 0, len);
// 計算已下載的百分比
int progress = (int) ((total + downloadLength) * 100 / conentLength);
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;
}
// 用于在界面上更新具體的下載進(jìn)度
@Override
protected void onProgressUpdate(Integer… values) {
// 取出進(jìn)度,如果比上次的進(jìn)度有變化氯质,則更新進(jìn)度條
int progress = values[0];
if (progress > lastProgress) {
listener.onProgress(progress);
lastProgress = progress;
}
}
// 用于通知最終的下載結(jié)果
@Override
protected void onPostExecute(Integer status) {
switch (status) {
case TYPE_SUCCESS:
listener.onSuccess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_PAUSE:
listener.onPaused();
break;
default:
break;
}
}
public void pauseDownload() {
isPaused = true;
}
public void cancelDownload() {
isCanceled = true;
}
private long getContentLength(String downloadUrl) throws IOException {
OkHttpClient client = new OkHttpClient();
Request reqeust = 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中的三個泛型參數(shù):
參數(shù)1祠斧、String琢锋,表示在執(zhí)行AysncTask的時候需要傳入一個字符串給后臺任務(wù)
參數(shù)2、Integer吴超,表示使用整形數(shù)據(jù)來作為進(jìn)度顯示單位
參數(shù)3、Integer跋涣,表示使用整形數(shù)據(jù)來反饋執(zhí)行結(jié)果鸟悴。
4遣臼、為了保證DownloadTask可以一直在后臺運行,我們還需要創(chuàng)建一個下載的服務(wù)揍堰。新建DownloadService
public class DownloadService extends Service {
private DownloadTask downloadTask;
private String downloadUrl;
private DownloadListener listener = new DownloadListener() {
@Override
public void onProgerss(int progress) {
getNotificationManager().notify(1, getNotification(“Downloading…”, progress));
}
@Override
public void onSuccess() {
downloadTask = null;
// 下載成功時將前臺服務(wù)通知關(guān)閉屏歹,并創(chuàng)建一個下載成功的通知
stopForeground(true);
getNotificationManger().notify(1, getNotification(“Download Success”, -1));
Toast.makeText(DownloadService.this, “Download Success”, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed() {
downloadTask = null;
// 下載失敗時將前臺服務(wù)關(guān)閉,并創(chuàng)建一個下載失敗的通知
stopForeground(true);
getNotificationManger().notify(1, getNotification(“Download Failed”, -1));
Toast.makeText(DownloadService.this, “Download Failed”, Toast.LENGTH_SHORT).show();
}
@Override
public void onPause() {
downloadTask = null;
Toast.makeText(DownloadService.this,”Paused”, Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downloadTask = null;
stopForground(true);
Toast.makeText(DownloadServie.this, “Canceled”, Toast.LENGTH_SHORT).show();
}
};
// 定義內(nèi)部私有類
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…” Toask.LENGTH_SHORT).show();
}
}
public void pauseDownload() {
if (downloadTask != null) {
downloadTask.pauseDownload();
}
}
public void cancelDownload() {
if (downloadTask != null) {
downloadTask.cancelDownload();
} else {
if (downloadUrl != null ) {
// 取消下載時需將文件刪除季希,并將通知關(guān)閉
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.make(DownloadService.this, “Cancel”, Toast.LENGTH_SHORT).show();
}
}
}
private NotificaitonManager getNotificationManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVIC);
}
private Ntification geeNotificaiton(String titl, int progress) {
Intent intnet - new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getctivity(this, 0, intent, 0);
NotificationCompat.Builder builder = new NotificaiotnCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodePesource(getResources(), R.mipmap.ic_laucher));
builder.setContentIntent(pi);
builder.setContentTItle(title);
if (progress > 0) {
// 當(dāng)progress大于或等于0時才需要顯示下載進(jìn)度
builder.setContentText(progress + “%”);
builder.setProgress(100, progress, false);
}
return builder.build();
}
}