Android寶典|Service必考知識點總結

目錄

  1. 思維導圖
  2. 概述
  3. 啟動方式及生命周期
    • startService 方式啟動
    • bindService 方式啟動
    • 混合啟動
  4. 疑問
    • startCommand 返回值含義
    • bindService 第三個參數(shù)含義
  5. startForegroundService
    • FOREGROUND_SERVICE 權限申請
    • onStartCommand 必須調(diào)用 startForeground 構造通知欄
    • 非綁定服務
  6. 常見問題匯總
    • 如何保證 Service 不被殺死
  7. 參考

思維導圖

image

概述

Service 是一種可以在后臺長時間運行而沒有用戶界面的應用組件阀湿。它有兩種啟動方式磨德,第一種方式是 startService 啟動服務杨蛋,服務啟動后惫恼,會在后臺無限期運行,直到通過 stopService 或者 stopSelf 停止服務琳骡。另一種方式是 bindService 綁定服務锅论,組件與服務綁定在一起,服務的生命周期受組件影響楣号,如果組件被銷毀了最易,服務也就停止了。

Service 是運行在主線程中的炫狱,因此不能執(zhí)行耗時任務耘纱,如果想執(zhí)行耗時任務,可以使用 IntentService毕荐。

啟動方式及生命周期

image
startService 方式啟動

當應用組件通過 startService 方法來啟動 Service 時,Service 則會處于啟動狀態(tài)艳馒,一旦服務啟動憎亚,它就會在后臺無限期的運行,生命周期獨立于啟動它的組件弄慰,即使啟動它的組件已經(jīng)銷毀了也不受任何影響第美,由于啟動的服務長期運行在后臺,這會大量的消耗電量陆爽,因此什往,我們應該在任務執(zhí)行完之后調(diào)用 stopSelf 來停止服務,或者通過其他應用組件調(diào)用 stopServcie 來停止服務慌闭。

通過這種方式啟動 Service 生命周期是:

onCreate别威、onStartCommand、onDestory驴剔。

首次啟動服務的時候省古,系統(tǒng)會調(diào)用 onCreate 方法,多次啟動不會在調(diào)用 onCreate 方法丧失,只會調(diào)用 onStartCommand豺妓,onStartCommand 被調(diào)用后,服務就啟動起來了布讹,將會在后臺無限期的運行琳拭,直到通過 stopService 或者 stopSelf 方法來停止服務。當服務被銷毀時描验,將會回調(diào) onDestory 方法白嘁。

多次調(diào)用 startService(Intent) 會回調(diào) onStartCommand 方法,而多次調(diào)用 stopService 只有第一次會回調(diào) onDestory 方法挠乳。

bindService 方式啟動

即將啟動組件和服務綁定在一起权薯,前面講的通過 startService 方式啟動的服務是與組件相獨立的姑躲,即使啟動服務的組件被銷毀了,服務仍然在后臺運行不受干擾盟蚣,但是通過 bindService 方式綁定的服務就不一樣了黍析,它與綁定組件的生命周期是有關的。

多個組件可以綁定到同一個服務上屎开,如果只有一個組件綁定服務阐枣,當綁定的組件被銷毀時,服務也就會停止了奄抽。如果是多個組件綁定到一個服務上蔼两,當綁定到該服務的所有組件都被銷毀時,服務才會停止逞度。

通過這種方式啟動 Service 生命周期是:

onCreate额划、onBind、onUnbind档泽、onDestory

onBind():當其他組件想通過 bindService 與服務綁定時俊戳,系統(tǒng)將會回調(diào)這個方法,在實現(xiàn)中馆匿,必須返回一個 IBinder 接口抑胎,供客戶端和服務進行通信,必須實現(xiàn)此方法渐北,這個方法是 Service 的一個抽象方法阿逃,但是如果不允許綁定,返回 null 就好了赃蛛。

onUnbind:當所有與服務綁定的組件都解除綁定時恃锉,就會調(diào)用此方法。

