Android編程權(quán)威指南(第二版)學(xué)習筆記(二十六)—— 第26章 后臺服務(wù)

本章主要講了 Android 的一大組件:服務(wù)。使用 IntentService 作為后臺服務(wù)带欢,用 AlarmManager 定時啟動运授,以及應(yīng)用通知的發(fā)出,還介紹了新的 JobScheduler 及其使用乔煞。

GitHub 地址:
完成第26章

Activity 就是 Android 應(yīng)用的前臺吁朦。所有應(yīng)用代碼都專注于提供良好的用戶視覺體驗。服務(wù)就是 Android 應(yīng)用的后臺渡贾,用戶無需關(guān)心后臺發(fā)生的一切逗宜。即使前臺關(guān)閉,activity 消失好久了,后臺服務(wù)依然可以持續(xù)不斷地工作纺讲。
服務(wù)最關(guān)鍵的特性就是:用戶離開當前應(yīng)用后(打開其他應(yīng)用或退回主屏幕)擂仍,服務(wù)依然可以在后臺運行。

1. 服務(wù)的使用

1.1 服務(wù)的能與不能

與 activity 一樣熬甚,服務(wù)是一個有生命周期回調(diào)方法的應(yīng)用組件逢渔。這些回調(diào)方法同樣也會在主 UI 線程上運行。
初始創(chuàng)建的服務(wù)不會在后臺線程上運行任何代碼乡括。而大多數(shù)重要服務(wù)都需要某種后臺線程复局,IntentService 類提供了一套標準實現(xiàn)代碼,所以推薦使用 IntentService 完成本章粟判。

1.2 服務(wù)的生命周期

如果是startService(Intent)方法啟動的服務(wù),其生命周期很簡單峦剔,并具有三種生命周期回調(diào)方法档礁。

  1. onCreate(...)方法:服務(wù)創(chuàng)建時調(diào)用。
  2. onStartCommand(Intent,int,int)方法:每次組件通過 startService(Intent)方法
    啟動服務(wù)時調(diào)用一次吝沫。它有兩個整數(shù)參數(shù)呻澜,一個是標識符集,一個是啟動 ID惨险。標識符集用來表示當前 intent 發(fā)送究竟是一次重新發(fā)送羹幸,還是一次從沒成功過的發(fā)送。每次調(diào)用 onStartCommand(Intent,int,int)方法辫愉,啟動 ID 都會不同栅受。因此,啟動 ID 也可用于區(qū)分不同的命令恭朗。
  3. onDestroy()方法:服務(wù)不再需要時調(diào)用屏镊。通常是在服務(wù)停止后。 服務(wù)停止時會調(diào)用 onDestroy()方法痰腮。服務(wù)停止的方式取決于服務(wù)的類型而芥。
  4. 服務(wù)的類型由 onStartCommand(...)方法的返回值確定,可能的服務(wù)類型有 Service.START_NOT_STICKY膀值、
    START_REDELIVER_INTENTSTART_STICKY棍丐。IntentService 是一種 non-sticky 服務(wù)

1.3 不同類型的服務(wù)

  1. non-sticky 服務(wù)
    non-sticky 服務(wù)在服務(wù)自己認為已完成任務(wù)時停止沧踏。為獲得 non-sticky 服務(wù)歌逢,應(yīng)返回START_NOT_STICKYSTART_REDELIVER_INTENT。兩者區(qū)別在于悦冀,如果系統(tǒng)需要在服務(wù)完成任務(wù)之前關(guān)閉它趋翻,則服務(wù)的具體表現(xiàn)會有所不同。START_NOT_STICKY型服務(wù)說消亡就消亡了;而START_REDELIVER_INTENT型服務(wù)則會在資源不再吃緊時,嘗試再次啟動服務(wù)踏烙。

    通過調(diào)用 stopSelf()或 stopSelf(int)方法师骗,我們告訴 Android 任務(wù)已完成。stopSelf() 是個無條件方法讨惩。不管 onStartCommand(...)方法調(diào)用多少次辟癌,該方法總是會成功停止服務(wù)。stopSelf(int)是個有條件的方法荐捻。該方法需要來自于 onStartCommand(...)方法的啟動 ID黍少。只有在接收到最新啟動 ID 后,該方法才會停止服務(wù)处面。(這也是 IntentService 的后臺工作原理厂置。)

  2. sticky 服務(wù)
    sticky 服務(wù)會持續(xù)運行,直到外部組件調(diào)用 Context.stopService(Intent)方法讓它停止魂角。 為獲得 sticky 服務(wù)昵济,應(yīng)返回 START_STICKY。
    sticky 服務(wù)啟動后會持續(xù)運行野揪,除非某個組件調(diào)用 Context.stopService(Intent)方法停止它访忿。如因某種原因需終止服務(wù),可傳入一個 null intent 給 onStartCommand(...)方法斯稳,實現(xiàn)服務(wù)的重啟海铆。sticky 服務(wù)適用于長時間運行的服務(wù),如音樂播放器這種啟動后一直保持運行狀態(tài)挣惰,直到用戶主動停止的服務(wù)卧斟。

