上篇我們講解了Android中的5中等級的進(jìn)程,分別是:前臺進(jìn)程、可見進(jìn)程、服務(wù)進(jìn)程、后臺進(jìn)程、空進(jìn)程。系統(tǒng)會按照內(nèi)存的需求先殺死等級較低的進(jìn)程。其中,后臺進(jìn)程、空進(jìn)程極容易被殺死,且殺死后不會重啟。但是今天將的服務(wù)進(jìn)程卻不同, 服務(wù)進(jìn)程被系統(tǒng)殺死后,內(nèi)存充足時會自動重新啟動,適合做一些后臺的操作。今天我們就來講講Service。
這篇文章,我打算先直接從Google提供的Service的生命周期講起,根據(jù)生命周期的各種回調(diào)方法一步步深入。
Service的生命周期
Service可以看成一種沒有前臺界面的Activity,生命周期與Activity有點(diǎn)類似。
看看Service很NB啊,((o)/),既然可以做到被系統(tǒng)殺死后在內(nèi)存充足的情況下重啟,那么必有其特殊之處。不同的使用方式,有著不同的生命周期。現(xiàn)在看不懂沒關(guān)系,后面會一個個講的。這只是個藥引子~
服務(wù)有兩種使用方法:
- 方式一:調(diào)用startService創(chuàng)建啟動服務(wù)
- 方式二:調(diào)用bindService創(chuàng)建綁定服務(wù)
無論使用哪一個方法,講解這個之前都要先看看怎么創(chuàng)建一個Service。
聲明一個Service
- 清單文件配置:
<service android:name=".RemoteService"/>
為了不讓其他應(yīng)用調(diào)用到我們自己的Service,為了安全,我們不要在Service中添加Intentfilter過濾器。也就是說盡量使用顯示啟動方法或者綁定Service。
更安全的防護(hù)罩是在Service的清單文件中添加android:exported="false"
這樣就將該Service與外界的APP隔絕起來,其他應(yīng)用無權(quán)訪問我們的服務(wù)。
- 繼承自Service或者其子類
public class RemoteService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
經(jīng)過上面兩個操作,我們便創(chuàng)建好了一個Service。
最顯眼的莫過于需要實(shí)現(xiàn)一個IBinder onBind(Intent intent)
,這個方法,現(xiàn)在先不講。先來看看如果啟動一個服務(wù)。
聲明過Service之后,下面就分創(chuàng)建啟動的服務(wù)和創(chuàng)建綁定的服務(wù)兩個來講解了,最后再總結(jié)講解總的生命周期。
創(chuàng)建啟動的服務(wù)
分兩部分講,啟動服務(wù)和停止服務(wù)。
啟動服務(wù)
還是先看看這部分生命周期方法。
通過startService(intent)創(chuàng)建啟動的服務(wù)
public void startService(View view){
Intent intent = new Intent();
intent.setClass(this, RemoteService.class);
intent.putExtra("sendData", "通過intent保存MainActivity組件中要傳遞的數(shù)據(jù)");
startService(intent);//啟動RemoteService服務(wù)
}
前面講過,可以將Service看出一個沒有前臺界面的Activity,看看現(xiàn)在在組件中(這里是Activity)啟動一個Service也是如啟動一個Activity一般如此簡單。
講解創(chuàng)建啟動的Service的生命周期方法
先看下演示,看下整體概況:
public class RemoteService extends Service {
@Override
public void onCreate() {//Service被創(chuàng)建時回調(diào)
super.onCreate();
Log.i("hui", "------onCreate-----");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {//Service被啟動時回調(diào)
Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {//Service被銷毀時回調(diào)
super.onDestroy();
Log.i("hui", "------onDestroy-----");
}
@Override
public IBinder onBind(Intent intent) { return null; }}
下面看看這種情況下的生命周期方法的具體流程。
onCreate()
當(dāng)Service被創(chuàng)建的時候會回調(diào)這個方法。第一次啟動Service的時候,由于Service沒有被創(chuàng)建過,故:先創(chuàng)建Service,然后再啟動Service。所以:onCreater->onStartCommand。只有當(dāng)Service沒有創(chuàng)建的時候才會回調(diào)這個方法,一旦Service被創(chuàng)建后再次startService啟動Service也不會再次回調(diào)這個方法。
onStartCommand
返回值
默認(rèn)返回super.onStartCommand(intent, flags, startId)
,看下這個方法。
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
默認(rèn)情況mStartCompatibility 為false,返回START_STICKY。其實(shí),這個返回值有三種。
還記得前面講解Service的生命周期的時候說過,Service的生命周期很NB,因?yàn)樗幌到y(tǒng)殺死后,在內(nèi)存充足的情況下回重新啟動。
這里的重新啟動,有三種重新啟動方式,上面的返回的值就對應(yīng)三種重新啟動方式之一。下面就來看看者這三個具體的返回值。看起來很煩人,不過都很好理解的,實(shí)在不理解沒關(guān)系,我們只需要返回默認(rèn)的就可以了。
- START_NO_STICKY
sticky是“粘性的”,這里指的是非粘性啟動。下面翻譯Google文檔的解釋:
如果返回START_NOT_STICKY,表示當(dāng)Service運(yùn)行的進(jìn)程被Android系統(tǒng)強(qiáng)制殺掉之后,不會重新創(chuàng)建該Service,當(dāng)然如果在其被殺掉之后一段時間又調(diào)用了startService,那么該Service又將被實(shí)例化。
那什么情境下返回該值比較恰當(dāng)呢?
如果我們某個Service執(zhí)行的工作被中斷幾次無關(guān)緊要或者對Android內(nèi)存緊張的情況下需要被殺掉且不會立即重新創(chuàng)建這種行為也可接受,那么我們便可將 onStartCommand的返回值設(shè)置為START_NOT_STICKY。舉個例子,某個Service需要定時從服務(wù)器獲取最新數(shù)據(jù):通過一個定時器每隔指定的N分鐘讓定時器啟動Service去獲取服務(wù)端的最新數(shù)據(jù)。當(dāng)執(zhí)行到Service的onStartCommand時,在該方法內(nèi)再規(guī)劃一個N分鐘后的定時器用于再次啟動該Service并開辟一個新的線程去執(zhí)行網(wǎng)絡(luò)操作。假設(shè)Service在從服務(wù)器獲取最新數(shù)據(jù)的過程中被Android系統(tǒng)強(qiáng)制殺掉,Service不會再重新創(chuàng)建,這也沒關(guān)系,因?yàn)樵龠^N分鐘定時器就會再次啟動該Service并重新獲取數(shù)據(jù)。 - START_STICKY(默認(rèn)返回值)
這里指的是粘性啟動。既然粘性啟動,就不會向上面的非粘性啟動那么的容易被干死而不啟動。這種啟動方式,啟動后,如果在某一刻該服務(wù)被干死,那么系統(tǒng)會將這個Service標(biāo)記為" started"狀態(tài),這意味著Service是已經(jīng)啟動的狀態(tài)。然后系統(tǒng)會嘗試著去重新啟動該服務(wù)(try to re-create the service
)的實(shí)例,然后再回調(diào)onStartCommand方法,但是此時intent的數(shù)據(jù)已經(jīng)是null了。所以使用這種方式啟動的服務(wù)在使用intent時需要進(jìn)行非空判斷。
當(dāng)我們使用服務(wù)時不需要intent時,以及可以在任何時間重啟Service都沒問題的話就可以使用這種方式啟動服務(wù)。例如:后臺播放音樂。 - START_REDELIVER_INTENT
redeliver是“再交付”的意思。也就是在交付intent的意思。這是基于START_STICKY啟動方式的一種加強(qiáng),使用這種方式啟動的Service會保留intent的值。
使用場景:需要重啟,需要保留intent。
入?yún)?/h6>
intent
這個intent就是剛才startService(intent)中的intent。在創(chuàng)建啟動的服務(wù)所在的組件(這里是Activity)中如果需要傳遞給Service中數(shù)據(jù),可以將要傳遞的額數(shù)據(jù)放到這個Intent里面。
-
flags
代表了創(chuàng)建啟動Service的方式,是下面三種方式值的一種。這三種方式的值對應(yīng)著上面的三種啟動方式。
- 0
對應(yīng)START_NO_STICKY啟動方式
- START_FLAG_RETRY(0x0002)
對應(yīng)START_STICKY啟動方式
- START_FLAG_REDELIVERY(0x0001)
對應(yīng)START_REDELIVER_INTENT啟動方式
startId
這個代表著每次創(chuàng)建啟動Service的唯一身份ID,每次startService,這個startId均不相同。(可以從上面的圖“通過startService創(chuàng)建啟動的Service生命周期”看出來)。這個用于處理多個onStartCommand請求時,關(guān)閉Service時使用的。下面會對這個使用詳細(xì)講解的。
總結(jié)onStartCommand方法:
- 這個方法在第一次創(chuàng)建啟動Service的時候,由于沒有創(chuàng)建過Service,所以會onCreate->onStartCommand。
- 第二次乃至以后再重復(fù)啟動Service的時候,Service已經(jīng)被創(chuàng)建過,無需再被創(chuàng)建了。故:只回調(diào)了onStartCommand。所以,創(chuàng)建啟動Service的時候,每次啟動都會回調(diào)onStartCommand方法
onDestory
- 服務(wù)停止的時候會回調(diào)onDestory方法。
總結(jié)創(chuàng)建啟動服務(wù):
- 其他組件可以通過startService(intent)創(chuàng)建啟動服務(wù)。該服務(wù)被啟動后,服務(wù)與啟動他的組件沒有任何關(guān)系了。即使該組件被干掉,該Service任然不會被干掉。
- 這種方式的服務(wù)一旦被啟動后,基本不會被關(guān)閉。除非不足以支持優(yōu)先級較高的進(jìn)程(前臺進(jìn)程、可見進(jìn)程)繼續(xù)運(yùn)行,系統(tǒng)才會停止該服務(wù)。系統(tǒng)一旦停止該服務(wù)后,當(dāng)系統(tǒng)內(nèi)存足夠的時候會再次重啟(具體依賴于onStartCommand的返回值)。但是如果手動關(guān)閉了該服務(wù),該服務(wù)不會再次重啟了(我記得好像是Android4.0以前的時候是會重啟的,Android4.0以后不會重啟了,具體哪個版本忘記了~~~)。
- 可以將要傳遞給Service的數(shù)據(jù)放到Intent里面。服務(wù)被啟動后回調(diào)onStartCommand方法,在這個方法中會給我們剛才傳遞的Intent,然后就可以為所欲為了。
-
Service默認(rèn)情況下不會單獨(dú)啟動一個進(jìn)程或者線程,默認(rèn)情況下與主線程所在的進(jìn)程在同一個進(jìn)程中。所以,不能在Service中進(jìn)行耗時操作,否則會引起ANR(響應(yīng)時間超過5s)。鑒于這種情況,強(qiáng)烈建議在服務(wù)啟動后,在onStartCommand方法中開啟一個子線程去做具體的操作。相應(yīng)的操作做完后去關(guān)閉服務(wù),以免于多余的消耗系統(tǒng)資源給用戶帶來不適感。
intent
這個intent就是剛才startService(intent)中的intent。在創(chuàng)建啟動的服務(wù)所在的組件(這里是Activity)中如果需要傳遞給Service中數(shù)據(jù),可以將要傳遞的額數(shù)據(jù)放到這個Intent里面。
flags
代表了創(chuàng)建啟動Service的方式,是下面三種方式值的一種。這三種方式的值對應(yīng)著上面的三種啟動方式。
- 0
對應(yīng)START_NO_STICKY啟動方式 - START_FLAG_RETRY(0x0002)
對應(yīng)START_STICKY啟動方式 - START_FLAG_REDELIVERY(0x0001)
對應(yīng)START_REDELIVER_INTENT啟動方式
startId
這個代表著每次創(chuàng)建啟動Service的唯一身份ID,每次startService,這個startId均不相同。(可以從上面的圖“通過startService創(chuàng)建啟動的Service生命周期”看出來)。這個用于處理多個onStartCommand請求時,關(guān)閉Service時使用的。下面會對這個使用詳細(xì)講解的。
總結(jié)onStartCommand方法:
下面就來講講如何優(yōu)雅的關(guān)閉服務(wù)。
關(guān)閉服務(wù)
關(guān)閉服務(wù)有兩種方法,stopService和stopSelf(),以及stopSelf(int startId)。
stopService
public void stopService(View view){
Intent intent = new Intent();
intent.setClass(this, RemoteService.class);
stopService(intent);//停止RemoteService服務(wù)
}
這種方法適用于第三方組件關(guān)閉該服務(wù)。
stopSelf()
public int onStartCommand(Intent intent, int flags, int startId) {//Service被啟動時回調(diào)
Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
new Thread(){
@Override
public void run() {
super.run();
try {
sleep(6000);//6S后干掉自己
stopSelf();//干掉自己
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
return super.onStartCommand(intent, flags, startId);}
上面例子中,在onStartCommand中開啟一個子線程,6s后調(diào)用stopSelf方法干掉自己,停止服務(wù)。
那么問題來了。如果我要進(jìn)行多次的下載圖片,也就是說會多次調(diào)用onStartCommand方法。但是,不能每次下載完一張關(guān)閉一次,然后新的下載重新打開再重新下載,再停止服務(wù)在重新啟動服務(wù)下載,這是不合理的。
下載完所有圖片后需要關(guān)閉服務(wù)。可是我們不知道每張圖片需要下載圖片多久,不能再這次下載圖片完成后就立即停止服務(wù),而是在關(guān)閉服務(wù)的時候看看在自己下載圖片的過程中是否有新的請求過來,如果沒有新的下載圖片的請求,那么我們就可以關(guān)閉服務(wù)了,這樣就省了一次停止與啟動服務(wù)。
那么就可以這樣做,下載圖片開始的時候,保存一個TAG標(biāo)記。每次有新的下載請求的時候就更改TAG標(biāo)記的值。而這個TAG標(biāo)記是與下載的線程攜帶的。當(dāng)下載圖片的線程結(jié)束后,看看現(xiàn)在的TAG還是不是當(dāng)初的TAG(現(xiàn)在的你還是以前的你么?O(∩_∩)O哈哈~)。如果lastTag != oldTag,說明已經(jīng)有新的請求下載的Service,那么我們就不能關(guān)閉服務(wù)。關(guān)閉服務(wù)就由這個新的線程自己去關(guān)閉,關(guān)閉時候做同樣的判斷。
如果有一個子線程它存儲的lastTag == oldTag,說明在自己下載的過程中沒有新的下載需求。那么這是我就可以停止服務(wù)了。
Google就為我們提供這么一個方法,stopSelf(startId)。拿下面例子講一講:
stopSelf(int startId)
public int onStartCommand(Intent intent, int flags, final int startId) {//Service被啟動時回調(diào)
Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
new Thread(){
@Override
public void run() {
super.run();
try {
sleep(1000 * startId);
stopSelf(startId);//干掉自己
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
每次一個onStartCommand請求的時候,都會傳過來唯一的startId。這個startId被線程攜帶著的。當(dāng)代碼執(zhí)行到stopSelf的時候,進(jìn)行r.getLastStartId != startId(最近啟動的Service的id跟startId進(jìn)行是否相等判斷)的判斷,如果不等于就return掉,從而不會停止服務(wù);如果最近啟動的一個Service的id等于startId,說明在自己線程運(yùn)行的過程中沒有新的onStartCommand請求過來,這時就可以停止掉服務(wù)了。
小結(jié)創(chuàng)建啟動的Service
- 清單文件注冊,繼承Service(沒什么說的,必須的)
- 純啟動Service生命周期onCreate-onStartCommand-onDestory
- 在onStartCommand方法中開啟子線程進(jìn)行估計(jì)耗時的操作
- 子線程結(jié)束后stopSelf關(guān)閉Service(自己關(guān)閉自己)
- 也可通過其他組件stopService(intent)來關(guān)閉指定的Service
對于這些總結(jié)有疑問的,堅(jiān)持重新再看一遍,重新捋一下思路。
創(chuàng)建綁定的Service
我們知道創(chuàng)建啟動的Service后,Service與啟動它的組件不會有任何關(guān)系了。這時的Service就像一個斷了線的風(fēng)箏一樣,不會受我們的控制了。
需求:如果我們需要開發(fā)一個音樂播放器,主頁面有上一首(last)、下一首(next)、播放(play)、暫停(pause)這幾個基本按鈕。
分析:
- 那么,我們能把播放音樂的代碼直接放在MainActivity中么?肯定不行的,那么就需要開啟一個子線程運(yùn)行播放音樂的代碼。由于音樂播放器即使退到后臺,還是要繼續(xù)播音樂的,MainActivity退出的時候,子線程就變成了空進(jìn)程,很容易被殺掉,所以播放音樂的代碼不能放到MainActivity的子線程中。這里就用到了Service。
- 那么把 play方法放到onStartCommand中?肯定不行,由于創(chuàng)建啟動的Service后,Service與啟動它的組件不會有任何關(guān)系,我們拿不到Service的對象,也就沒辦法調(diào)用Service中的play等方法了。。。。
如果我們想使用創(chuàng)建啟動的Service中的方法,是沒用辦法的。怎么辦呢?
這時,就可以使用Service的另一種姿勢了:創(chuàng)建綁定的Service。
那么我們就來看看如何一步步使用Service中的方法。分兩部分講綁定服務(wù)和解綁服務(wù)。
綁定Service
一共有三步,打開冰箱門、把大象塞進(jìn)去、關(guān)不上冰箱門。
- Service中實(shí)現(xiàn)onBind方法:
/** * 綁定的Service必須實(shí)現(xiàn)的方法,返回非null * @param intent * @return */
@Overridepublic IBinder onBind(Intent intent) {
Log.i("hui", "-onBind-intent:"+ intent.getStringExtra("sendData"));
return myBinder;
}
實(shí)現(xiàn)onBind方法有三種解決方案(先使用第一個方案詳細(xì)講解,再講解后面的兩個):
1.使用已經(jīng)實(shí)現(xiàn)了IBinder的Binder子類
2.使用Messenger
3.使用AIDL
- MainActivity中創(chuàng)建ServiceConnection的具體實(shí)現(xiàn)
/** * 服務(wù)連接對象 */
private ServiceConnection serviceConnection = new ServiceConnection() {
/**
* 當(dāng)創(chuàng)建綁定Service與組件成功連接后,此方法會
* 被調(diào)用。
* @param name Service組件名稱
* @param service Service中返回的IBinder對象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("hui", "---------onServiceConnected-----------");
}
/**
* 當(dāng)創(chuàng)建綁定Service與組件意外中斷后,此方法會被調(diào)用。
* @param name
*/ @Override
public void onServiceDisconnected(ComponentName name) {
Log.d("hui", "---------onServiceConnected-----------");
}
};
- MainActivity中綁定Service
public void bindService(View view){
Intent intent = new Intent();
intent.putExtra("sendData", "通過bindService的intent保存MainActivity組件中要傳遞的數(shù)據(jù)");
intent.setClass(this, RemoteService.class);
/**
* Intent service, ServiceConnection conn, int flags
** @param service 包裝了要啟動的Service及要攜帶的信息
* @param conn
* @param flags 啟動Service的發(fā)送,一般為BIND_AUTO_CREATE,表示:如果服務(wù)不存在會自動建
*/
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
分析:使用
bindService(Intent service, @NonNull ServiceConnection conn, @BindServiceFlags int flags)
方法創(chuàng)建綁定的Service。
- ServiceConnection
類似于媒婆,是公公家(MainActivity)和丈母娘家(Service)交流的紐帶,拿到的IBnder就是丈母娘家的閨女。 - onServiceConnected
* 當(dāng)兩家喜結(jié)連理后,也就是當(dāng)創(chuàng)建綁定Service與組件成功連接后,此方法會
* 被調(diào)用。
* @param name Service組件名稱[丈母娘家(Service)名稱]
* @param service 丈母娘家的閨女,Service中返回的IBinder對象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("hui", "---------onServiceConnected-----------");
}
婚姻意外over
public void onServiceDisconnected(ComponentName name) {
Log.d("hui", "---------onServiceConnected-----------");
}
四大組件中,只有Activity、Service、ContentProvider支持綁定Service。
解綁Service
解綁Service十分簡單,干掉兩家人之間的媒婆就行了。
public void unBindService(View view){
unbindService(serviceConnection);
}
當(dāng)?shù)谝淮吸c(diǎn)擊解綁時,unbindService方法解綁了MainActivity與RemoteService之間的紐扣--serviceConnection,之后MainActivity和RemoteService之間沒有任何關(guān)系了。所以當(dāng)?shù)诙卧俅谓饨墪r,crash了。但是stopService卻可以多次被調(diào)用。兩者之間是有區(qū)別的。
如何調(diào)用Service中的方法呢?
在onServiceConnected(ComponentName name, IBinder service)方法中,這里的IBnder對象service就是啟動Service的組件(這里是MainActivity)所連接的綁定的Service中的IBinder onBinder(Intent)方法返回的IBinder對象。既然我們都拿到了Service中的某個對象值(這里是返回的service),那么一切就簡單了。。。
調(diào)用Service中方法思路
- 創(chuàng)建一個實(shí)現(xiàn)了IBinder接口的對象并且返回
- 這個對象中提供一個返回Service對象的方法
- 拿到Service對象,為所欲為
RemoteService
public class RemoteService extends Service {
private final MyBinder myBinder = new MyBinder();
@Override public void onCreate() {//Service被創(chuàng)建時回調(diào)
super.onCreate();
Log.i("hui", "------onCreate-----");
}
/**
*
* @param intent 傳遞過來的包裝的intent數(shù)據(jù)
* @param flags
* @param startId 啟動的Service的唯一標(biāo)記
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, final int startId) {//Service被啟動時回調(diào)
Log.i("hui", "-onStartCommand-intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {//Service被銷毀時回調(diào)
super.onDestroy();
Log.i("hui", "------onDestroy-----");
}
/**
* 綁定的Service必須實(shí)現(xiàn)的方法,返回非null
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
Log.i("hui", "-onBind-intent:"+ intent.getStringExtra("sendData"));
return myBinder;
}
/**
* Binder是實(shí)現(xiàn)了IBinder接口的對象
*/
public class MyBinder extends Binder{
/**
* 拿到Service的對象
* @return
*/
public RemoteService getRemoteService(){
return RemoteService.this;
}
}
public void playMusic() {
Log.i("hui", "--------playMusic------");
}
public void pauseMusic() {
Log.i("hui", "--------pauseMusic------");
}
}
MAinActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindService(View view){
Intent intent = new Intent();
intent.putExtra("sendData", "通過bindService的intent保存MainActivity組件中要傳遞的數(shù)據(jù)"); intent.setClass(this, RemoteService.class);
/**
* Intent service, ServiceConnection conn, int flags
** @param service 包裝了要啟動的Service及要攜帶的信息
* @param conn
* @param flags 啟動Service的發(fā)送,一般為BIND_AUTO_CREATE,表示:如果服務(wù)不存在會自動創(chuàng)建 */
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
//播放
public void play(View view){
if(remoteService != null){
remoteService.playMusic();
}
}
//暫停
public void pause(View view){
if(remoteService != null){
remoteService.pauseMusic();
}
}
private RemoteService remoteService;
/**
* 服務(wù)連接對象
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
/**
* 當(dāng)創(chuàng)建綁定Service與組件成功連接后,此方法會
* 被調(diào)用。
* @param name 組件名稱
* @param service Service中返回的IBinder對象
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("hui", "---------onServiceConnected-----------");
//將拿到的Service中onBInder返回的對象也就是MyBInder對象進(jìn)行強(qiáng)制轉(zhuǎn)換
RemoteService.MyBinder myBInder = (RemoteService.MyBinder) service;
//調(diào)用MyBinder的方法,拿到RemoteService對象
remoteService = myBInder.getRemoteService();
}
/**
* 當(dāng)創(chuàng)建綁定Service與組件意外中斷后,此方法會被調(diào)用。
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("hui", "---------onServiceConnected-----------");
}
};
}
分析步驟:
當(dāng)創(chuàng)建綁定Service后,由于RemoteService的onBinder返回非null,故RemoteService可以通過ServiceConnection與MainActivity成功建立連接,然后回調(diào)ServiceConnection的onServiceConnected方法。
同時,會把RemoteService的onBinder的返回值MyBinder傳遞給MainActivity。拿到service,也就是MyBinder對象,進(jìn)行轉(zhuǎn)型,拿到RemoteService中的MyBinder。
然后我們調(diào)用myBInder.getRemoteService()方法拿到RemoteService的對象,進(jìn)而調(diào)用RemoteService中的方法。
創(chuàng)建綁定服務(wù)的生命周期
在剛才的“拿到Service對象”的GIF圖中,當(dāng)我退出APP的時候,MainActivity被銷毀,這時的RemoteService方法的onDestory方法也被調(diào)用了,說明:MainActivity被銷毀的時候RemoteService也被銷毀了。
所以,創(chuàng)建綁定的Service與其綁定的Service同生共死。也就是說創(chuàng)建綁定的Service的存活與否依賴于其綁定的組件,如果該Service所綁定的所有的組件均已被銷毀,那么該Service就被銷毀了。
服務(wù)的混合啟動
那么對于音樂播放器而言這是萬萬不可的,我Service不能因?yàn)槟憬M件的銷毀就被干掉(組件被銷毀后 ,播放音樂的子線程所在進(jìn)程就變成了空進(jìn)程,很容易被干掉),怎么辦?
使用創(chuàng)建啟動的Service+創(chuàng)建綁定的Service的混合啟動方案{【bindService+startService】}
也就是說我們在MainActivity上綁定Service后,再次啟動Service。這樣,對于創(chuàng)建啟動的Service而言不會因?yàn)榻M件的銷毀而銷毀,創(chuàng)建綁定的Service后,組件可以與Service交互。
playMusic的方法中開啟子線程,模擬播放音樂
public void playMusic() {
new Thread(){
@Override
public void run() {
super.run();
int i = 0;
while (true){
Log.i("hui", "--------playMusic--i=" + (i++));
try {
sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();}
startService+bindService兩個知識點(diǎn):
- 對于startService+bindService可以做一些實(shí)際的可控制的后臺操作,也就是可以在Service中做我們指定的操作。使得組件與Service之間有了紐帶關(guān)聯(lián)。
- 生命周期方法中,單獨(dú)的解綁Service不會銷毀Service,只有解綁加上stopService才會銷毀Service
- 被銷毀后的Service中雖然任然運(yùn)行著播放音樂的代碼但是,這個進(jìn)程已經(jīng)變成了空進(jìn)程,一不留神就被系統(tǒng)干掉了。這與運(yùn)行服務(wù)進(jìn)程是完全不同的。
- Google建議:如果您的服務(wù)僅供本地應(yīng)用使用,不需要跨進(jìn)程工作,則可以實(shí)現(xiàn)自有Binder 類,讓您的客戶端通過該類直接訪問服務(wù)中的公共方法。
上面我們幾乎講解完了綁定Service的整體流程,使用Binder或者子類實(shí)現(xiàn)onBinder方法的,但是這種方法有局限性,不支持跨進(jìn)程通信。那么如果我們要跨進(jìn)程通信,怎么辦呢?這就是開頭講的綁定Service的實(shí)現(xiàn)onBinder方法的方案二(使用Messenger)和方案三(使用AIDL)。
跨進(jìn)程知識補(bǔ)充
一般情況下一個APP的所有組件運(yùn)行在唯一的一個進(jìn)程中,除非你指定他運(yùn)行的進(jìn)程(例如使用“android:process”的屬性,通過這個屬性,我們可以指定某個組件運(yùn)行的進(jìn)程。我們可以通過設(shè)置這個屬性,讓每個組件運(yùn)行在它自己的進(jìn)程中,也可以只讓某些組件共享一個進(jìn)程。),否則都在自己的進(jìn)程中運(yùn)行者的。
兩個APP是運(yùn)行在不同的進(jìn)程中的,默認(rèn)無法共享資源的。如果讓兩個APP之間進(jìn)行通信,就涉及到了跨進(jìn)程通信(IPC)。
使用Messenger跨進(jìn)程通信
總體思想,基于Handler處理方案,利用Handler在Service中創(chuàng)建一個Messenger對象,然后在客戶端(這里是ClientMainActivity)拿到這個Messenger對象,然后使用這個Messenger對象將Message消息發(fā)送給Handler處理。按著這個理解,這種綁定方案理解起來就簡單多了。
在原來的項(xiàng)目中新建一個MessengerService
public class MessengerService extends Service {
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("hui", "--MessengerService-----handleMessage-----");
}
};
//利用Handler構(gòu)建一個Messenger對象
private Messenger message = new Messenger(handler);
@Override
public IBinder onBind(Intent intent) {
return message.getBinder();//返回Messenger中的binder,客戶端拿到這個Messenger中的binder可以構(gòu)建出唯一的Messenger。
}
}
//清單文件
<service android:name=".MessengerService" >
<intent-filter>
<action android:name="com.example.asia.remoteservice.MessengerService"/>
</intent-filter>
</service>
創(chuàng)建第三方APP-Client
public class ClientMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startMessengerService(View view){
Intent intent = new Intent();
//這里注意,MessengerService的清單文件配置action為"com.example.asia.remoteservice.MessengerService"
intent.setAction("com.example.asia.remoteservice.MessengerService");
bindService(intent, conn, BIND_AUTO_CREATE);
}
public void sendMsgService(View view){
if(null != messenger){
Message message = Message.obtain();
try {
//使用messenger,將我們要傳遞的消息放到handler上,
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
private Messenger messenger;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("hui", "----client-----onServiceConnected-----------");
//客戶端拿到這個binder(這個是Messenger中的Binder)可以構(gòu)建出唯一的Messenger。
messenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
與 AIDL 比較
當(dāng)您需要執(zhí)行 IPC 時,為您的接口使用[Messenger] 要比使用 AIDL 實(shí)現(xiàn)它更加簡單,因?yàn)?[Messenger] 會將所有服務(wù)調(diào)用排入隊(duì)列,而純粹的 AIDL 接口會同時向服務(wù)發(fā)送多個請求,服務(wù)隨后必須應(yīng)對多線程處理。
由于對于大多數(shù)應(yīng)用,服務(wù)不需要執(zhí)行多線程處理,因此使用 Messenger可讓服務(wù)一次處理一個調(diào)用。如果您的服務(wù)必須執(zhí)行多線程處理,則應(yīng)使用 AIDL 來定義接口。
---來自Google文檔
好了,到這里為止。回顧一下知識點(diǎn),如有疑問,重新看看:
-
Service兩種使用方式
- 創(chuàng)建啟動Service
- 創(chuàng)建綁定服務(wù)
-
創(chuàng)建啟動Service
- startService
- stopService
- stopSelf
- onStartCommand
-
創(chuàng)建綁定服務(wù)
- 實(shí)現(xiàn)IBinder,繼承Binder
- 使用Messenger
- 使用AIDL
-
生命周期
- 創(chuàng)建啟動Service:onCreate、onStartCommand、onDestory
- 創(chuàng)建綁定Service : onCreate、onBind、onUnbind、onDestory
混合啟動
AIDL
這個單獨(dú)一片講解
對于以上講解的,如有問題,不吝賜教!
END.