多次 bindService 并不會回調(diào)任何方法焊虏,多次 unBindService 則會 Crash淡喜。

實例:

  1. 創(chuàng)建服務

    其實就是在 onBind 方法中返回 IBinder 實例

    public class MyBindService extends Service {
    
        private static final String TAG = "MyBindService";
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i(TAG, "onCreate: ");
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i(TAG, "onStartCommand: ");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.i(TAG, "onBind: ");
            return new MyBinder();
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            Log.i(TAG, "onUnbind: ");
            return super.onUnbind(intent);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.i(TAG, "onDestroy: ");
        }
    
        public class MyBinder extends Binder{
            public String getInfo(){
                return "Info";
            }
        }
    }
    
  2. 綁定服務

    綁定服務需要提供一個 ServiceConnection 接口,在接口回調(diào)中獲取 Binder 對象诵闭,與服務進行通信炼团。

        private MyBindService.MyBinder mMyBinder;
        //綁定/解除綁定 Service 回調(diào)接口
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //綁定成功后回調(diào)
                //1.獲取 Binder 接口對象
                mMyBinder = (MyBindService.MyBinder) service;
                //2.從服務獲取數(shù)據(jù)
                String content = mMyBinder.getInfo();
                //3.界面提示
                Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //解除綁定后回調(diào)
                mMyBinder = null;
            }
        };
        
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        unbindService(mConnection);
    
混合啟動

如果一個服務被啟動又被綁定,onCreate 方法只會執(zhí)行一次疏尿,startService 調(diào)用多少次瘟芝,onStartCommand 就會執(zhí)行多少次,調(diào)用 stopService 并不會回調(diào) onDestory褥琐,unBindService 可以锌俱。

疑問

onStartCommand 返回值

返回值的取值其實已經(jīng)定義在了 Service 基類中了,常用的有:

  • START_NOT_STICKY

    如果系統(tǒng)在 onStartCommand 返回后終止服務敌呈,則除非有掛起 Intent 要傳遞贸宏,否則系統(tǒng)不會重建服務造寝。這是最安全的選項,可以避免在不必要時以及應用能夠輕松重啟所有未完成的作業(yè)時運行服務吭练。

  • START_STICKT

    如果系統(tǒng)在 onStartCommand 返回后終止服務诫龙,則會重建服務并調(diào)用 onStartCommadn,但絕對不會重新傳遞最后一個 Intent鲫咽。相反签赃,除非有掛起 Intent 要啟動服務,否則系統(tǒng)會通過空 Intent 調(diào)用 onStartCommand分尸。這適用于不執(zhí)行命令锦聊、但無限期運行并等待作業(yè)的媒體播放器等。

  • START_REDELIVER_INTENT

    如果系統(tǒng)在 onStartCommand 返回后終止服務箩绍,則會重建服務孔庭,并通過傳遞給服務等最后一個 Intent 調(diào)用 onStartCommand。任何掛起 Intent 均依次傳遞材蛛。這適用于主動執(zhí)行應該立即恢復的作業(yè)的服務史飞,例如下載文件。

bindService 參數(shù)
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        return mBase.bindService(service, conn, flags);
    }

前兩個參數(shù)就不用多說了仰税,第三個參數(shù)一般傳 Context.BIND_AUTO_CREATE。

它的值是一些枚舉值抽诉,

startForegroundService

單獨拿出來說陨簇,逼格高吧。

回到正題迹淌,最開始使用這個方法時河绽,不是 Crash 就是 ANR,頭大唉窃!

首先使用前臺服務耙饰,必須申請 FOREGROUND_SERVICE 權限,這是普通權限纹份,未申請則會引發(fā) SecurityException苟跪。

ANR 的解決

