Android:Service知識總結

謹以文章記錄學習歷程,如有錯誤還請指明勾徽。

前言

記得我的第一部手機是Nokia的迫横,當時的Symbian系統(tǒng)相較于其他手機最突出的一點就是,Symbian系統(tǒng)支持后臺功能吨凑,畢竟這使得我們可以一邊聽著音樂一邊聊著qq捍歪,在當時這可是很酷的一件事,畢竟Symbian系統(tǒng)出現(xiàn)以前鸵钝,只能用一部mp3聽音樂糙臼,另外再拿一部手機打電話。

Android顯然看到了這個發(fā)光點恩商,自始至終就支持后臺功能变逃,IOS隨著時間的推移也發(fā)現(xiàn)這個功能的重要性,在后續(xù)版本加入了后臺功能怠堪。
本文就將針對這一炫酷的后臺功能(Service)開始講解揽乱,深入總結Service的方方面面的知識


簡介

  • 服務(Service) 是Android實現(xiàn)程序后臺運行的解決方案研叫,是Android四大組件之一锤窑。
  • 適合于執(zhí)行不需要和用戶交互而且需要長期運行的任務。

生命周期

首先放上Google官方文檔給出的生命周期示意圖:
(單獨使用startService()和單獨使用bindService()時的生命周期)

Google 服務生命周期示意圖

回調方法介紹

回調方法 說明
onCreate() 首次創(chuàng)建服務時調用嚷炉,執(zhí)行一次性設置程序(在調用 onStartCommand()onBind() 之前)渊啰。如果服務已在運行,則不會調用此方法。
onStartCommand() 當另一個組件(如 Activity)通過調用 startService() 請求啟動服務時绘证,系統(tǒng)將調用此方法隧膏。一旦執(zhí)行此方法,服務即會啟動并可在后臺無限期運行嚷那。 如果您實現(xiàn)此方法胞枕,則在服務工作完成后,需要由您通過調用 stopSelf()stopService() 來停止服務魏宽。(如果您只想提供綁定腐泻,則無需實現(xiàn)此方法。)
onDestroy() 當服務不再使用且將被銷毀時队询,系統(tǒng)將調用此方法派桩。服務應該實現(xiàn)此方法來清理所有資源,如線程蚌斩、注冊的偵聽器铆惑、接收器等。 這是服務接收的最后一個調用送膳。
onBind() 當另一個組件想通過調用 bindService() 與服務綁定(例如執(zhí)行 RPC)時员魏,系統(tǒng)將調用此方法。在此方法的實現(xiàn)中叠聋,您必須通過返回 IBinder 提供一個接口撕阎,供客戶端用來與服務進行通信。請務必實現(xiàn)此方法晒奕,但如果您并不希望允許綁定闻书,則應返回 null
onUnbind() 解綁服務

特別事項

  1. onCreate()只調1次
  2. startService()調用次數(shù)=onStartCommand()調用次數(shù)脑慧。
  3. Service通過bindService()被綁定啟動后魄眉,將一直運行,直到調用unBindService()或者調用bindService()Context被銷毀(即 使用bindService()建立的連接斷開)闷袒。

盡管Context銷毀時坑律,Service會自動停止,但只要調用bindService()進行綁定囊骤,仍然還是需要在某處調用unBindService()解除綁定

  1. onStartCommand()方法必須返回一個整數(shù),描述系統(tǒng)在殺死服務后如何繼續(xù)運行晃择。

    • START_NOT_STICKY:
      不會重新創(chuàng)建服務,除非有未發(fā)送的intent也物。當應用程序可以簡單地重新啟動任何未完成的工作時宫屠,這是避免在不必要的情況下運行服務的最安全的選項。
    • START_STICKY
      重新創(chuàng)建服務并調用onStartCommand()滑蚯,但不會再次送入上一個intent浪蹂。相反除非有未發(fā)送完的啟動服務的intent抵栈,否則使將用null intent調用onStartCommand()。這適用于不執(zhí)行命令的媒體播放器(或類似的服務)坤次,但它們會持續(xù)運行并隨時待命古劲。
    • START_REDELIVER_INTENT
      重新創(chuàng)建服務并調用onStartCommand(),并將上一個intent交付給服務缰猴。任何未處理的intent都將依次傳遞产艾。這適用于需要立即恢復工作的活躍服務,例如下載文件滑绒。
  2. 手機屏幕旋轉時闷堡,Activity會銷毀 & 重新創(chuàng)建,因此使用bindService()建立的連接會斷開

startService()與bindService()混合使用時的生命周期:

我們給出兩個例子:

  • startService() -> bindService()或者 bindService() -> startService()蹬挤,這二者順序僅僅影響onBind()onStartCommand()的順序
  • 多次重復startService()bindService()也同樣遵循上述 特別事項
  1. 按順序1,2,3,4執(zhí)行
    (1)startService():調用onCreate()->onStartCommand()
    (2)bindService():調用onBind()
    (3)stopService():沒有調用onDestory()缚窿,Service仍然在運行棘幸!
    (4)unbindService():調用onUnbind() -> onDestory()焰扳,此時Service關閉!

官方解釋:若被停止的服務依然有ServiceConnection 與其綁定误续,則服務不能銷毀吨悍,直至我們把所有ServiceConnection 解綁

  1. 將上述3,4調換
    (1)startService():調用onCreate()->onStartCommand()
    (2)bindService():調用onBind()
    (3)unbindService():調用onUnbind()Service仍然在運行蹋嵌!
    (4)stopService():調用onDestory()育瓜,此時Service關閉!

官方解釋:
當所有ServiceConnection 解綁后栽烂,系統(tǒng)會自動銷毀服務(不包括同時用startService() 啟動的情況)躏仇。此時,我們不得不再調用一次stopService() 來銷毀它

給出Google官方的圖腺办,方便直觀的理解:


分類

按啟動方式:

(上面生命周期已經(jīng)介紹的很詳細了焰手,只簡要概括)

類別 區(qū)別 應用場景
Started Service
(startService() 啟動的服務)
停止服務使用stopService() 不需要與Service通信
服務長期運行
Bound Service
(bindService() 啟動的服務)
停止服務使用unbindService() 需要與Service通信
混合服務Service
(startService() 同時也 bindService() 啟動的服務)
停止服務應同時使用stopService(()unbindService() 需要與Service通信
服務長期運行

(官方的分類只有前兩種,上面的混合服務Service只是我個人的看法怀喉,也是為了后續(xù)方便介紹如何使用)

按運行地點
類別 區(qū)別 優(yōu)點 缺點 應用
本地服務(Local) 該服務依附在主進程上 節(jié)約了資源
通信方便不需要IPC书妻,也不需要AIDL
主進程被Kill后,服務便會終止躬拢。 我們大部分寫的Service就是這種服務躲履,如音樂后臺播放
遠程服務(Remote) 該服務是獨立的進程 有較高的靈活性,Activity所在進程被Kill的時候聊闯,該服務依然在運行 獨立進程工猜,占用一定資源
使用AIDL進行IPC麻煩
一些提供系統(tǒng)服務的Service,這種Service是常駐的菱蔬。
按運行類型
類別 區(qū)別 應用
前臺服務 通知欄顯示Notification 當服務被終止的時候篷帅,通知欄的 Notification 也會消失,這樣對于用戶有一定的通知作用。常見的如音樂播放服務犹褒。
后臺服務 默認的服務即為后臺服務抵窒,用戶不可見 當服務被終止的時候,用戶是看不到效果的叠骑。某些不需要運行或終止提示的服務李皇,如天氣更新,日期同步宙枷,郵件同步等掉房。

具體實現(xiàn)

實際上,在上述不同場景下的分類多多少少都有所重疊慰丛,比如:

  • Bound Service包括 本地(Binder) 和 遠程服務(MessageAIDL)
  • Started Service/Bound Service都可以在位于前臺卓囚,也都可以在后臺

Demo地址

GitHub:DemoForService

1. Started Service

  • 繼承Service
  • 復寫onCreate() , onStartCommand() , onDestroy() , onBind() 方法
  • AndroidManifest.xml中注冊
  • 通過startService() , stopService() 啟動和停止服務
  • 前臺通知只需要再合適的地方調用startForeground()Demo 通知欄適配Android8.0

下面以Demo具體說明:
MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "MainActivity";


    private Button mButton1;
    private Button mButton2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton1 = findViewById(R.id.button1);
        mButton2 = findViewById(R.id.button2);

        mButton1.setOnClickListener(this);
        mButton2.setOnClickListener(this);
     
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button1:
                //啟動服務
                startService(new Intent(MainActivity.this,StartedService.class));              
                break;
            case R.id.button2:
                //停止服務
                stopService(new Intent(MainActivity.this,StartedService.class));
                break;
         }
    }
 }

StartedService.java

//繼承Service類
public class StartedService extends Service {
    //TAG標記
    private static final String TAG = "StartedService";