1.3 服務(wù)的使用

一個最基本的 IntentService 如下:

public class PollService extends IntentService {

    private static final String TAG = "PollService";

    // 外界獲取服務(wù)的實例
    public static Intent newIntent(Context context) {
        return new Intent(context, PollService.class);
    }

    public PollService() {
        super(TAG);
    }
    
    // 服務(wù)主要執(zhí)行代碼的地方
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(TAG, "Received an intent: " + intent);
    }
}

在外界使用 Context.startService(Intent) 即可開啟服務(wù)。

2. 使用 AlarmManager 定時啟動服務(wù)

一個基本的定時啟動代碼如下:

// 首先獲取服務(wù)啟動的 intent
Intent i = PollService.newIntent(context);
// 將其放入 PendingIntent 中
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
// 獲取 AlarmManager 服務(wù)
AlarmManager alarmManager = (AlarmManager)
        context.getSystemService(Context.ALARM_SERVICE);
// 如果開啟服務(wù)
if (isOn) {
    // 將這個 PendingIntent 放到 AlarmManager 中定時啟動
    alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
            SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);
} else {
    // 如果沒有開啟服務(wù)憎茂,就讓 AlarmManager 撤銷該定時器
    alarmManager.cancel(pi);
    // 它自己也要撤銷
    pi.cancel();
}

2.1 PendingIntent

PendingIntent 是一種 token 對象唆涝。調(diào)用 PendingIntent.getService(...)方法獲取 PendingIntent 時,我們告訴操作系統(tǒng):“請記住唇辨, 我需要使用 startService(Intent)方法發(fā)送這個 intent廊酣。”隨后赏枚,調(diào)用 PendingIntent 對象的 send()方法時亡驰,操作系統(tǒng)會按照要求發(fā)送原來封裝的 intent。
PendingIntent 真正精妙的地方在于饿幅,將 PendingIntent token 交給其他應(yīng)用使用時凡辱,它是代表當前應(yīng)用發(fā)送 token 對象的。另外栗恩,PendingIntent 本身存在于操作系統(tǒng)而不是 token 里透乾。如果不顧及別人感受的話,也可以在交給別人一個 PendingIntent 對象后,立即撤銷它乳乌,讓 send()方法什么也做不了捧韵。如果使用同一個 intent 請求 PendingIntent 兩次,得到的 PendingIntent 仍會是同一個汉操。我們可借此測試某個 PendingIntent 是否已存在再来,或撤銷已發(fā)出的 PendingIntent。

PendingIntent.getService(...) 方法打包了啟動服務(wù)的方法的調(diào)用磷瘤。它有四個參數(shù):一個用來發(fā)送 intent 的 Context芒篷,一個區(qū)分 PendingIntent 來源的請求代碼,一個待發(fā)送的 Intent 對象以及一組用來決定如何創(chuàng)建 PendingIntent 的標志符采缚。

2.2 使用 AlarmManager

我們用 AlarmManager.setInexactRepeating(…) 方法開啟了定時啟動针炉,該方法同樣具有四個參數(shù): 一個描述定時器時間基準的常量,定時器啟動的時間扳抽,定時器循環(huán)的時間間隔以及一個到時要發(fā)送的 PendingIntent糊识。

  1. AlarmManager.ELAPSED_REALTIME 是基準時間值 , 這表明我們是以 SystemClock. elapsedRealtime()走過的時間來確定何時啟動時間的摔蓝。也就是說,經(jīng)過一段指定的時間愉耙,就啟動定時器贮尉。假如使用 AlarmManager.RTC,啟動基準時間就是當前時刻(例如朴沿,System. currentTimeMillis())猜谚。也就是說,一旦到了某個固定時刻赌渣,就啟動定時器魏铅。
  2. 時間間隔由我們自己確定,不過推薦使用 AlarmManager 自身定義的常量坚芜。

2.3 獲取定時器激活狀態(tài)

由于我們在代碼中撤銷定時器的同時也撤銷了 PendingIntent览芳,所以通過發(fā)送一個 PendingIntent.FLAG_NO_CREATE 標志給 getService 方法可以獲取這個 PendingIntent 存在狀態(tài)。

3. 通知

如果服務(wù)需要與用戶溝通鸿竖,通知信息(notification)總是一個不錯的選擇沧竟。通知信息是指顯示在通知抽屜上的消息條目,用戶可向下滑動屏幕讀取缚忧。 想要發(fā)送通知信息悟泵,首先要創(chuàng)建 Notification 對象。

