【Android Service】
Service 簡介(★★★)
很多情況下堤结,一些與用戶很少需要產(chǎn)生交互的應(yīng)用程序昔头,我們一般讓它們在后臺運 行就行了,而且在它們運行期間我們?nèi)匀荒苓\行其他的應(yīng)用赫模。為了處理這種后臺進程潜支, Android 引入了 Service 的概念。
- Service 在 Android 中是一種長生命周期的組件浅悉,它不實現(xiàn)任何用戶界面趟据,是一個沒有界面的 Activity
- Service 長期在后臺運行, 執(zhí)行不關(guān)乎界面的一些操作比如: 網(wǎng)易新聞服務(wù),每隔 1 分鐘去服務(wù)查看是否有最新新聞
- Service 和 Thread 有點相似,但是使用 Thread 不安全不嚴(yán)謹(jǐn)
- Service 和其他組件一樣术健,都是運行在主線程中汹碱,因此不能用它來做耗時的操作。(Service默認(rèn)并不會運行在子線程中荞估,它也不運行在一個獨立的進程中咳促,它同樣執(zhí)行在UI線程中稚新,因此,不要在Service中執(zhí)行耗時的操作跪腹,除非你在Service中創(chuàng)建子線程來完成耗時操作)
Android 中的進程(★★)
1. Android 中進程的種類
進程優(yōu)先級由高到低褂删,依次為:
- Foreground process 前臺進程
- Visible process 可視進程, 可以看見, 但不可以交互.
- Service process 服務(wù)進程
- Background process 后臺進程
- Empty process 空進程(當(dāng)程序退出時, 進程沒有被銷毀, 而是變成了空進程)
2. 進程的回收機制
Android 系統(tǒng)有一套內(nèi)存回收機制,會根據(jù)優(yōu)先級進行回收。Android 系統(tǒng)會盡可能的維持程序的進程, 但是終究還是需要回收一些舊的進程節(jié)省內(nèi)存提供給新的或者重要的進 程使用尺迂。
- 進程的回收順序是:從低到高
- 當(dāng)系統(tǒng)內(nèi)存不夠用時, 會把空進程一個一個回收掉
- 當(dāng)系統(tǒng)回收所有的完空進程不夠用時, 繼續(xù)向上回收后臺進程, 依次類推
- 但是當(dāng)回收服務(wù), 可視, 前臺這三種進程時, 系統(tǒng)非必要情況下不會輕易回收, 如果需要回收掉這三種進程, 那么在系統(tǒng)內(nèi)存夠用時, 會再給重新啟動進程;但是服務(wù)進程如果用戶手動的關(guān)閉服務(wù), 這時服務(wù)不會再重啟了笤妙。
3. 為什么用服務(wù)而不是線程
進程中運行著線程冒掌, Android 應(yīng)用程序剛啟動都會開啟一個進程給這個程序來使用噪裕。
Android 一個應(yīng)用程序把所有的界面關(guān)閉時, 進程這時還沒有被銷毀, 現(xiàn)在處于的是空進程狀態(tài),Thread 運行在空進程中, 很容易被銷毀。
服務(wù)不容易被銷毀, 如果非法狀態(tài)下被銷毀了, 系統(tǒng)會在內(nèi)存夠用時, 重新啟動股毫。
Service 的生命周期(★★★★)
service 的生命周期膳音,從它被創(chuàng)建開始,到它被銷毀為止铃诬,可以有兩條不同的路徑祭陷。
A started service(標(biāo)準(zhǔn)開啟模式)
當(dāng)應(yīng)用組件(如 Activity)通過調(diào)用 startService() 啟動服務(wù)時,服務(wù)即處于“啟動”狀態(tài)趣席。
??一旦啟動兵志,服務(wù)即可在后臺無限期運行,即使啟動服務(wù)的組件已被銷毀也不受影響宣肚,除非手動調(diào)用才能停止服務(wù)想罕。(調(diào)用 stopSelf()方法或者其他組件調(diào)用 stopService()方法)
?? 已啟動的服務(wù)通常是執(zhí)行單一操作,而且不會將結(jié)果返回給調(diào)用方霉涨。
A bound service(綁定模式)
當(dāng)應(yīng)用組件通過調(diào)用 bindService() 綁定到服務(wù)時按价,服務(wù)即處于“綁定”狀態(tài)。
??綁定服務(wù)提供了一個客戶端-服務(wù)器接口 IBinder 接口笙瑟,允許組件與服務(wù)進行交互楼镐、發(fā)送請求、獲取結(jié)果往枷,甚至是利用進程間通信 (IPC) 跨進程執(zhí)行這些操作框产。
??僅當(dāng)與另一個應(yīng)用組件綁定時,綁定服務(wù)才會運行错洁。多個組件可以同時綁定到該服務(wù)秉宿,但全部取消綁定后,該服務(wù)即會被銷毀墓臭。(通過 unbindService()方法來關(guān)閉這種連接)
Service 的這兩種生命周期并不是完全分開的
也就是說蘸鲸,你可以和一個已經(jīng)調(diào)用了 startService()而被開啟的 service 進行綁定。
比如窿锉,一個后臺音樂 service 可能因調(diào)用 startService()方法而被開啟了酌摇,稍后膝舅, 可能用戶想要控制播放器或者得到一些當(dāng)前歌曲的信息,可以通過 bindService()將一 個 activity 和 service 綁定窑多。這種情況下仍稀,stopService()或 stopSelf()實際上并不能停止這個 service,除非所有的客戶都解除綁定埂息。
Service 的生命周期圖
這個圖說明了 service 典型的回調(diào)方法技潘,盡管這個圖中將開啟的 service 和綁定的service 分開,但是你需要記住千康,任何service 都潛在地允許綁定享幽。所以,一個被開啟的 service 仍然可能被綁定拾弃。(你可以看到兩層嵌套的 service 的生命周期)
整體生命周期(The entire lifetime)
service 整體的生命時間是從 onCreate()被調(diào)用開始值桩,到 onDestroy()方法返回為止。
和 activity 一樣豪椿,service 在 onCreate()中進行它的初始化工作奔坟,在 onDestroy()中釋放殘留的資源。
比如搭盾,一個音樂播放 service 可以在 onCreate()中創(chuàng)建播放音樂的線程咳秉,在onDestory()中停止這個線程。
onCreate() 和 onDestroy()會被所有的 service 調(diào)用鸯隅,不論 service 是通過startService()還是 bindService()建立澜建。
積極活動的生命時間(The active lifetime)
service 積極活動的生命時間(active lifetime)是從 onStartCommand() 或onBind()被調(diào)用開始,它們各自處理由 startService()或 bindService()方法傳過來的 Intent 對象滋迈。
如果 service 是被開啟的霎奢,那么它的活動生命周期和整個生命周期一同結(jié)束。
如果 service 是被綁定的饼灿,它們它的活動生命周期是在 onUnbind()方法返回后結(jié)束幕侠。
盡管一個被開啟的 service 是通過調(diào)用 stopSelf() 或 stopService()來停止的,沒有一個對應(yīng)的回調(diào)函數(shù)與之對應(yīng)碍彭,即沒有 onStop()回調(diào)方法晤硕。所以,當(dāng)調(diào)用了停止的方法庇忌,除非這個 service 和客戶組件綁定舞箍,否則系統(tǒng)將會直接銷毀它,onDestory()方法會被調(diào)用皆疹,并且是這個時候唯一會被調(diào)用的回調(diào)方法疏橄。
Service 的生命周期回調(diào)函數(shù)
和 activity 一樣,service 也有一系列的生命周期回調(diào)函數(shù),你可以實現(xiàn)它們來監(jiān)測 service 狀態(tài)的變化捎迫,并且在適當(dāng)?shù)臅r候執(zhí)行適當(dāng)?shù)墓ぷ鳌?/p>
下面的 service 展示了每一個生命周期的方法:
不像是 activity 的生命周期回調(diào)函數(shù)晃酒,我們不需要調(diào)用基類的實現(xiàn)。
public class TestService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
onCreate()
首次創(chuàng)建服務(wù)時窄绒,系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或onBind() 之前)贝次。如果服務(wù)已在運行,則不會調(diào)用此方法彰导,該方法只調(diào)用一次
onBind()
當(dāng)另一個組件想通過調(diào)用 bindService() 與服務(wù)綁定(例如執(zhí)行 RPC)時蛔翅,系統(tǒng)將調(diào)用此方法。在此方法的實現(xiàn)中位谋,必須返回 一個IBinder 接口的實現(xiàn)類山析,供客戶端用來與服務(wù)進行通信。
??無論是啟動狀態(tài)還是綁定狀態(tài)倔幼,此方法必須重寫盖腿,但在啟動狀態(tài)的情況下直接返回 null。
onStartCommand()
當(dāng)另一個組件(如 Activity)通過調(diào)用 startService() 請求啟動服務(wù)時损同,系統(tǒng)將調(diào)用此方法。(啟動服務(wù)使用startService(Intent intent)方法鸟款,僅需要傳遞一個Intent對象即可膏燃,在Intent對象中指定需要啟動的服務(wù)。)??
??對于啟動服務(wù)何什,一旦啟動將與訪問它的組件無任何關(guān)聯(lián)组哩,即使訪問它的組件被銷毀了,這個服務(wù)也一直運行下去处渣,直到手動調(diào)用停止服務(wù)才被銷毀伶贰。在服務(wù)的外部,必須使用stopService()方法停止罐栈,在服務(wù)的內(nèi)部可以調(diào)用stopSelf()方法停止當(dāng)前服務(wù)黍衙。
??第一次調(diào)用startService方法時,onCreate方法荠诬、onStartCommand方法將依次被調(diào)用琅翻,而多次調(diào)用startService時,只有onStartCommand方法被調(diào)用柑贞,最后我們調(diào)用stopService方法停止服務(wù)時onDestory方法被回調(diào)方椎,這就是啟動狀態(tài)下Service的執(zhí)行周期。
onStartCommand的參數(shù)
接著我們重新回過頭來進一步分析onStartCommand(Intent intent, int flags, int startId)钧嘶,這個方法有3個傳入?yún)?shù)棠众,它們的含義如下:
onStartCommand(Intent intent, int flags, int startId)
intent :啟動時,啟動組件傳遞過來的Intent有决,如Activity可利用Intent封裝所需要的參數(shù)并傳遞給Service
-
flags:表示啟動請求時是否有額外數(shù)據(jù)闸拿,可選值有 0轿亮,START_FLAG_REDELIVERY,START_FLAG_RETRY胸墙。
- 0代表沒有
-
START_FLAG_REDELIVERY
這個值代表了onStartCommand方法的返回值為START_REDELIVER_INTENT我注,而且在上一次服務(wù)被殺死前會去調(diào)用stopSelf方法停止服務(wù)。其中START_REDELIVER_INTENT意味著當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后迟隅,則會重建服務(wù)但骨,并通過傳遞給服務(wù)的最后一個 Intent 調(diào)用 onStartCommand(),此時Intent時有值的智袭。 -
START_FLAG_RETRY
該flag代表當(dāng)onStartCommand調(diào)用后一直沒有返回值時奔缠,會嘗試重新去調(diào)用onStartCommand()。
startId : 指明當(dāng)前服務(wù)的唯一ID吼野,與stopSelfResult (int startId)配合使用校哎,stopSelfResult 可以更安全地根據(jù)ID停止服務(wù)。
onStartCommand的返回值
實際上onStartCommand的返回值int類型才是最最值得注意的瞳步,它有三種可選值闷哆, START_STICKY,START_NOT_STICKY单起,START_REDELIVER_INTENT抱怔,它們具體含義如下:
START_STICKY
當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后,一段時間后內(nèi)存再次空閑時嘀倒,系統(tǒng)將會嘗試重新創(chuàng)建此Service屈留,一旦創(chuàng)建成功后將回調(diào)onStartCommand方法,但其中的Intent將是null测蘑,除非有掛起的Intent灌危,如pendingintent,這個狀態(tài)下比較適用于不執(zhí)行命令碳胳、但無限期運行并等待作業(yè)的媒體播放器或類似服務(wù)勇蝙。START_NOT_STICKY
當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后,即使系統(tǒng)內(nèi)存再次空閑時固逗,系統(tǒng)也不會嘗試重新創(chuàng)建此Service浅蚪。除非程序中再次調(diào)用startService啟動此Service,這是最安全的選項烫罩,可以避免在不必要時以及應(yīng)用能夠輕松重啟所有未完成的作業(yè)時運行服務(wù)惜傲。START_REDELIVER_INTENT
當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后,則會重建服務(wù)贝攒,并通過傳遞給服務(wù)的最后一個 Intent 調(diào)用 onStartCommand()盗誊,任何掛起 Intent均依次傳遞。與START_STICKY不同的是,其中的傳遞的Intent將是非空哈踱,是最后一次調(diào)用startService中的intent荒适。這個值適用于主動執(zhí)行應(yīng)該立即恢復(fù)的作業(yè)(例如下載文件)的服務(wù)。
由于每次啟動服務(wù)(調(diào)用startService)時开镣,onStartCommand方法都會被調(diào)用刀诬,因此我們可以通過該方法使用Intent給Service傳遞所需要的參數(shù),然后在onStartCommand方法中處理的事件邪财,最后根據(jù)需求選擇不同的Flag返回值陕壹,以達(dá)到對程序更友好的控制。
onDestroy()
當(dāng)服務(wù)不再使用且將被銷毀時树埠,系統(tǒng)將調(diào)用此方法糠馆。服務(wù)應(yīng)該實現(xiàn)此方法來清理所有資源,如線程怎憋、注冊的偵聽器又碌、接收器等,這是服務(wù)接收的最后一個調(diào)用绊袋。
管理生命周期
當(dāng)綁定 service 和所有客戶端解除綁定之后毕匀,Android 系統(tǒng)將會銷毀它,(除非它同時被 onStartCommand()方法開啟)愤炸。因此期揪,如果你的 service 是一個純粹的綁定 service,那么你不需要管理它的生命周期规个。
然而,如果你選擇實現(xiàn) onStartCommand()回調(diào)方法姓建,那么你必須顯式地停止 service诞仓,因為 service 此時被看做是開啟的。這種情況下速兔,service 會一直運行到它 自己調(diào)用 stopSelf()或另一個組件調(diào)用 stopService()墅拭,不論它是否和客戶端綁定。
另外涣狗,如果你的 service 被開啟并且接受綁定谍婉,那么當(dāng)系統(tǒng)調(diào)用你的 onUnbind() 方法時,如果你想要在下次客戶端綁定的時候接受一個 onRebind()的調(diào)用(而不是調(diào)用 onBind())镀钓,你可以選擇在 onUnbind()中返回 true穗熬。
onRebind()的返回值為 void,但是客戶端仍然在它的 onServiceConnected()回調(diào)方法中得到 IBinder 對象丁溅。
下圖展示了這種 service(被開啟唤蔗,還允許綁定)的生命周期:
Service在清單文件中的聲明
前面說過Service分為啟動狀態(tài)和綁定狀態(tài)兩種,但無論哪種具體的Service啟動類型妓柜,都是通過繼承Service基類自定義而來箱季,也都需要在AndroidManifest.xml中聲明。
??我們先來了解一下Service在AndroidManifest.xml中的聲明語法棍掐,其格式如下:
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
android:exported:代表是否能被其他應(yīng)用隱式調(diào)用藏雏。
其默認(rèn)值是由service中有無intent-filter決定的,如果有intent-filter作煌,默認(rèn)值為true掘殴,否則為false。為false的情況下最疆,即使有intent-filter匹配杯巨,也無法打開,即無法被其他應(yīng)用隱式調(diào)用努酸。android:name:對應(yīng)Service類名
android:permission:是權(quán)限聲明
android:process:是否需要在單獨的進程中運行,當(dāng)設(shè)置為android:process=”:remote”時服爷,代表Service在單獨的進程中運行。
注意:“:”很重要获诈,它的意思是指要在當(dāng)前進程名稱前面附加上當(dāng)前的包名仍源,所以“remote”和”:remote”不是同一個意思,前者的進程名稱為:remote舔涎,而后者的進程名稱為:App-packageName:remote笼踩。android:isolatedProcess :設(shè)置 true 意味著,服務(wù)會在一個特殊的進程下運行亡嫌,這個進程與系統(tǒng)其他進程分開且沒有自己的權(quán)限嚎于。與其通信的唯一途徑是通過服務(wù)的API(bind and start)。
android:enabled:是否可以被系統(tǒng)實例化挟冠,默認(rèn)為 true因為父標(biāo)簽也有 enable 屬性于购,所以必須兩個都為默認(rèn)值 true 的情況下服務(wù)才會被激活,否則不會激活知染。
后臺服務(wù)
后臺服務(wù)可交互性主要是體現(xiàn)在不同的啟動服務(wù)方式肋僧,startService()和bindService()。
bindService()可以返回一個代理對象控淡,可調(diào)用Service中的方法和獲取返回結(jié)果等操作嫌吠;
而startService()不行。
不可交互的后臺服務(wù)(啟動服務(wù))
不可交互的后臺服務(wù)即是普通的Service掺炭。
Service的生命周期很簡單辫诅,分別為onCreate、onStartCommand竹伸、onDestroy這三個泥栖。
當(dāng)我們startService()的時候簇宽,首次創(chuàng)建Service會回調(diào)onCreate()方法母截,然后回調(diào)onStartCommand()方法店乐;再次startService()的時候,就只會執(zhí)行一次onStartCommand()滑进。
服務(wù)一旦開啟后钢颂,我們就需要通過stopService()方法或者stopSelf()方法钞它,就能把服務(wù)關(guān)閉,這時就會回調(diào)onDestroy()
① 服務(wù)端:創(chuàng)建服務(wù)類
創(chuàng)建一個服務(wù)非常簡單殊鞭,只要繼承Service遭垛,并實現(xiàn)onBind()方法
public class BackGroupService extends Service {
/* 綁定服務(wù)時調(diào)用 */
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("Service", "onBind");
return null;
}
/* 服務(wù)創(chuàng)建時調(diào)用 */
@Override
public void onCreate() {
Log.e("Service", "onCreate");
super.onCreate();
}
/* 執(zhí)行startService時調(diào)用 */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("Service", "onStartCommand");
//這里執(zhí)行耗時操作
new Thread() {
@Override
public void run() {
while (true){
try {
Log.e("Service", "doSomething");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
/* 服務(wù)被銷毀時調(diào)用 */
@Override
public void onDestroy() {
Log.e("Service", "onDestroy");
super.onDestroy();
}
}
② 配置服務(wù)
Service也是四大組件之一,所以必須在manifests中配置
<service android:name=".Service.BackGroupService"/>
③ 客戶端:啟動服務(wù)和停止服務(wù)
我們通過兩個按鈕分別演示啟動服務(wù)和停止服務(wù)操灿,通過startService()開啟服務(wù)锯仪,通過stopService()停止服務(wù)
public class MainActivity extends AppCompatActivity {
Button bt_open, bt_close;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_open = (Button) findViewById(R.id.open);
bt_close = (Button) findViewById(R.id.close);
//自定義Service的意圖
final Intent intent = new Intent(this, BackGroupService.class);
bt_open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//啟動服務(wù)
startService(intent);
}
});
bt_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//停止服務(wù)
stopService(intent);
}
});
}
}
當(dāng)你開啟服務(wù)后,還有一種方法可以關(guān)閉服務(wù)趾盐,在設(shè)置中庶喜,通過應(yīng)用->找到自己應(yīng)用->停止
④ 運行代碼
運行程序后,我們點擊開始服務(wù)救鲤,然后一段時間后關(guān)閉服務(wù)久窟。我們以Log信息來驗證普通Service的生命周期:onCreate->onStartCommand->onDestroy
11-24 00:19:51.483 16407-16407/com.handsome.boke2 E/Service: onCreate
11-24 00:19:51.483 16407-16407/com.handsome.boke2 E/Service: onStartCommand
11-24 00:19:51.485 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:19:53.490 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:19:55.491 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:19:57.491 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:19:58.056 16407-16407/com.handsome.boke2 E/Service: onDestroy
11-24 00:19:59.492 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:20:01.494 16407-16613/com.handsome.boke2 E/Service: doSomething
11-24 00:20:03.495 16407-16613/com.handsome.boke2 E/Service: doSomething
其中你會發(fā)現(xiàn)我們的子線程進行的耗時操作是一直存在的,而我們Service已經(jīng)被關(guān)閉了本缠,關(guān)閉該子線程的方法需要直接通過Home鍵關(guān)閉該應(yīng)用程序
可交互的后臺服務(wù)(綁定服務(wù))
可交互的后臺服務(wù)是指前臺頁面可以調(diào)用后臺服務(wù)的方法斥扛。
??可交互的后臺服務(wù)實現(xiàn)步驟是和不可交互的后臺服務(wù)實現(xiàn)步驟是一樣的,區(qū)別在于啟動的方式和獲得Service的代理對象丹锹。
??與啟動服務(wù)不同的是綁定服務(wù)的生命周期通常只在為其他應(yīng)用組件(如Activity)服務(wù)時處于活動狀態(tài)稀颁,不會無限期在后臺運行,也就是說宿主(如Activity)解除綁定后楣黍,綁定服務(wù)就會被銷毀峻村。
??那么在提供綁定的服務(wù)時,該如何實現(xiàn)呢锡凝?實際上我們必須提供一個 IBinder接口的實現(xiàn)類,該類用以提供客戶端用來與服務(wù)進行交互的編程接口垢啼,該接口可以通過三種方法定義接口:
擴展 Binder 類
??如果服務(wù)是提供給自有應(yīng)用專用的窜锯,并且Service(服務(wù)端)與客戶端相同的進程中運行(常見情況),則應(yīng)通過擴展 Binder 類并從 onBind() 返回它的一個實例來創(chuàng)建接口芭析。
??客戶端收到 Binder 后锚扎,可利用它直接訪問 Binder 實現(xiàn)中以及Service 中可用的公共方法。
??如果我們的服務(wù)只是自有應(yīng)用的后臺工作線程馁启,則優(yōu)先采用這種方法驾孔。 不采用該方式創(chuàng)建接口的唯一原因是芍秆,服務(wù)被其他應(yīng)用或不同的進程調(diào)用。使用 Messenger
??Messenger可以翻譯為信使翠勉,通過它可以在不同的進程中共傳遞Message對象(Handler中的Messager妖啥,因此 Handler 是 Messenger 的基礎(chǔ)),在Message中可以存放我們需要傳遞的數(shù)據(jù)对碌,然后在進程間傳遞荆虱。
??如果需要讓接口跨不同的進程工作,則可使用 Messenger 為服務(wù)創(chuàng)建接口朽们,客戶端就可利用 Message 對象向服務(wù)發(fā)送命令怀读。同時客戶端也可定義自有 Messenger,以便服務(wù)回傳消息骑脱。
??這是執(zhí)行進程間通信 (IPC) 的最簡單方法菜枷,因為 Messenger 會在單一線程中創(chuàng)建包含所有請求的隊列,也就是說Messenger是以串行的方式處理客戶端發(fā)來的消息叁丧,這樣我們就不必對服務(wù)進行線程安全設(shè)計了啤誊。使用 AIDL
??由于Messenger是以串行的方式處理客戶端發(fā)來的消息,如果當(dāng)前有大量消息同時發(fā)送到Service(服務(wù)端)歹袁,Service仍然只能一個個處理坷衍,這也就是Messenger跨進程通信的缺點了,因此如果有大量并發(fā)請求条舔,Messenger就會顯得力不從心了枫耳,這時AIDL(Android 接口定義語言)就派上用場了, 但實際上Messenger 的跨進程方式其底層實現(xiàn) 就是AIDL孟抗,只不過android系統(tǒng)幫我們封裝成透明的Messenger罷了 迁杨。
??因此,如果我們想讓服務(wù)同時處理多個請求凄硼,則應(yīng)該使用 AIDL铅协。 在此情況下,服務(wù)必須具備多線程處理能力摊沉,并采用線程安全式設(shè)計狐史。
??使用AIDL必須創(chuàng)建一個定義編程接口的 .aidl 文件。Android SDK 工具利用該文件生成一個實現(xiàn)接口并處理 IPC 的抽象類说墨,隨后可在服務(wù)內(nèi)對其進行擴展骏全。
以上3種實現(xiàn)方式,我們可以根據(jù)需求自由的選擇尼斧,但需要注意的是大多數(shù)應(yīng)用“都不會”使用 AIDL 來創(chuàng)建綁定服務(wù)姜贡,因為它可能要求具備多線程處理能力,并可能導(dǎo)致實現(xiàn)的復(fù)雜性增加棺棵。因此楼咳,AIDL 并不適合大多數(shù)應(yīng)用熄捍,本篇中也不打算闡述如何使用AIDL(后面會另開一篇分析AIDL),接下來我們分別針對擴展 Binder 類和Messenger的使用進行分析母怜。
1. 擴展 Binder 類
前面描述過余耽,如果我們的服務(wù)僅供本地應(yīng)用使用,不需要跨進程工作糙申,則可以實現(xiàn)自有 Binder 類宾添,讓客戶端通過該類直接訪問服務(wù)中的公共方法。其使用開發(fā)步驟如下
- 創(chuàng)建BindService服務(wù)端柜裸,繼承自Service并在類中缕陕,創(chuàng)建一個實現(xiàn)IBinder 接口的實例對象并提供公共方法給客戶端調(diào)用
- 從 onBind() 回調(diào)方法返回此 Binder 實例。
- 在客戶端中疙挺,從 onServiceConnected() 回調(diào)方法接收 Binder扛邑,并使用提供的方法調(diào)用綁定服務(wù)。
注意:此方式只有在客戶端和服務(wù)位于同一應(yīng)用和進程內(nèi)才有效铐然,如對于需要將 Activity 綁定到在后臺播放音樂的自有服務(wù)的音樂應(yīng)用蔬崩,此方式非常有效。另一點之所以要求服務(wù)和客戶端必須在同一應(yīng)用內(nèi)搀暑,是為了便于客戶端轉(zhuǎn)換返回的對象和正確調(diào)用其 API沥阳。服務(wù)和客戶端還必須在同一進程內(nèi),因為此方式不執(zhí)行任何跨進程編組自点。
① 服務(wù)端:創(chuàng)建服務(wù)類
LocalService類繼承自Service桐罕,在該類中創(chuàng)建了一個LocalBinder繼承自Binder類,LocalBinder中聲明了一個getService方法桂敛,客戶端可訪問該方法獲取LocalService對象的實例功炮,只要客戶端獲取到LocalService對象的實例就可調(diào)用LocalService服務(wù)端的公共方法,如getCount方法术唬。
??值得注意的是薪伏,我們在onBind方法中返回了binder對象,該對象便是LocalBinder的具體實例粗仓,而binder對象最終會返回給客戶端嫁怀,客戶端通過返回的binder對象便可以與服務(wù)端實現(xiàn)交互。
package com.zejian.ipctest.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
/* 綁定服務(wù)簡單實例--服務(wù)端 */
public class LocalService extends Service{
private final static String TAG = "wzj";
private int count;
private boolean quit;
private Thread thread;
private LocalBinder binder = new LocalBinder();
/* 創(chuàng)建Binder對象借浊,返回給客戶端即Activity使用眶掌,提供數(shù)據(jù)交換的接口 */
public class LocalBinder extends Binder {
// 聲明一個方法,getService巴碗。(提供給客戶端調(diào)用)
LocalService getService() {
// 返回當(dāng)前對象LocalService,這樣我們就可在客戶端端調(diào)用Service的公共方法了
return LocalService.this;
}
}
/* 把Binder類返回給客戶端 */
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service is invoke Created");
thread = new Thread(new Runnable() {
@Override
public void run() {
// 每間隔一秒count加1 ,直到quit為true即寒。
while (!quit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
});
thread.start();
}
/* 公共方法 */
public int getCount(){
return count;
}
/* 解除綁定時調(diào)用 */
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "Service is invoke onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "Service is invoke Destroyed");
this.quit = true;
super.onDestroy();
}
}
② 配置服務(wù)
...
③ 客戶端:綁定服務(wù)和解除綁定服務(wù)
接著看看客戶端BindActivity的實現(xiàn):
package com.zejian.ipctest.service;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/* 綁定服務(wù)實例--客戶端 */
public class BindActivity extends Activity {
protected static final String TAG = "wzj";
Button btnBind;
Button btnUnBind;
Button btnGetDatas;
private ServiceConnection conn;
private LocalService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind);
btnBind = (Button) findViewById(R.id.BindService);
btnUnBind = (Button) findViewById(R.id.unBindService);
btnGetDatas = (Button) findViewById(R.id.getServiceDatas);
//創(chuàng)建綁定對象
final Intent intent = new Intent(this, LocalService.class);
// 開啟綁定
btnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "綁定調(diào)用:bindService");
//調(diào)用綁定方法
bindService(intent, conn, Service.BIND_AUTO_CREATE);
}
});
// 解除綁定
btnUnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "解除綁定調(diào)用:unbindService");
// 解除綁定
if(mService!=null) {
mService = null;
unbindService(conn);
}
}
});
// 獲取數(shù)據(jù)
btnGetDatas.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mService != null) {
// 通過綁定服務(wù)傳遞的Binder對象橡淆,獲取Service暴露出來的數(shù)據(jù)
Log.d(TAG, "從服務(wù)端獲取數(shù)據(jù):" + mService.getCount());
} else {
Log.d(TAG, "還沒綁定呢召噩,先綁定,無法從服務(wù)端獲取數(shù)據(jù)");
}
}
});
/* ServiceConnection代表與服務(wù)的連接,它只有兩個方法:onServiceConnected和onServiceDisconnected逸爵,前者是在操作者在連接一個服務(wù)成功時被調(diào)用具滴,而后者是在服務(wù)崩潰或被殺死導(dǎo)致的連接中斷時被調(diào)用 */
conn = new ServiceConnection() {
/* onServiceConnected 綁定服務(wù)的時候被回調(diào),在這個方法獲取綁定Service傳遞過來的IBinder對象师倔,通過這個IBinder對象构韵,實現(xiàn)宿主和Service的交互。 */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "綁定成功調(diào)用:onServiceConnected");
// 獲取Binder
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
/* onServiceDisconnected 當(dāng)取消綁定的時候被回調(diào)趋艘。但正常情況下是不被調(diào)用的疲恢,它的調(diào)用時機是當(dāng)Service服務(wù)被意外銷毀時,例如內(nèi)存的資源不足時這個方法才被自動調(diào)用瓷胧。 */
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
}
}
ServiceConnection
在客戶端中我們創(chuàng)建了一個ServiceConnection對象显拳,該代表與服務(wù)的連接,它只有兩個方法搓萧, onServiceConnected和onServiceDisconnected杂数,其含義如下:
-
onServiceConnected(ComponentName name, IBinder service)
系統(tǒng)會調(diào)用該方法以傳遞服務(wù)的onBind() 方法返回的 IBinder。
其中service便是服務(wù)端返回的IBinder實現(xiàn)類對象瘸洛,通過該對象我們便可以調(diào)用獲取LocalService實例對象揍移,進而調(diào)用服務(wù)端的公共方法。而ComponentName是一個封裝了組件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的類反肋,如包名那伐,組件描述等信息,較少使用該參數(shù)囚玫。 -
onServiceDisconnected(ComponentName name)
Android 系統(tǒng)會在與服務(wù)的連接意外中斷時(例如當(dāng)服務(wù)崩潰或被終止時)調(diào)用該方法喧锦。注意:當(dāng)客戶端取消綁定時,系統(tǒng)“絕對不會”調(diào)用該方法抓督。
在onServiceConnected()被回調(diào)前燃少,我們還需先把當(dāng)前Activity綁定到服務(wù)LocalService上,綁定服務(wù)是通過通過bindService()方法铃在,解綁服務(wù)則使用unbindService()方法阵具,這兩個方法解析如下:
-
bindService(Intent service, ServiceConnection conn, int flags)
該方法執(zhí)行綁定服務(wù)操作,其中Intent是我們要綁定的服務(wù)(也就是LocalService)的意圖定铜,而ServiceConnection代表與服務(wù)的連接阳液,它只有兩個方法,前面已分析過揣炕,flags則是指定綁定時是否自動創(chuàng)建Service帘皿。0代表不自動創(chuàng)建、BIND_AUTO_CREATE則代表自動創(chuàng)建畸陡。 -
unbindService(ServiceConnection conn)
該方法執(zhí)行解除綁定的操作
Activity通過bindService()綁定到LocalService后鹰溜,ServiceConnection#onServiceConnected()便會被回調(diào)并可以獲取到LocalService實例對象mService虽填,之后我們就可以調(diào)用LocalService服務(wù)端的公共方法了。
而客戶端布局文件實現(xiàn)如下:
<?xml version="1.0" encoding="utf-8"?>
<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/BindService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="綁定服務(wù)器"
/>
<Button
android:id="@+id/unBindService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解除綁定"
/>
<Button
android:id="@+id/getServiceDatas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="獲取服務(wù)方數(shù)據(jù)"
/>
</LinearLayout>
④ 運行代碼
我們運行程序曹动,點擊綁定服務(wù)并多次點擊綁定服務(wù)接著多次調(diào)用LocalService中的getCount()獲取數(shù)據(jù)斋日,最后調(diào)用解除綁定的方法移除服務(wù),其結(jié)果如下:
通過Log可知墓陈,當(dāng)我們第一次點擊綁定服務(wù)時恶守,LocalService服務(wù)端的onCreate()、onBind方法會依次被調(diào)用贡必,此時客戶端的ServiceConnection#onServiceConnected()被調(diào)用并返回LocalBinder對象兔港,接著調(diào)用LocalBinder#getService方法返回LocalService實例對象,此時客戶端便持有了LocalService的實例對象赊级,也就可以任意調(diào)用LocalService類中的聲明公共方法了押框。
??更值得注意的是,我們多次調(diào)用bindService方法綁定LocalService服務(wù)端理逊,而LocalService得onBind方法只調(diào)用了一次橡伞,那就是在第一次調(diào)用bindService時才會回調(diào)onBind方法。
??接著我們點擊獲取服務(wù)端的數(shù)據(jù)晋被,從Log中看出我們點擊了3次通過getCount()獲取了服務(wù)端的3個不同數(shù)據(jù)兑徘,最后點擊解除綁定,此時LocalService的onUnBind羡洛、onDestroy方法依次被回調(diào)挂脑,并且多次綁定只需一次解綁即可。此情景也就說明了綁定狀態(tài)下的Service生命周期方法的調(diào)用依次為onCreate()欲侮、onBind崭闲、onUnBind、onDestroy威蕉。
??以上便是同一應(yīng)用同一進程中客戶端與服務(wù)端的綁定回調(diào)方式刁俭。
2. 使用Messenger
前面了解了如何使用IBinder應(yīng)用內(nèi)同一進程的通信后,我們接著來了解服務(wù)與遠(yuǎn)程進程(即不同進程間)通信韧涨,而不同進程間的通信牍戚,最簡單的方式就是使用 Messenger 服務(wù)提供通信接口,利用此方式虑粥,我們無需使用 AIDL 便可執(zhí)行進程間通信 (IPC)如孝。以下是 Messenger 使用的主要步驟:
- 服務(wù)實現(xiàn)一個 Handler,由其接收來自客戶端的每個調(diào)用的回調(diào)
- Handler 用于創(chuàng)建 Messenger 對象(對 Handler 的引用)
- Messenger 創(chuàng)建一個 IBinder娩贷,服務(wù)通過 onBind() 使其返回客戶端
- 客戶端使用 IBinder 將 Messenger(引用服務(wù)的 Handler)實例化第晰,然后使用Messenger將 Message 對象發(fā)送給服務(wù)
- 服務(wù)在其 Handler 中(在 handleMessage() 方法中)接收每個 Message
① 服務(wù)端
以下是一個使用 Messenger 接口的簡單服務(wù)示例,服務(wù)端進程實現(xiàn)如下:
??首先我們同樣需要創(chuàng)建一個服務(wù)類MessengerService繼承自Service。
??同時創(chuàng)建一個繼承自Handler的IncomingHandler對象來接收客戶端進程發(fā)送過來的消息并通過其handleMessage(Message msg)進行消息處理但荤。
??接著通過IncomingHandler對象創(chuàng)建一個Messenger對象罗岖,該對象是與客戶端交互的特殊對象。
??然后在Service的onBind中返回這個Messenger對象的底層Binder即可腹躁。
package com.zejian.ipctest.messenger;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
/* 服務(wù)端簡單實例,服務(wù)端進程 */
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
private static final String TAG ="wzj" ;
/* 用于接收從客戶端傳遞過來的數(shù)據(jù) */
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
break;
default:
super.handleMessage(msg);
}
}
}
/* 創(chuàng)建Messenger并傳入Handler實例對象 */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/* 當(dāng)綁定Service時,該方法被調(diào)用,將通過mMessenger返回一個實現(xiàn)IBinder接口的實例對象 */
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Service is invoke onBind");
return mMessenger.getBinder();
}
}
② 配置服務(wù)
在清單文件聲明Service和Activity,由于要測試不同進程的交互南蓬,則需要將Service放在單獨的進程中纺非,因此Service聲明如下:
<service android:name=".messenger.MessengerService"
android:process=":remote"
/>
其中android:process=":remote"
代表該Service在單獨的進程中創(chuàng)建。
③ 客戶端
下面看看客戶端進程的實現(xiàn):
package com.zejian.ipctest.messenger;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/* 與服務(wù)器交互的客戶端 */
public class ActivityMessenger extends Activity {
/* 與服務(wù)端交互的Messenger */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/* 實現(xiàn)與服務(wù)端鏈接的對象ServiceConnection */
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
/* 通過服務(wù)端傳遞的IBinder對象,創(chuàng)建相應(yīng)的Messenger
* 通過該Messenger對象與服務(wù)端進行交互 */
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 創(chuàng)建與服務(wù)交互的消息實體Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
//發(fā)送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenager);
Button bindService= (Button) findViewById(R.id.bindService);
Button unbindService= (Button) findViewById(R.id.unbindService);
Button sendMsg= (Button) findViewById(R.id.sendMsgToService);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("zj","onClick-->bindService");
//當(dāng)前Activity綁定服務(wù)端
bindService(new Intent(ActivityMessenger.this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
});
//發(fā)送消息給服務(wù)端
sendMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sayHello(v);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Unbind from the service
if (mBound) {
Log.d("zj","onClick-->unbindService");
unbindService(mConnection);
mBound = false;
}
}
});
}
}
在客戶端進程中赘方,我們需要創(chuàng)建一個ServiceConnection對象烧颖,該對象代表與服務(wù)端的鏈接,當(dāng)調(diào)用bindService方法將當(dāng)前Activity綁定到MessengerService時窄陡,onServiceConnected方法被調(diào)用炕淮,利用服務(wù)端傳遞給來的底層Binder對象構(gòu)造出與服務(wù)端交互的Messenger對象,接著創(chuàng)建與服務(wù)交互的消息實體Message跳夭,將要發(fā)生的信息封裝在Message中并通過Messenger實例對象發(fā)送給服務(wù)端涂圆。
④ 運行代碼
最后我們運行程序,結(jié)果如下:
接著多次點擊綁定服務(wù)币叹,然后發(fā)送信息給服務(wù)端润歉,最后解除綁定,Log打印如下:
通過上述例子可知Service服務(wù)端確實收到了客戶端發(fā)送的信息颈抚,而且在Messenger中進行數(shù)據(jù)傳遞必須將數(shù)據(jù)封裝到Message中踩衩,因為Message和Messenger都實現(xiàn)了Parcelable接口,可以輕松跨進程傳遞數(shù)據(jù)(關(guān)于Parcelable接口可以看博主的另一篇文章:序列化與反序列化之Parcelable和Serializable淺析)贩汉。
??Message可以傳遞的信息載體有驱富,what,arg1,arg2,Bundle以及replyTo。至于object字段匹舞,對于同一進程中的數(shù)據(jù)傳遞確實很實用褐鸥,但對于進程間的通信,則顯得相當(dāng)尷尬策菜,在android2.2前晶疼,object不支持跨進程傳輸,但即便是android2.2之后也只能傳遞android系統(tǒng)提供的實現(xiàn)了Parcelable接口的對象又憨,也就是說我們通過自定義實現(xiàn)Parcelable接口的對象無法通過object字段來傳遞翠霍,因此object字段的實用性在跨進程中也變得相當(dāng)?shù)土恕2贿^所幸我們還有Bundle對象蠢莺,Bundle可以支持大量的數(shù)據(jù)類型寒匙。
??接著從Log我們也看出無論是使用拓展Binder類的實現(xiàn)方式還是使用Messenger的實現(xiàn)方式,它們的生命周期方法的調(diào)用順序基本是一樣的,即onCreate()锄弱、onBind考蕾、onUnBind、onDestroy会宪,而且多次綁定中也只有第一次時才調(diào)用onBind()肖卧。
??以上的例子演示了如何在服務(wù)端接收客戶端發(fā)送的消息,但有時候我們可能還需要服務(wù)端能回應(yīng)客戶端掸鹅,這時便需要提供雙向消息傳遞了塞帐,下面就來實現(xiàn)一個簡單服務(wù)端與客戶端雙向消息傳遞的簡單例子。
??先來看看服務(wù)端的修改巍沙,在服務(wù)端葵姥,我們只需修改IncomingHandler,收到消息后句携,給客戶端回復(fù)一條信息榔幸。
/* 用于接收從客戶端傳遞過來的數(shù)據(jù) */
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
//回復(fù)客戶端信息,該對象由客戶端傳遞過來
Messenger client=msg.replyTo;
//獲取回復(fù)信息的消息實體
Message replyMsg=Message.obtain(null,MessengerService.MSG_SAY_HELLO);
Bundle bundle=new Bundle();
bundle.putString("reply","ok~,I had receiver message from you! ");
replyMsg.setData(bundle);
//向客戶端發(fā)送消息
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
接著修改客戶端,為了接收服務(wù)端的回復(fù)矮嫉,客戶端也需要一個接收消息的Messenger和Handler削咆,其實現(xiàn)如下:
/* 用于接收服務(wù)器返回的信息 */
private Messenger mRecevierReplyMsg= new Messenger(new ReceiverReplyMsgHandler());
private static class ReceiverReplyMsgHandler extends Handler{
private static final String TAG = "zj";
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//接收服務(wù)端回復(fù)
case MessengerService.MSG_SAY_HELLO:
Log.i(TAG, "receiver message from service:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
除了添加以上代碼,還需要在發(fā)送信息時把接收服務(wù)器端的回復(fù)的Messenger通過Message的replyTo參數(shù)傳遞給服務(wù)端敞临,以便作為同學(xué)橋梁态辛,代碼如下:
public void sayHello(View v) {
if (!mBound) return;
// 創(chuàng)建與服務(wù)交互的消息實體Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
//把接收服務(wù)器端的回復(fù)的Messenger通過Message的replyTo參數(shù)傳遞給服務(wù)端
msg.replyTo=mRecevierReplyMsg;
try {
//發(fā)送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
到此服務(wù)端與客戶端雙向消息傳遞的簡單例子修改完成,我們運行一下代碼挺尿,看看Log打印奏黑,如下:
通過Messenge方式進行進程間通信的原理圖:
3. AIDL
關(guān)于AIDL跨進程服務(wù)的使用和原理分析,可以見我另一篇博客:Android基礎(chǔ)——初學(xué)者必知的AIDL在應(yīng)用層上的Binder機制
實例-遠(yuǎn)程服務(wù)調(diào)用商城支付
在 Android 平臺中编矾,各個組件運行在自己的進程中熟史,他們之間是不能相互訪問的,但是在程序之間是不可避免的要傳遞一些對象窄俏,在進程之間相互通信蹂匹。為了實現(xiàn)進程之間的相互 通信,Android 采用了一種輕量級的實現(xiàn)方式RPC(Remote Procedure Call 遠(yuǎn)程進程調(diào)用) 來完成進程之間的通信凹蜈,并且 Android 通過接口定義語言(Android Interface Definition Language ,AIDL)來生成兩個進程之間相互訪問的代碼限寞,例如,你在 Activity 里的代碼需要訪問 Service 中的一個方法仰坦,那么就可以通過這種方式來實現(xiàn)了履植。
AIDL 是 Android 的一種接口描述語言; 編譯器可以通過 aidl 文件生成一段代碼,通過預(yù)先定義的接口達(dá)到兩個進程內(nèi)部通信進程的目的悄晃。如果需要在一個 Activity 中, 訪問另一個 Service 中的某個對象, 需要先將對象轉(zhuǎn)化成 AIDL 可識別的參數(shù)(可能是多個參數(shù)), 然后 使用 AIDL 來傳遞這些參數(shù), 在消息的接收端, 使用這些參數(shù)組裝成自己需要的對象玫霎。
AIDL RPC 機制是通過接口來實現(xiàn)的,類似 Windows 中的 COM 或者 Corba,但他是輕量級的庶近,客戶端和被調(diào)用實現(xiàn)之間是通過代理模式實現(xiàn)的翁脆,代理類和被代理類實現(xiàn)同一個接 口 IBinder 接口。
下面是案例-商城支付的步驟:
需求:分別創(chuàng)建兩個工程鼻种,模擬一個支付平臺禁悠,暫且叫支付寶瞳浦,模擬一個商戶端叁执,叫商戶突勇。 商戶可以調(diào)用支付寶發(fā)布的遠(yuǎn)程服務(wù)進行收款操作痴施。
-
① 新創(chuàng)建一個 Android 工程《支付寶》脖母,包名:com.itheima.alipay疆导。在 src 目錄 下創(chuàng)建 com.itheima.alipay.aidl 包蟆沫,然后在該包下創(chuàng)建 AlipayRemoteService.aidl 文件歉秫。
在該文件中只聲明一個接口蛾洛,在接口里聲明一個方法。文件清單如下:
package com.itheima.alipay.aidl;
interface AlipayRemoteService{
boolean forwardPayMoney(float money);
}
當(dāng)該 aidl 文件創(chuàng)建好以后 ADT 會自動在 gen 目錄下創(chuàng)建對應(yīng)的類
- ② 在《支付寶》src 目錄下創(chuàng)建 com.itheima.alipay.service 包雁芙,在該包中新建一個Service轧膘,叫 AlipayService,該類實現(xiàn)付款功能兔甘。代碼清單如下:
public class AlipayService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new PayController();
}
public boolean pay(float money) {
System.out.println("成功付款" + money);
return true;
}
/* 因為 Stub 已經(jīng)繼承了 IBinder 接口谎碍,因此 PayController 類也間接繼承了該接口 */
public class PayController extends Stub {
@Override
public boolean forwardPayMoney(float money) throws RemoteException {
return pay(money);
}
}
}
- ③ 在《支付寶》工程的 AndroidManifest.xml 中注冊該 AlipayService
<service android:name="com.itheima.alipay.service.AlipayService">
<intent-filter>
<action android:name="com.itheima.alipay"></action>
</intent-filter>
</service>
-
④ 創(chuàng)建一個新 Android 工程,名字叫《商戶》洞焙,包名:com.itheima.shop蟆淀。
將《支付寶》工程中的 AlipayRemoteService.aidl 文件拷貝到《商戶》工程的 src 目 錄下,同時注意添加對應(yīng)的包名澡匪,要求包名必須跟該文件在原工程中的包名嚴(yán)格一致熔任。
《商戶》src 目錄結(jié)構(gòu)如下圖:
⑤ 編輯 activity_main.xm 布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="商城支付-調(diào)用遠(yuǎn)程服務(wù)"
android:textColor="#ff0000"
android:textSize="28sp" />
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入要轉(zhuǎn)正的金額"
android:inputType="number" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:onClick="pay"
android:text="確定支付" />
</LinearLayout>
- ⑥ 編寫 MainActivity 類,在該類中實現(xiàn)核心方法
public class MainActivity extends Activity {
//聲明一個 AlipayRemoteService 對象唁情,該類是根據(jù) aidl 文件自動生成
private AlipayRemoteService alipayRemoteService;
private EditText et;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et = (EditText) findViewById(R.id.et);
//創(chuàng)建一個隱式意圖疑苔,用于啟動《支付寶》中的 Service
Intent intent = new Intent();
intent.setAction("com.itheima.alipay");
//綁定遠(yuǎn)程服務(wù)
boolean bindService = bindService(intent, new MyConnection(), Context.BIND_AUTO_CREATE);
if (bindService) {
Toast.makeText(this, "服務(wù)綁定成功", 1).show();
System.out.println("服務(wù)綁定成功");
} else {
Toast.makeText(this, "服務(wù)綁定失敗", 1).show();
System.out.println("服務(wù)綁定失敗");
}
}
public void pay(View view) {
float money = Float.valueOf(et.getText().toString());
try {
alipayRemoteService.forwardPayMoney(money);
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(this, "付款失敗", 1).show();
}
Toast.makeText(this, "成功轉(zhuǎn)賬:" + money + "元!", 0).show();
}
class MyConnection implements ServiceConnection {
/* 通過 Stub 的靜態(tài)方法 asInterface 將 IBinder 對象轉(zhuǎn)化為本地AlipayRemoteService 對象 */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("服務(wù)已經(jīng)連接甸鸟。惦费。。");
alipayRemoteService = Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("服務(wù)已經(jīng)關(guān)閉抢韭。");
}
}
}
-
⑦ 先將《支付寶》部署到模擬器薪贫,然后將《商戶》部署到模擬器,然后在《商戶》界面輸入一個金額篮绰,然后點擊確定支付后雷,發(fā)現(xiàn)《商戶》工程已經(jīng)成功通過遠(yuǎn)程服務(wù)調(diào)用了《支付寶》中的服務(wù)。運行圖如下:
關(guān)于綁定服務(wù)的注意點
多個客戶端可同時連接到一個服務(wù)。不過臀突,只有在第一個客戶端綁定時勉抓,系統(tǒng)才會調(diào)用服務(wù)的 onBind() 方法來檢索 IBinder。系統(tǒng)隨后無需再次調(diào)用 onBind()候学,便可將同一 IBinder 傳遞至任何其他綁定的客戶端藕筋。當(dāng)最后一個客戶端取消與服務(wù)的綁定時,系統(tǒng)會將服務(wù)銷毀(除非 startService() 也啟動了該服務(wù))梳码。
通常情況下我們應(yīng)該在客戶端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 時刻設(shè)置綁定和取消綁定操作隐圾,以便控制綁定狀態(tài)下的Service,一般有以下兩種情況:
- 如果只需要在 Activity 可見時與服務(wù)交互掰茶,則應(yīng)在 onStart() 期間綁定暇藏,在 onStop() 期間取消綁定。
- 如果希望 Activity 在后臺停止運行狀態(tài)下仍可接收響應(yīng)濒蒋,則可在 onCreate() 期間綁定盐碱,在 onDestroy() 期間取消綁定。需要注意的是沪伙,這意味著 Activity 在其整個運行過程中(甚至包括后臺運行期間)都需要使用服務(wù)瓮顽,因此如果服務(wù)位于其他進程內(nèi),那么當(dāng)提高該進程的權(quán)重時围橡,系統(tǒng)很可能會終止該進程暖混。
通常情況下(注意),切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定翁授,因為每一次生命周期轉(zhuǎn)換都會發(fā)生這些回調(diào)拣播,這樣反復(fù)綁定與解綁是不合理的。此外黔漂,如果應(yīng)用內(nèi)的多個 Activity 綁定到同一服務(wù)诫尽,并且其中兩個 Activity 之間發(fā)生了轉(zhuǎn)換,則如果當(dāng)前 Activity 在下一次綁定(恢復(fù)期間)之前取消綁定(暫停期間)炬守,系統(tǒng)可能會銷毀服務(wù)并重建服務(wù)牧嫉,因此服務(wù)的綁定不應(yīng)該發(fā)生在 Activity 的 onResume() 和 onPause()中。
我們應(yīng)該始終捕獲 DeadObjectException 異常减途,該異常是在連接中斷時引發(fā)的酣藻,表示調(diào)用的對象已死亡,也就是Service對象已銷毀鳍置,這是遠(yuǎn)程方法引發(fā)的唯一異常辽剧,DeadObjectException繼承自RemoteException,因此我們也可以捕獲RemoteException異常税产。
應(yīng)用組件(客戶端)可通過調(diào)用 bindService() 綁定到服務(wù)怕轿,Android 系統(tǒng)隨后調(diào)用服務(wù)的 onBind() 方法偷崩,該方法返回用于與服務(wù)交互的 IBinder,而該綁定是異步執(zhí)行的撞羽。
混合性交互的后臺服務(wù)
雖然服務(wù)的狀態(tài)有啟動和綁定兩種阐斜,但實際上一個服務(wù)可以同時是這兩種狀態(tài),也就是說诀紊,它既可以是啟動服務(wù)(以無限期運行)谒出,也可以是綁定服務(wù)。
??有點需要注意的是Android系統(tǒng)僅會為一個Service創(chuàng)建一個實例對象邻奠,所以不管是啟動服務(wù)還是綁定服務(wù)笤喳,操作的是同一個Service實例。
??既要宿主解除綁定碌宴,又要條用停止服務(wù)杀狡,該服務(wù)才會銷毀。
前臺服務(wù)
由于后臺服務(wù)優(yōu)先級相對比較低贰镣,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的情況下捣卤,它就有可能會被回收掉,所以前臺服務(wù)就是來彌補這個缺點的八孝,它可以一直保持運行狀態(tài)而不被系統(tǒng)回收。
前臺服務(wù)被認(rèn)為是用戶主動意識到的一種服務(wù)鸠项,因此在內(nèi)存不足時干跛,系統(tǒng)也不會考慮將其終止。
??前臺服務(wù)必須為狀態(tài)欄提供通知祟绊,狀態(tài)欄位于“正在進行”標(biāo)題下方楼入,這意味著除非服務(wù)停止或從前臺刪除,否則不能清除通知牧抽。例如將從服務(wù)播放音樂的音樂播放器設(shè)置為在前臺運行嘉熊,這是因為用戶明確意識到其操作。 狀態(tài)欄中的通知可能表示正在播放的歌曲扬舒,并允許用戶啟動 Activity 來與音樂播放器進行交互阐肤。
??如果需要設(shè)置服務(wù)運行于前臺, 我們該如何才能實現(xiàn)呢讲坎?Android官方給我們提供了兩個方法孕惜,分別是startForeground()和stopForeground(),這兩個方式解析如下:
-
startForeground(int id, Notification notification)
該方法的作用是把當(dāng)前服務(wù)設(shè)置為前臺服務(wù)晨炕,其中id參數(shù)代表唯一標(biāo)識通知的整型數(shù)衫画,需要注意的是提供給 startForeground() 的整型 ID 不得為 0,而notification是一個狀態(tài)欄的通知瓮栗。 -
stopForeground(boolean removeNotification)
該方法是用來從前臺刪除服務(wù)削罩,此方法傳入一個布爾值瞄勾,指示是否也刪除狀態(tài)欄通知,true為刪除弥激。 注意該方法并不會停止服務(wù)进陡。 但是,如果在服務(wù)正在前臺運行時將其停止秆撮,則通知也會被刪除四濒。
下面我們結(jié)合一個簡單案例來使用以上兩個方法
① 服務(wù)端:創(chuàng)建服務(wù)類
ForegroundService代碼如下:
在ForegroundService類中,創(chuàng)建了一個notification的通知职辨,并通過啟動Service時傳遞過來的參數(shù)判斷是啟動前臺服務(wù)還是關(guān)閉前臺服務(wù)盗蟆,最后在onDestroy方法被調(diào)用時,也應(yīng)該移除前臺服務(wù)舒裤。
package com.zejian.ipctest.foregroundService;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import com.zejian.ipctest.R;
/* 啟動前臺服務(wù)Demo */
public class ForegroundService extends Service {
//id不可設(shè)置為0,否則不能設(shè)置為前臺service
private static final int NOTIFICATION_DOWNLOAD_PROGRESS_ID = 0x0001;
private boolean isRemove=false;//是否需要移除
//Notification
public void createNotification(){
//使用兼容版本
NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
//設(shè)置狀態(tài)欄的通知圖標(biāo)
builder.setSmallIcon(R.mipmap.ic_launcher);
//設(shè)置通知欄橫條的圖標(biāo)
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.screenflash_logo));
//禁止用戶點擊刪除按鈕刪除
builder.setAutoCancel(false);
//禁止滑動刪除
builder.setOngoing(true);
//右上角的時間顯示
builder.setShowWhen(true);
//設(shè)置通知欄的標(biāo)題內(nèi)容
builder.setContentTitle("I am Foreground Service!!!");
//創(chuàng)建通知
Notification notification = builder.build();
//設(shè)置為前臺服務(wù)
startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int i=intent.getExtras().getInt("cmd");
if(i==0){
if(!isRemove) {
createNotification();
}
isRemove=true;
}else {
//移除前臺服務(wù)
if (isRemove) {
stopForeground(true);
}
isRemove=false;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//移除前臺服務(wù)
if (isRemove) {
stopForeground(true);
}
isRemove=false;
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
② 配置服務(wù)
...
③ 客戶端:
以下是ForegroundActivity的實現(xiàn):
package com.zejian.ipctest.foregroundService;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
public class ForegroundActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_foreground);
Button btnStart= (Button) findViewById(R.id.startForeground);
Button btnStop= (Button) findViewById(R.id.stopForeground);
final Intent intent = new Intent(this,ForegroundService.class);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intent.putExtra("cmd",0);//0,開啟前臺服務(wù),1,關(guān)閉前臺服務(wù)
startService(intent);
}
});
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intent.putExtra("cmd",1);//0,開啟前臺服務(wù),1,關(guān)閉前臺服務(wù)
startService(intent);
}
});
}
}
代碼比較簡單喳资,我們直接運行程序看看結(jié)果:
當(dāng)我們將該程序退出并殺掉的時候,通過設(shè)置->應(yīng)用->選擇正在運行中的應(yīng)用腾供,我們可以發(fā)現(xiàn)仆邓,我們的程序退出殺掉了,而服務(wù)還在進行著
IntentService
IntentService是專門用來解決Service中不能執(zhí)行耗時操作這一問題的伴鳖,創(chuàng)建一個IntentService也很簡單节值,只要繼承IntentService并覆寫onHandlerIntent函數(shù),在該函數(shù)中就可以執(zhí)行耗時操作了
public class TheIntentService extends IntentService {
public TheIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
//在這里執(zhí)行耗時操作
}
}
AccessibilityService無障礙服務(wù)
關(guān)于AccessibilityService無障礙服務(wù)的使用和實例榜聂,可以見我另一篇博客:Android進階——學(xué)習(xí)AccessibilityService實現(xiàn)微信搶紅包插件
系統(tǒng)服務(wù)
系統(tǒng)服務(wù)提供了很多便捷服務(wù)搞疗,可以查詢Wifi、網(wǎng)絡(luò)狀態(tài)须肆、查詢電量匿乃、查詢音量、查詢包名豌汇、查詢Application信息等等等相關(guān)多的服務(wù)幢炸,具體大家可以自信查詢文檔,這里舉例幾個常見的服務(wù)
判斷Wifi是否開啟
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
boolean enabled = wm.isWifiEnabled();
需要權(quán)限
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
獲取系統(tǒng)最大音量
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
int max = am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM);
獲取當(dāng)前音量
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
int current = am.getStreamMaxVolume(AudioManager.STREAM_RING);
判斷網(wǎng)絡(luò)是否有連接
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
boolean isAvailable = info.isAvailable();
需要權(quán)限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- ?
如何保證服務(wù)不被殺死
onStartCommand方法拒贱,返回START_STICKY或START_REDELIVER_INTENT
該值表示服務(wù)在內(nèi)存資源緊張時被殺死后宛徊,在內(nèi)存資源足夠時再恢復(fù)。
【結(jié)論】 手動返回START_STICKY逻澳,親測當(dāng)service因內(nèi)存不足被kill岩调,當(dāng)內(nèi)存又有的時候,service又被重新創(chuàng)建赡盘,比較不錯号枕,但是不能保證任何情況下都被重建,比如進程被干掉了....
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
將服務(wù)設(shè)為前臺服務(wù)startForeground
可以使用startForeground 將service放到前臺狀態(tài)陨享。這樣在低內(nèi)存時被kill的幾率會低一些葱淳。
【結(jié)論】如果在極度極度低內(nèi)存的壓力下钝腺,該service還是會被kill掉,并且不一定會restart赞厕。
在onStartCommand方法內(nèi)添加如下代碼:
Notification notification = new Notification(R.drawable.ic_launcher,
getString(R.string.app_name), System.currentTimeMillis());
PendingIntent pendingintent = PendingIntent.getActivity(this, 0,
new Intent(this, AppMain.class), 0);
notification.setLatestEventInfo(this, "uploadservice", "請保持程序在后臺運行", pendingintent);
startForeground(0x0001, notification);
注意在onDestroy里還需要stopForeground(true)艳狐,運行時在下拉列表會看到自己的APP在:
開啟兩個服務(wù),相互監(jiān)聽皿桑,相互啟動
服務(wù)A監(jiān)聽B的廣播來啟動B毫目,服務(wù)B監(jiān)聽A的廣播來啟動A。這里給出第一種方式的代碼實現(xiàn)如下:
package com.zejian.ipctest.neverKilledService;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
/* 用戶通過 settings -> Apps -> Running -> Stop 方式殺死Service */
public class ServiceKilledByAppStop extends Service{
private BroadcastReceiver mReceiver;
private IntentFilter mIF;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Intent a = new Intent(ServiceKilledByAppStop.this, ServiceKilledByAppStop.class);
startService(a);
}
};
mIF = new IntentFilter();
//自定義action
mIF.addAction("com.restart.service");
//注冊廣播接者
registerReceiver(mReceiver, mIF);
}
@Override
public void onDestroy() {
super.onDestroy();
Intent intent = new Intent();
intent.setAction("com.restart.service");
//發(fā)送廣播
sendBroadcast(intent);
unregisterReceiver(mReceiver);
}
}
onDestroy方法里重啟service
service +broadcast 方式诲侮,就是當(dāng)service走ondestory的時候镀虐,發(fā)送一個自定義的廣播,當(dāng)收到廣播的時候沟绪,重新啟動service刮便;
【結(jié)論】當(dāng)使用類似口口管家等第三方應(yīng)用或是在setting里-應(yīng)用-強制停止時,APP進程可能就直接被干掉了绽慈,onDestroy方法都進不來恨旱,所以還是無法保證。
<receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="com.dbjtech.waiqin.destroy" />//這個就是自定義的action
</intent-filter>
</receiver>
在onDestroy時:
@Override
public void onDestroy() {
stopForeground(true);
Intent intent = new Intent("com.dbjtech.waiqin.destroy");
sendBroadcast(intent);
super.onDestroy();
}
在BootReceiver里
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.dbjtech.waiqin.destroy")) {
//TODO
//在這里寫重新啟動service的相關(guān)操作
startUploadService(context);
}
}
}
也可以直接在onDestroy()里startService
@Override
public void onDestroy() {
Intent sevice = new Intent(this, MainService.class);
this.startService(sevice);
super.onDestroy();
}
監(jiān)聽系統(tǒng)廣播判斷Service狀態(tài)
通過系統(tǒng)的一些廣播坝疼,比如:手機重啟搜贤、界面喚醒、應(yīng)用狀態(tài)改變等等監(jiān)聽并捕獲到钝凶,然后判斷我們的Service是否還存活入客,別忘記加權(quán)限。
【結(jié)論】這也能算是一種措施腿椎,不過感覺監(jiān)聽多了會導(dǎo)致Service很混亂,帶來諸多不便
<receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
<action android:name="com.dbjtech.waiqin.destroy" />
</intent-filter>
</receiver>
BroadcastReceiver中:
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
System.out.println("手機開機了....");
startUploadService(context);
}
if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
startUploadService(context);
}
}
Application加上Persistent屬性
看Android的文檔知道夭咬,當(dāng)進程長期不活動啃炸,或系統(tǒng)需要資源時,會自動清理門戶卓舵,殺死一些Service南用,和不可見的Activity等所在的進程。但是如果某個進程不想被殺死(如數(shù)據(jù)緩存進程掏湾,或狀態(tài)監(jiān)控進程裹虫,或遠(yuǎn)程服務(wù)進程),可以這么做:android:persistent="true"
【結(jié)論】據(jù)說這個屬性不能亂設(shè)置融击,不過設(shè)置后筑公,的確發(fā)現(xiàn)優(yōu)先級提高不少,或許是相當(dāng)于系統(tǒng)級的進程尊浪,但是還是無法保證存活
<application
android:name="com.test.Application"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:theme="@style/AppTheme" >
</application>
提升service優(yōu)先級(可能無效)
在AndroidManifest.xml文件中對于intent-filter可以通過android:priority = "1000"這個屬性設(shè)置最高優(yōu)先級匣屡,1000是最高值封救,如果數(shù)字越小則優(yōu)先級越低,同時適用于廣播捣作。
【結(jié)論】目前看來誉结,priority這個屬性貌似只適用于broadcast,對于Service來說可能無效
<service
android:name="com.dbjtech.acbxt.waiqin.UploadService"
android:enabled="true" >
<intent-filter android:priority="1000" >
<action android:name="com.dbjtech.myservice" />
</intent-filter>
</service>
服務(wù)Service與線程Thread的區(qū)別
-
兩者概念的迥異
- Thread 是程序執(zhí)行的最小單元券躁,它是分配CPU的基本單位惩坑,android系統(tǒng)中UI線程也是線程的一種,當(dāng)然Thread還可以用于執(zhí)行一些耗時異步的操作也拜。
- Service是Android的一種機制以舒,服務(wù)是運行在主線程上的,它是由系統(tǒng)進程托管搪泳。它與其他組件之間的通信類似于client和server稀轨,是一種輕量級的IPC通信,這種通信的載體是binder岸军,它是在linux層交換信息的一種IPC奋刽,而所謂的Service后臺任務(wù)只不過是指沒有UI的組件罷了。
-
兩者的執(zhí)行任務(wù)迥異
- 在android系統(tǒng)中艰赞,線程一般指的是工作線程(即后臺線程)佣谐,而主線程是一種特殊的工作線程,它負(fù)責(zé)將事件分派給相應(yīng)的用戶界面小工具方妖,如繪圖事件及事件響應(yīng)狭魂,因此為了保證應(yīng)用 UI 的響應(yīng)能力主線程上不可執(zhí)行耗時操作。如果執(zhí)行的操作不能很快完成党觅,則應(yīng)確保它們在單獨的工作線程執(zhí)行雌澄。
- Service 則是android系統(tǒng)中的組件,一般情況下它運行于主線程中杯瞻,因此在Service中是不可以執(zhí)行耗時操作的镐牺,否則系統(tǒng)會報ANR異常,之所以稱Service為后臺服務(wù)魁莉,大部分原因是它本身沒有UI睬涧,用戶無法感知(當(dāng)然也可以利用某些手段讓用戶知道),但如果需要讓Service執(zhí)行耗時任務(wù)旗唁,可在Service中開啟單獨線程去執(zhí)行畦浓。
-
兩者使用場景
- 當(dāng)要執(zhí)行耗時的網(wǎng)絡(luò)或者數(shù)據(jù)庫查詢以及其他阻塞UI線程或密集使用CPU的任務(wù)時,都應(yīng)該使用工作線程(Thread)检疫,這樣才能保證UI線程不被占用而影響用戶體驗讶请。
- 在應(yīng)用程序中,如果需要長時間的在后臺運行屎媳,而且不需要交互的情況下秽梅,使用服務(wù)抹蚀。比如播放音樂,通過Service+Notification方式在后臺執(zhí)行同時在通知欄顯示著企垦。
-
兩者的最佳使用方式
在大部分情況下环壤,Thread和Service都會結(jié)合著使用,比如下載文件钞诡,一般會通過Service在后臺執(zhí)行+Notification在通知欄顯示+Thread異步下載郑现,再如應(yīng)用程序會維持一個Service來從網(wǎng)絡(luò)中獲取推送服務(wù)。在Android官方看來也是如此荧降,所以官網(wǎng)提供了一個Thread與Service的結(jié)合來方便我們執(zhí)行后臺耗時任務(wù)接箫,它就是IntentService,(如果想更深入了解IntentService朵诫,可以看博主的另一篇文章:Android 多線程之IntentService 完全詳解)辛友,當(dāng)然 IntentService并不適用于所有的場景,但它的優(yōu)點是使用方便剪返、代碼簡潔废累,不需要我們創(chuàng)建Service實例并同時也創(chuàng)建線程,某些場景下還是非常贊的脱盲!由于IntentService是單個worker thread邑滨,所以任務(wù)需要排隊,因此不適合大多數(shù)的多任務(wù)情況钱反。
-
兩者的真正關(guān)系
- 兩者沒有半毛錢關(guān)系掖看。
Android 中服務(wù)的調(diào)用實例(★★★)
電話竊聽器(★★★)
需求:
開啟一個服務(wù)監(jiān)聽用戶電話,當(dāng)電話被接通時開始錄音面哥,電話掛斷時停止錄音哎壳。
① 新建一個 Android 工程《電話竊聽器》,包名:com.itheima.listenCall尚卫。
② 在 src 目錄下新創(chuàng)建一個 MyService 類繼承 Service 類归榕,在該類中實現(xiàn)核心業(yè)務(wù)方法, 實現(xiàn)監(jiān)聽電話焕毫,以及完成錄音的功能。
public class MyService extends Service {
/* 綁定服務(wù)時調(diào)用 */
@Override
public IBinder onBind(Intent intent) {
return null;
}
/* 服務(wù)被創(chuàng)建時調(diào)用 */
@Override
public void onCreate() {
super.onCreate();
System.out.println("服務(wù)已經(jīng)被創(chuàng)建驶乾。");
TelephonyManager telephonyManager = (TelephonyManager)
getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(new MyPhoneListener(), PhoneStateListener.LISTEN_CALL_STATE);
}
class MyPhoneListener extends PhoneStateListener {
MediaRecorder recorder;
boolean isCalling = false;
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
if (TelephonyManager.CALL_STATE_OFFHOOK == state) {
System.out.println("開始通話邑飒。。级乐。");
isCalling = true;
//新建一個 MediaRecorder 對象
recorder = new MediaRecorder();
//設(shè)置聲音來源
recorder.setAudioSource(AudioSource.MIC);
//設(shè)置輸入格式
recorder.setOutputFormat(OutputFormat.THREE_GPP);
//格式化日期疙咸,作為文件名稱
SimpleDateFormat format = new
SimpleDateFormat("yyyy-MM-dd_hh_mm_ss");
String date = format.format(new Date());
//設(shè)置輸出到的文件
recorder.setOutputFile(getFilesDir() + "/" + date + ".3gp");
//設(shè)置音頻編碼
recorder.setAudioEncoder(AudioEncoder.DEFAULT);
try {
//錄音準(zhǔn)備
recorder.prepare();
//錄音開始
recorder.start();
} catch (Exception e) {
e.printStackTrace();
}
} else if (TelephonyManager.CALL_STATE_IDLE == state && isCalling) {
recorder.stop();
recorder.release();
isCalling = false;
System.out.println("錄音結(jié)束。");
}
}
}
}
③ 在 MainActivity 類中啟動 Service
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent service = new Intent();
service.setClass(this, MyService.class);
startService(service);
}
④ 在 AndroidManifest.xml 文件中注冊該 Service
Tip:在 Android 中四大組件都需要在清單文件中進行注冊风科。
<service android:name="com.itheima.listenCall.MyService"/>
⑤ 在 AndroidManifest.xml 中添加權(quán)限撒轮。
Tip:在該案例中乞旦,我們把錄音文件存儲在 data/data/com.itheima.listenCall/files 目錄中,因此不需要聲明外部存儲的寫權(quán)限题山。
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
⑥ 將本工程部署到模擬器中兰粉,然后通過 DDMS 給該模擬器撥打電話,當(dāng)我們接聽一段時間并關(guān)閉后顶瞳,發(fā)現(xiàn)控制臺成功打印出了錄音信息玖姑。
打開 data/data/com.itheima.listenCall/files 目錄發(fā)現(xiàn),錄音文件被成功保存了慨菱。我們將該文件導(dǎo)出到電腦上焰络,發(fā)現(xiàn)聲音可以正常播放。
本地服務(wù)調(diào)用音樂播放器
-
① 新創(chuàng)建一個 Android 工程《音樂播放器》符喝,包名:com.itheima.musicPlayer闪彼。
在 res 目錄下新建一個文件夾 raw(名字必須為 raw,約定大于配置的原則)协饲,然后在raw 目錄中拷貝進一個音樂文件畏腕,注意文件名必須遵循 Android 資源文件的命名規(guī)則。 目錄結(jié)構(gòu)如下圖:
② 在 src 目錄下囱稽,新建一個 MediaService 繼承 Service 類郊尝,在該類中實現(xiàn)核心服務(wù)的方法。
public class MediaService extends Service {
//聲明一個 MediaPlayer 對象
private MediaPlayer player;
@Override
public IBinder onBind(Intent intent) {
System.out.println("服務(wù)返回 MediaController 對象了......");
return new MediaController();
}
@Override
public void onCreate() {
System.out.println("音樂服務(wù)已經(jīng)被創(chuàng)建......");
//初始化音樂播放器
player = MediaPlayer.create(this, R.raw.m);
}
//自定義一個 Binder 對象战惊,Binder 是 IBinder 接口的子類
class MediaController extends Binder {
public void play() {
player.start();
}
public void pause() {
player.pause();
}
public void stop() {
player.stop();
}
//獲取音樂的總時長
public int getDuration() {
return player.getDuration();
}
//獲取當(dāng)前播放位置
public int getCurrentPostion() {
return player.getCurrentPosition();
}
//判斷是否在播放
public boolean isPlaying() {
return player.isPlaying();
}
}
}
- ③ 布局文件activity_main.xml 清單如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="音樂播放器"
android:textColor="#ff0000"
android:textSize="28sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/bt_play"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="play"
android:text="播放" />
<Button
android:id="@+id/bt_pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="pause"
android:text="暫停" />
</LinearLayout>
</LinearLayout>
- ④ MainActivity流昏,在該類中完成業(yè)務(wù)的控制,代碼清單如下:
public class MainActivity extends Activity {
//聲明進度條
private ProgressBar pb;
//聲明自定義的 MediaController 對象
private MediaController mediaController;
private boolean isRunning;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//實例化進度條
pb = (ProgressBar) findViewById(R.id.pb);
//創(chuàng)建一個用于啟動服務(wù)的顯示意圖吞获,指向我們自定義的 MediaService 類
Intent intent = new Intent(this, MediaService.class);
//綁定服務(wù)况凉,同時服務(wù)開啟,如果成功則返回 true 否則返回 false
isRunning = bindService(intent, new MediaConnection(), BIND_AUTO_CREATE);
if (isRunning) {
System.out.println("音樂播放器服務(wù)綁定成功各拷!");
} else {
System.out.println("音樂播放器服務(wù)綁定失數笕蕖!");
}
}
//用于循環(huán)更新當(dāng)前播放進度
private void updateProgressBar() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
SystemClock.sleep(400);
pb.setProgress(mediaController.getCurrentPostion());
if (mediaController.getDuration() == mediaController.getCurrentPostion()){
break;
}
}
}
}).start();
}
public void play(View view) {
if (mediaController != null) {
//如果音樂正在播放則不能再次播放
if (mediaController.isPlaying()) {
Toast.makeText(this, "音樂播放中", 0).show();
return;
} else {
mediaController.play();
Toast.makeText(this, "音樂開是播放", 0).show();
}
}
}
//暫停
public void pause(View view) {
if (mediaController != null) {
mediaController.pause();
}
}
//停止
public void stop(View view) {
if (mediaController != null) {
//停止的時候?qū)⑦M度條設(shè)置為初始位置
pb.setProgress(0);
mediaController.stop();
Toast.makeText(this, "音樂已經(jīng)關(guān)閉烤黍!", 0).show();
}
}
//新建一個 ServiceConnection 類
class MediaConnection implements ServiceConnection {
/* 當(dāng) service 被綁定的時候回調(diào)該函數(shù) */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//返回的 IBinder 對象其實就是我們自定義的 MediaController 類對象
}
/**
* mediaController = (MediaController) service;
* //給進度條設(shè)置最大值
* pb.setMax(mediaController.getDuration());
* //更新進度條
* updateProgressBar(); System.out.println("服務(wù)已經(jīng)連接......");
* 服務(wù)被關(guān)閉或者斷開的時候調(diào)用該方法
*/
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("服務(wù)已經(jīng)斷開......");
}
}
}
- ⑤ 在 AndroidManifest.xml 中注冊Service
<service android:name="com.itheima.musicPlayer.MediaService"/>
-
⑥ 將工程部署到模擬器上知市,點擊播放,發(fā)現(xiàn)成功播放了音樂速蕊。點擊暫停嫂丙,發(fā)現(xiàn)音樂暫停了,然后點擊播放规哲,音樂再次響起跟啤。點擊停止,問題來了,我們發(fā)現(xiàn)點擊停止后再次點擊播放音樂沒能再次播放隅肥,因為這里面直接調(diào)用MediaPlayer 的 stop 方法是有 bug 的竿奏。因此 為了解決這樣的問題,我們應(yīng)該將停止調(diào)用層 pause 方法腥放,同時只需調(diào)用 MediaPlayer 的seekTo(int)方法將音樂設(shè)置到開始位置泛啸。
引用:
Android四大組件——Service后臺服務(wù)、前臺服務(wù)捉片、IntentService平痰、跨進程服務(wù)、無障礙服務(wù)伍纫、系統(tǒng)服務(wù)
關(guān)于Android Service真正的完全詳解宗雇,你需要知道的一切
Android開發(fā)之如何保證Service不被殺掉(broadcast+system/app)