Android第一行代碼讀書筆記 - 第十章

====================================

====== 第十章:后臺默默的勞動者 — 探究服務(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();

}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市友浸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌武学,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硼补,死亡現(xiàn)場離奇詭異熏矿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疾捍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門栏妖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吊趾,“玉大人,你說我怎么就攤上這事论泛∑ㄗ啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵勇边,是天一觀的道長折联。 經(jīng)常有香客問我,道長诚镰,這世上最難降的妖魔是什么清笨? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮沙合,結(jié)果婚禮上跌帐,老公的妹妹穿的比我還像新娘。我一直安慰自己究履,他們只是感情好脸狸,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泥彤,像睡著了一般卿啡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剑逃,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天官辽,我揣著相機(jī)與錄音,去河邊找鬼萤捆。 笑死俗批,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蕴侣。 我是一名探鬼主播臭觉,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蝠筑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了什乙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤辅愿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阔蛉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體癞埠,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡苗踪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毕莱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片测暗。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖质和,靈堂內(nèi)的尸體忽然破棺而出稚字,到底是詐尸還是另有隱情,我是刑警寧澤瘫想,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布昌讲,位于F島的核電站,受9級特大地震影響短绸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窄驹,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一证逻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丈咐,春花似錦、人聲如沸负拟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至键菱,卻和暖如春今布,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侵蒙。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工傅蹂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人犁功。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓婚夫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親限嫌。 傳聞我的和親對象是個殘疾皇子侍筛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容