    //復寫onCreate()方法诅病,輸出一段內(nèi)容
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: "+ TAG);
        super.onCreate();

    }

    //復寫onStartCommand(方法),輸出一段內(nèi)容
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: "+ TAG);

        //適配Android 8.0的通知
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        //實例化通道channel
        final String ID = "channalID";
        final String name = "name";
        NotificationChannel channel = new NotificationChannel(ID,name, NotificationManager.IMPORTANCE_HIGH);
        //調用該方法之后哪亿,該channel的ID才能被通知使用
        manager.createNotificationChannel(channel);

        //添加建立好的通道的ID
        Notification notification = new NotificationCompat.Builder(this,ID)
                .setContentTitle("前臺服務標題")
                .setContentText("前臺服務內(nèi)容")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setChannelId(ID)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .build();

        //開啟前臺服務
        startForeground(1,notification);
        return super.onStartCommand(intent, flags, startId);
    }

    //復寫onDestroy()方法,輸出一段內(nèi)容
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: "+TAG);
        stopForeground(true);
        super.onDestroy();
    }

    //不需要通信贤笆,返回空即可
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidMenifest.xml

<service android:name=".StartedService"/><service android:name=".StartedService"/>

前臺Service優(yōu)先級較高蝇棉,不會由于系統(tǒng)內(nèi)存不足而被回收;后臺Service優(yōu)先級較低芥永,當系統(tǒng)出現(xiàn)內(nèi)存不足情況時篡殷,很有可能會被回收前臺Service優(yōu)先級較高,不會由于系統(tǒng)內(nèi)存不足而被回收埋涧;后臺Service優(yōu)先級較低板辽,當系統(tǒng)出現(xiàn)內(nèi)存不足情況時,很有可能會被回收

Androidmanifest里Service的常見屬性說明

屬性 說明 備注
android:name Service的類名
android:label Service的名字 若不設置棘催,默認為Service類名
android:icon Service的圖標
android:permission 申明此Service的權限 有提供了該權限的應用才能控制或連接此服務
android:process 表示該服務是否在另一個進程中運行(遠程服務) 不設置默認為本地服務
remote則設置成遠程服務
android:enabled 系統(tǒng)默認啟動 true:Service 將會默認被系統(tǒng)啟動
不設置則默認為false
android:exported 該服務是否能夠被其他應用程序所控制或連接 不設置默認此項為 false

結果展示

首次啟動服務

再次啟動服務

停止服務

前臺服務

2. Bound Service

2.1 本地服務-Binder實現(xiàn)

  • 繼承Service類劲弦,并新建子類繼承Binder類,寫入關聯(lián)方法巧鸭,創(chuàng)建實例
  • 調用方通過ServiceConnection實例與Service子類建立聯(lián)系
  • 復寫onCreate() , onBind() , onUnBind() , onDestroy()

下面給出部分Demo代碼作介紹:

MainActivity.java

  • 在Activity通過調用MyBinder類中的public方法來實現(xiàn)Activity與Service的聯(lián)系
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "MainActivity";

    private LocalService.LocalBinder mBinder;
    private LocalService mLocalService;

    ...
    private Button mButton3;
    private Button mButton4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         ...
        mButton3 = findViewById(R.id.button3);
        mButton4 = findViewById(R.id.button4);

        ...
        mButton3.setOnClickListener(this);
        mButton4.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            ...
            //調用bindService()
            case R.id.button3:
                //這里傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯(lián)后自動創(chuàng)建Service
                //這會使得LocalService中的onCreate()方法得到執(zhí)行瓶您,但onStartCommand()方法不會執(zhí)行
                bindService(new Intent(MainActivity.this,LocalService.class),mConnection,BIND_AUTO_CREATE);
                break;
            //調用unBindService()
            case R.id.button4:
                unbindService(mConnection);
                break;

        }
    }
    //創(chuàng)建ServiceConnection的匿名類
    private ServiceConnection mConnection = new ServiceConnection() {

        //在建立關聯(lián)的時候調用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            //得到MyBinder的實例,可以調用MyBinder中的方法
            mBinder = (LocalService.LocalBinder) service;
            //得到LocalService的實例纲仍,可以調用其中方法
            mLocalService = mBinder.getService();
            //調用MyBinder類中的方法
            mBinder.showBinderInfo();
            //調用LocalService服務中的方法
            mLocalService.showServiceInfo();
        }

        //注意:調用unBindService()方法時呀袱,不會調用該方法!V5夜赵!
        //該方法只有在系統(tǒng)回收該服務時候才會調用!O绺铩寇僧!
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}

LocalService

  • 在新建子類繼承Service類摊腋,并新建一個子類繼承自Binder類、寫入與Activity關聯(lián)需要的方法嘁傀、創(chuàng)建實例
//繼承Service類
public class LocalService extends Service {

