Service詳解及應(yīng)用(二)-- 講解應(yīng)用篇

上篇我們講解了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生命周期

看看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ù)。

MainActivity添加兩個按鈕,一個按鈕啟動服務(wù),一個按鈕停止服務(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ù)
}
查看已經(jīng)成功啟動的RemoteService

前面講過,可以將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;    }}

下面看看這種情況下的生命周期方法的具體流程。

通過startService創(chuàng)建啟動的Service生命周期

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)資源給用戶帶來不適感。

下面就來講講如何優(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ù)
}
啟動服務(wù)后再停止服務(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);}
stopSelf,6s后干掉自己

上面例子中,在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);
}
stopSelf(startId)
停止服務(wù)的時候的判斷

每次一個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);
}

創(chuàng)建綁定的Service

分析:使用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);
}
解綁Service

當(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-----------");
        }
    };
}

拿到Service對象

分析步驟:
當(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混合啟動模式

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) {
        }
    };
}
使用Messenger圖示流程
Messenger實(shí)現(xiàn)綁定

與 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.

晚安
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,003評論 2 374

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

  • 前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當(dāng)之處,歡迎私信博主,加以改正!原文鏈接,demo鏈接 Serv...
    PassersHowe閱讀 1,436評論 0 5
  • [文章內(nèi)容來自Developers] Service是一個可以在后臺執(zhí)行長時間運(yùn)行操作而不提供用戶界面的應(yīng)用組件。...
    岳小川閱讀 876評論 0 7
  • 想你時你在天邊想你時你在眼前想你時你在腦海想你時你在心田 每次聽到王菲“傳奇”歌曲里這幾句歌詞,都會被其細(xì)膩的筆觸...
    一心向榮閱讀 406評論 2 0
  • 笑容是一場夢,只是這個夢境中,有了你,所以變得不太孤單,而我們都是夢中的主角,所以心中向往,無所畏懼。 笑容是一首...
    Hui_淺沫閱讀 132評論 0 0
  • 做人要厚道! 2017年8月12日 星期六 晴天 前天午飯的時候,老太太說“以后不用你給老頭買啤酒了!”我問...
    風(fēng)箏2017閱讀 289評論 1 0