本章主要講了 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)方法档礁。
-
onCreate(...)
方法:服務(wù)創(chuàng)建時調(diào)用。 -
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ū)分不同的命令恭朗。 -
onDestroy()
方法:服務(wù)不再需要時調(diào)用屏镊。通常是在服務(wù)停止后。 服務(wù)停止時會調(diào)用 onDestroy()方法痰腮。服務(wù)停止的方式取決于服務(wù)的類型而芥。 - 服務(wù)的類型由 onStartCommand(...)方法的返回值確定,可能的服務(wù)類型有 Service.
START_NOT_STICKY
膀值、
START_REDELIVER_INTENT
和START_STICKY
棍丐。IntentService 是一種 non-sticky 服務(wù)。
1.3 不同類型的服務(wù)
-
non-sticky 服務(wù)
non-sticky 服務(wù)在服務(wù)自己認為已完成任務(wù)時停止沧踏。為獲得 non-sticky 服務(wù)歌逢,應(yīng)返回START_NOT_STICKY
或START_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 的后臺工作原理厂置。)
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糊识。
- AlarmManager.ELAPSED_REALTIME 是基準時間值 , 這表明我們是以 SystemClock. elapsedRealtime()走過的時間來確定何時啟動時間的摔蓝。也就是說,經(jīng)過一段指定的時間愉耙,就啟動定時器贮尉。假如使用 AlarmManager.RTC,啟動基準時間就是當前時刻(例如朴沿,System. currentTimeMillis())猜谚。也就是說,一旦到了某個固定時刻赌渣,就啟動定時器魏铅。
- 時間間隔由我們自己確定,不過推薦使用 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);
- 首先旱爆,調(diào)用 setTicker(CharSequence)和 setSmallIcon (int)方法,配置 ticker text 和小圖標窘茁。
- 然后配置 Notification 在下拉抽屜中的外觀怀伦。圖標的值來自于 setSmallIcon(int) 方法 , 而設(shè)置標題和顯示文字則需分別調(diào)用 setContentTitle (CharSequence)和 setContentText(CharSequence)方法山林。
- 接下來房待,須指定用戶點擊 Notification 消息時所觸發(fā)的動作行為。這里使用的是 PendingIntent驼抹。用戶在下拉抽屜中點擊 Notification 消息時桑孩,傳入 setContentIntent(PendingIntent)方法的 PendingIntent 會被觸發(fā)。
- 調(diào)用 setAutoCancel (true)方法可調(diào)整上述行為框冀。一旦執(zhí)行了 setAutoCancel(true)設(shè)置方法流椒,用戶點擊 Notification 消息時,該消息就會從消息抽屜中刪除明也。
- 最后宣虾,從當前 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