    private static final String TAG = "LocalService";

    //創(chuàng)建LocalBinder實例
    private final LocalBinder mBinder = new LocalBinder();

    //新建LocalBinder類繼承自Binder類
    public class LocalBinder extends Binder{
        //返回當前服務對象的方法
        LocalService getService(){
            return LocalService.this;
        }
        //LocalBinder中的公開方法兴蒸,供建立聯(lián)系的Activity訪問
        void showBinderInfo(){
            Log.d(TAG, "showInfo: 我是來自Binder的方法");
        }
    }
    //復寫方法
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: "+ TAG);
        super.onCreate();
    }

    //復寫方法
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: "+TAG);
        return super.onStartCommand(intent, flags, startId);
    }

    //復寫方法
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    //復寫方法
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: "+TAG);
        super.onDestroy();
    }

    //返回當前LocalBinder實例
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }

    //服務中的公開方法,供建立聯(lián)系的Activity訪問
    public void showServiceInfo(){
        Log.d(TAG, "showServiceInfo: 我是來自LocalService的方法");
    }
}

AndroidManifest.xml

 <service android:name=".LocalService" />

結果展示

綁定啟動服務

解綁服務

注意:多次解綁會報錯(java.lang.IllegalArgumentException)细办,原因大概是解綁之后橙凳,找不到該注冊的服務。詳細需要深入源碼找原因笑撞。

2.2遠程服務-IPC&AIDL

具體見文章:Android多線程基礎解析


總結

  • 本文對Android中的服務Service的方方面面做出詳細的總結岛啸。
  • 筆者水平有限,如有錯漏茴肥,歡迎指正坚踩。
  • 接下來我也會將所學的知識分享出來,有興趣可以繼續(xù)關注whd_Alive的Android開發(fā)筆記

歡迎關注whd_Alive的簡書

  • 不定期分享Android開發(fā)相關的技術干貨瓤狐,期待與你的交流瞬铸,共勉。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芬首,一起剝皮案震驚了整個濱河市赴捞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌郁稍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胜宇,死亡現(xiàn)場離奇詭異耀怜,居然都是意外死亡,警方通過查閱死者的電腦和手機桐愉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門财破,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人从诲,你說我怎么就攤上這事左痢。” “怎么了系洛?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵俊性,是天一觀的道長。 經(jīng)常有香客問我描扯,道長定页,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任绽诚,我火速辦了婚禮典徊,結果婚禮上杭煎,老公的妹妹穿的比我還像新娘。我一直安慰自己卒落,他們只是感情好羡铲,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著儡毕,像睡著了一般犀勒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妥曲,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天贾费,我揣著相機與錄音,去河邊找鬼檐盟。 笑死褂萧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的葵萎。 我是一名探鬼主播导犹,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羡忘!你這毒婦竟也來了谎痢?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤卷雕,失蹤者是張志新(化名)和其女友劉穎节猿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漫雕,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡滨嘱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浸间。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片太雨。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖魁蒜,靈堂內(nèi)的尸體忽然破棺而出囊扳,到底是詐尸還是另有隱情,我是刑警寧澤兜看,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布锥咸,位于F島的核電站,受9級特大地震影響铣减,放射性物質發(fā)生泄漏她君。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一葫哗、第九天 我趴在偏房一處隱蔽的房頂上張望缔刹。 院中可真熱鬧球涛,春花似錦、人聲如沸校镐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸟廓。三九已至从祝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間引谜,已是汗流浹背牍陌。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留员咽,地道東北人毒涧。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像贝室,于是被迫代替她去往敵國和親契讲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

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

  • 【Android Service】 Service 簡介(★★★) 很多情況下滑频,一些與用戶很少需要產(chǎn)生交互的應用程...
    Rtia閱讀 3,154評論 1 21
  • 服務基本上分為兩種形式 啟動 當應用組件(如 Activity)通過調用 startService() 啟動服務時...
    pifoo閱讀 1,272評論 0 8
  • 前言:本文所寫的是博主的個人見解捡偏,如有錯誤或者不恰當之處,歡迎私信博主峡迷,加以改正银伟!原文鏈接,demo鏈接 Serv...
    PassersHowe閱讀 1,415評論 0 5
  • 第一種方式:通過StartService啟動Service 通過startService啟動后凉当,service會一...
    Big不吃魚閱讀 118,292評論 8 94
  • 這是一座俄式建筑枣申,大樓外墻一層和二層之間有一個用水泥抹成的裝飾格子,格子里長了一棵小榆樹看杭。這棵榆樹樹干虬曲蒼勁,枝...
    扶青閱讀 499評論 1 3