Notification 需使用構(gòu)造對象來創(chuàng)建闪水。完整的 Notification 至少應(yīng)包括:

  • 在 Lollipop 之前的設(shè)備上糕非,首次顯示通知信息時,在狀態(tài)欄上顯示的 ticker text(Lollipop
    之后,ticker text 不再顯示在狀態(tài)欄上朽肥,但仍與可訪問性服務(wù)相關(guān));
  • 在狀態(tài)欄上顯示的圖標(在 Lollipop 之前的設(shè)備上禁筏,圖標在 ticker text 消失后出現(xiàn));
  • 代表通知信息自身,在通知抽屜中顯示的視圖;
  • 待觸發(fā)的 PendingIntent鞠呈,用戶點擊抽屜中的通知信息時觸發(fā)融师。

完成 Notification 對象的創(chuàng)建后,可調(diào)用 NotificationManager 系統(tǒng)服務(wù)的 notify(int, Notification)方法發(fā)送它蚁吝。

Resources resources = getResources();
Intent i = PhotoGalleryActivity.newIntent(this);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);

Notification notification = new NotificationCompat.Builder(this)
        .setTicker(resources.getString(R.string.new_pictures_title))
        .setSmallIcon(android.R.drawable.ic_menu_report_image)
        .setContentTitle(resources.getString(R.string.new_pictures_title))
        .setContentText(resources.getString(R.string.new_pictures_text))
        .setContentIntent(pi)
        .setAutoCancel(true)
        .build();

NotificationManagerCompat notificationManager =
        NotificationManagerCompat.from(this);
notificationManager.notify(0, notification);
  1. 首先旱爆,調(diào)用 setTicker(CharSequence)和 setSmallIcon (int)方法,配置 ticker text 和小圖標窘茁。
  2. 然后配置 Notification 在下拉抽屜中的外觀怀伦。圖標的值來自于 setSmallIcon(int) 方法 , 而設(shè)置標題和顯示文字則需分別調(diào)用 setContentTitle (CharSequence)和 setContentText(CharSequence)方法山林。
  3. 接下來房待,須指定用戶點擊 Notification 消息時所觸發(fā)的動作行為。這里使用的是 PendingIntent驼抹。用戶在下拉抽屜中點擊 Notification 消息時桑孩,傳入 setContentIntent(PendingIntent)方法的 PendingIntent 會被觸發(fā)。
  4. 調(diào)用 setAutoCancel (true)方法可調(diào)整上述行為框冀。一旦執(zhí)行了 setAutoCancel(true)設(shè)置方法流椒,用戶點擊 Notification 消息時,該消息就會從消息抽屜中刪除明也。
  5. 最后宣虾,從當前 context 中取出一個 NotificationManagerCompat 實例,然后調(diào)用 Notifi- cationManagerCompat.notify(...)方法貼出消息温数。傳入的整數(shù)參數(shù)是通知消息的標識符绣硝,在整個應(yīng)用中該值應(yīng)該是唯一的。如果使用同一 ID 發(fā)送兩條消息撑刺,則第二條消息會替換掉第一條消息鹉胖。在實際開發(fā)中,這也是進度條或其他動態(tài)視覺效果的實現(xiàn)方式够傍。

GitHub Page: kniost.github.io
簡書:http://www.reibang.com/u/723da691aa42

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末次员,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子王带,更是在濱河造成了極大的恐慌淑蔚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愕撰,死亡現(xiàn)場離奇詭異刹衫,居然都是意外死亡醋寝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門带迟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來音羞,“玉大人,你說我怎么就攤上這事仓犬⌒岽拢” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵搀继,是天一觀的道長窘面。 經(jīng)常有香客問我,道長叽躯,這世上最難降的妖魔是什么财边? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮点骑,結(jié)果婚禮上酣难,老公的妹妹穿的比我還像新娘。我一直安慰自己黑滴,他們只是感情好憨募,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袁辈,像睡著了一般菜谣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吵瞻,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音甘磨,去河邊找鬼橡羞。 笑死,一個胖子當著我的面吹牛济舆,可吹牛的內(nèi)容都是我干的卿泽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼滋觉,長吁一口氣:“原來是場噩夢啊……” “哼签夭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起椎侠,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤第租,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡窟扑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年录粱,在試婚紗的時候發(fā)現(xiàn)自己被綠了杏死。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片队萤。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡寄锐,死狀恐怖络断,靈堂內(nèi)的尸體忽然破棺而出汹碱,到底是詐尸還是另有隱情粘衬,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布咳促,位于F島的核電站稚新,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏等缀。R本人自食惡果不足惜枷莉,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尺迂。 院中可真熱鬧笤妙,春花似錦、人聲如沸噪裕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膳音。三九已至召衔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祭陷,已是汗流浹背苍凛。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兵志,地道東北人醇蝴。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像想罕,于是被迫代替她去往敵國和親悠栓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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