使用前臺服務,必須提供一個通知欄蔓涧,不然五秒就會 ANR件已。

    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        // 通知渠道的id
        String id = "my_channel_01";
        // 用戶可以看到的通知渠道的名字.
        CharSequence name = "Demo";
        // 用戶可以看到的通知渠道的描述
        String description = "Desc";
        int importance = NotificationManager.IMPORTANCE_HIGH;
        NotificationChannel mChannel = new NotificationChannel(id, name, importance);
        // 配置通知渠道的屬性
        mChannel.setDescription(description);
        // 設置通知出現(xiàn)時的閃燈(如果 android 設備支持的話)
        mChannel.enableLights(true);
        mChannel.setLightColor(Color.RED);
        // 設置通知出現(xiàn)時的震動(如果 android 設備支持的話)
        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
        mNotificationManager.createNotificationChannel(mChannel);

        // 通知渠道的id
        String CHANNEL_ID = "my_channel_01";
        // Create a notification and set the notification channel.
        Notification notification = new Notification.Builder(this, CHANNEL_ID)
                .setContentTitle("New Message").setContentText("You've received new messages.")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .build();
        startForeground(1, notification);
        return super.onStartCommand(intent, flags, startId);
    }

準確的來說,應該是必須調(diào)用 startForeground 方法元暴。

既然我是寫在 onStartCommand 方法里面篷扩,就說明這是一個啟動服務非綁定服務,所以可以多次調(diào)用 startForegroundService 方法茉盏,調(diào)用一次 stopService 即可停止服務鉴未。

Crash 的解決

Crash 的報錯如下:

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

這是由于我在 startForegroundService 之后就 stopService枢冤,在 Service 中并沒有構造通知欄導致的。

總結

使用前臺服務铜秆,有三點需要注意:

  1. 申請 FOREGROUND_SERVICE 權限淹真,它是普通權限
  2. 在 onStartCommand 中必須要調(diào)用 startForeground 構造一個通知欄,不然 ANR
  3. 前臺服務只能是啟動服務羽峰,不能是綁定服務

常見問題匯總

  1. 如何保證 Service 不被殺死
    • 在 Service 的 onStartCommand 中返回 START_STICKY趟咆,該標志使得 Service 被殺死后嘗試再次啟動 Service
    • 提高 Service 優(yōu)先級,比如設置成前臺服務
    • 在 Activity 的 onDestory 發(fā)送廣播梅屉,在廣播接收器的 onReceiver 重啟 Service

參考

Service 知識總結

Android Service和IntentService知識點詳細總結

Context.startForegroundService() did not then call Service.startForeground值纱?

developer服務概覽

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市坯汤,隨后出現(xiàn)的幾起案子虐唠,更是在濱河造成了極大的恐慌,老刑警劉巖惰聂,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疆偿,死亡現(xiàn)場離奇詭異,居然都是意外死亡搓幌,警方通過查閱死者的電腦和手機杆故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溉愁,“玉大人处铛,你說我怎么就攤上這事」战遥” “怎么了撤蟆?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長堂污。 經(jīng)常有香客問我家肯,道長,這世上最難降的妖魔是什么盟猖? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任讨衣,我火速辦了婚禮,結果婚禮上式镐,老公的妹妹穿的比我還像新娘值依。我一直安慰自己,他們只是感情好愿险,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般辆亏。 火紅的嫁衣襯著肌膚如雪风秤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音无虚,去河邊找鬼戴质。 笑死符糊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的啰脚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夫植!你這毒婦竟也來了?” 一聲冷哼從身側響起兔综,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤饿凛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后软驰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涧窒,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年锭亏,在試婚紗的時候發(fā)現(xiàn)自己被綠了纠吴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡慧瘤,死狀恐怖戴已,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锅减,我是刑警寧澤糖儡,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站怔匣,受9級特大地震影響握联,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一拴疤、第九天 我趴在偏房一處隱蔽的房頂上張望永部。 院中可真熱鬧,春花似錦呐矾、人聲如沸苔埋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽组橄。三九已至,卻和暖如春罚随,著一層夾襖步出監(jiān)牢的瞬間玉工,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工淘菩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遵班,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓潮改,卻偏偏與公主長得像狭郑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汇在,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

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