緊接上文
前臺服務(wù)
概述
前臺服務(wù)是用戶主動意識到的一種服務(wù),因此在內(nèi)存不足時(shí),系統(tǒng)也不會考慮將其終止。前臺服務(wù)必須為狀態(tài)欄提供通知,將其放在運(yùn)行中的標(biāo)題下方。這意味著除非將服務(wù)停止或從前臺移除,否則不能清除該通知。
針對上一篇文章中的案例我們可以發(fā)現(xiàn),系統(tǒng)對后臺服務(wù)進(jìn)行了限制,如果想要一直保持服務(wù)的運(yùn)行就需要將服務(wù)設(shè)置為前臺服務(wù)。前臺服務(wù)與普通服務(wù)的區(qū)別在于它會有個(gè)通知在狀態(tài)欄顯示,當(dāng)然有時(shí)可能也不僅僅是為了防止服務(wù)被回收才使用前臺服務(wù),有些項(xiàng)目的需要要求必須使用前臺服務(wù)。如音樂播放、小說聽書、天氣等軟件,這些都需要設(shè)置為前臺服務(wù),跟隨進(jìn)程的銷毀而銷毀。當(dāng)然有些不僅僅是依賴應(yīng)用進(jìn)程,也可設(shè)置為系統(tǒng)白名單,保持一直運(yùn)行的狀態(tài)。
需要注意的是:應(yīng)盡量限制應(yīng)用使用前臺服務(wù):只有當(dāng)應(yīng)用執(zhí)行的任務(wù)需供用戶查看(即使該任務(wù)未直接與應(yīng)用交互)時(shí),您才應(yīng)使用前臺服務(wù)。因此,前臺服務(wù)必須顯示優(yōu)先級為 PRIORITY_LOW 或更高的狀態(tài)欄通知,這有助于確保用戶知道應(yīng)用正在執(zhí)行的任務(wù)。如果某操作不是特別重要,因而您希望使用最低優(yōu)先級通知,則可能不適合使用服務(wù);相反,您可以考慮使用計(jì)劃作業(yè)。
每個(gè)運(yùn)行服務(wù)的應(yīng)用都會給系統(tǒng)帶來額外負(fù)擔(dān),從而消耗系統(tǒng)資源。如果應(yīng)用嘗試使用低優(yōu)先級通知隱藏其服務(wù),則可能會降低用戶正在主動交互的應(yīng)用的性能。因此,如果某個(gè)應(yīng)用嘗試運(yùn)行擁有最低優(yōu)先級通知的服務(wù),則系統(tǒng)會在抽屜式通知欄的底部調(diào)用出該應(yīng)用的行為。
例如,應(yīng)將通過服務(wù)播放音樂的音樂播放器設(shè)置為在前臺運(yùn)行,因?yàn)橛脩魰鞔_意識到其操作。狀態(tài)欄中的通知可能表示正在播放的歌曲,并且其允許用戶通過啟動 Activity 與音樂播放器進(jìn)行交互。同樣,如果應(yīng)用允許用戶追蹤其運(yùn)行,則需通過前臺服務(wù)來追蹤用戶的位置。
注意:如果應(yīng)用面向 Android 9(API 級別 28)或更高版本并使用前臺服務(wù),則其必須請求 FOREGROUND_SERVICE 權(quán)限。這是一種普通權(quán)限,因此,系統(tǒng)會自動為請求權(quán)限的應(yīng)用授予此權(quán)限。如果面向 API 級別 28 或更高版本的應(yīng)用試圖創(chuàng)建前臺服務(wù)但未請求 FOREGROUND_SERVICE,則系統(tǒng)會拋出 SecurityException。
有關(guān)前臺服務(wù)的通知:如果您的應(yīng)用正在運(yùn)行“前臺服務(wù)”(一種長時(shí)間在后臺運(yùn)行且用戶可以察覺到的 Service,如媒體播放器),則需要發(fā)出通知。不能像關(guān)閉其他通知那樣關(guān)閉這種通知。要移除此類通知,必須停止運(yùn)行服務(wù)或者將其從“前臺”狀態(tài)中移除。如要從前臺移除服務(wù),請調(diào)用 stopForeground()。此方法采用布爾值,指示是否需同時(shí)移除狀態(tài)欄通知。此方法不會停止服務(wù)。但是,如果您在服務(wù)仍運(yùn)行于前臺時(shí)將其停止,則通知也會隨之移除。
應(yīng)用場景
前臺服務(wù)執(zhí)行用戶可以注意到的操作。前臺服務(wù)顯示一個(gè)狀態(tài)欄通知,讓用戶知道你的應(yīng)用程序正在前臺執(zhí)行任務(wù),正在消耗系統(tǒng)資源。
應(yīng)用程序使用前臺服務(wù)的例子包括:
- 一個(gè)在前臺服務(wù)中播放音樂的音樂播放器應(yīng)用程序。通知可能會顯示當(dāng)前正在播放的歌曲。
- 一種健身應(yīng)用程序,在獲得用戶的許可后,在前臺服務(wù)中記錄用戶的跑步情況。該通知可能會顯示用戶在當(dāng)前健身會話中走過的距離。
只有當(dāng)你的應(yīng)用需要執(zhí)行用戶可以注意到的任務(wù)時(shí),才使用前臺服務(wù),即使他們沒有直接與應(yīng)用程序交互。如果操作的重要性足夠低,你想使用最低優(yōu)先級通知,那么創(chuàng)建一個(gè)后臺任務(wù)。
本文檔介紹了使用前臺服務(wù)所需的權(quán)限,以及如何啟動前臺服務(wù)并將其從后臺移除。它還描述了如何將某些用例與前臺服務(wù)類型相關(guān)聯(lián),以及當(dāng)您從正在后臺運(yùn)行的應(yīng)用程序啟動前臺服務(wù)時(shí)生效的訪問限制。
前臺服務(wù)的特點(diǎn)
默認(rèn)情況下用戶可取消前臺服務(wù)
從Android 13 (API級別33)開始,默認(rèn)情況下用戶可以取消與前臺服務(wù)相關(guān)的通知。為此,用戶在通知上執(zhí)行滑動手勢。傳統(tǒng)上,除非前臺服務(wù)停止或從前臺刪除,否則通知不會被取消。
如果您希望通知不能被用戶解散,則在使用notification.builder創(chuàng)建通知時(shí)將true傳遞給setOngoing() 方法。
立即顯示通知的服務(wù)
如果前臺服務(wù)至少有以下特征之一,系統(tǒng)在服務(wù)啟動后立即顯示相關(guān)的通知,即使在運(yùn)行Android 12或更高版本的設(shè)備上:
- 該服務(wù)與包含操作按鈕的通知相關(guān)聯(lián)。
- 該服務(wù)的前臺服務(wù)類型為mediaPlayback、mediaProjection或phoneCall。
- 該服務(wù)提供了與電話呼叫、導(dǎo)航或媒體播放相關(guān)的用例,這些用例在通知的category屬性中定義。
- 在設(shè)置通知時(shí),服務(wù)通過將FOREGROUND_SERVICE_IMMEDIATE傳遞給setForegroundServiceBehavior()來選擇退出行為更改。
在Android 13 (API級別33)或更高版本上,如果用戶拒絕通知權(quán)限,他們?nèi)匀粫谌蝿?wù)管理器中看到與前臺服務(wù)相關(guān)的通知,但在通知抽屜中看不到它們。
申請前臺服務(wù)權(quán)限
針對Android 9 (API級別28)或更高版本并使用前臺服務(wù)的應(yīng)用程序需要請求FOREGROUND_SERVICE權(quán)限,如下面的代碼片段所示。這是一個(gè)正常的權(quán)限,所以系統(tǒng)會自動將其授予請求應(yīng)用程序。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application ...>
...
</application>
</manifest>
注意:如果目標(biāo)API級別為28或更高的應(yīng)用程序試圖創(chuàng)建前臺服務(wù)而沒有請求FOREGROUND_SERVICE權(quán)限,系統(tǒng)將拋出一個(gè)SecurityException。
啟動一個(gè)前臺服務(wù)
- 緊接上文的案例,這里首先添加前臺服務(wù)的權(quán)限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- 在請求系統(tǒng)將某個(gè)服務(wù)作為前臺服務(wù)運(yùn)行之前,請先啟動該服務(wù)本身: 在MainActivity中修改啟動服務(wù)的方式如下:
//啟動一個(gè)普通后臺服務(wù)
//startService(startIntent);
//啟動一個(gè)前臺服務(wù) 在api大于26才可使用startForegroundService此方法
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(startIntent);
}else{
startService(startIntent);
}
- 在服務(wù)內(nèi)部,通常是在onStartCommand() 中,你可以請求你的服務(wù)在前臺運(yùn)行。為此,調(diào)用startForeground()。此方法接受兩個(gè)參數(shù):一個(gè)在狀態(tài)欄中唯一標(biāo)識通知的正整數(shù)和notification對象本身。接著修改服務(wù)內(nèi)部的代碼:
首先創(chuàng)建一個(gè)通知 在MyService的oncreate方法和onStartCommand()方法中做如下修改:
public class MyService extends Service {
public MyService() {
}
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private NotificationManager manager;
// 從當(dāng)前線程接收消息的處理程序
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
Thread.sleep(5000);
Log.i("Myservice","當(dāng)前進(jìn)程編號"+ Thread.currentThread().getName()+" ·····正在處理任務(wù)");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
//服務(wù)處理完成后,使用startId停止服務(wù),這樣我們就不會在處理另一個(gè)作業(yè)時(shí)停止服務(wù)
// stopSelf(msg.arg1);
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
manager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Log.i("Myservice","onCreate");
//啟動運(yùn)行該服務(wù)的線程。因?yàn)槟J(rèn)情況下服務(wù)通常運(yùn)行在進(jìn)程的主線程中,我們不希望阻塞主線程。所以創(chuàng)建了一個(gè)單獨(dú)的線程,
// 我們還將其設(shè)置為后臺優(yōu)先級,這樣cpu密集型工作就不會破壞我們的UI。
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Myservice","onStartCommand");
// If the notification supports a direct reply action, use
// PendingIntent.FLAG_MUTABLE instead.
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE);
Notification notification =
null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//只在Android O之上需要渠道
NotificationChannel notificationChannel = new NotificationChannel("channelid1","channelname",NotificationManager.IMPORTANCE_HIGH);
//如果這里用IMPORTANCE_NOENE就需要在系統(tǒng)的設(shè)置里面開啟渠道,通知才能正常彈出
manager.createNotificationChannel(notificationChannel);
notification = new Notification.Builder(this, "channelid1")
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
}
// Notification ID cannot be 0.
startForeground(1, notification);
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
//對于每個(gè)開始請求,發(fā)送一個(gè)消息來開始一個(gè)作業(yè),并傳遞啟動ID,這樣我們就知道當(dāng)我們完成作業(yè)時(shí)我們正在停止哪個(gè)請求
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
//在當(dāng)前線程下執(zhí)行服務(wù)的任務(wù)。
serviceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("Myservice","onDestroy");
}
}
上述代碼塊,也不知道怎么突出高亮,所以大家自行對比一下區(qū)別吧。
經(jīng)過上述三步,我們來測試一下效果。
這會在前臺開啟一個(gè)通知,日志可見并沒有進(jìn)行銷毀。服務(wù)列表中也可查看當(dāng)前前臺服務(wù)運(yùn)行的時(shí)間:
這樣一個(gè)前臺任務(wù)就創(chuàng)建好了。
注意:狀態(tài)欄通知必須使用優(yōu)先級為PRIORITY_LOW或更高。如果你的應(yīng)用程序試圖使用一個(gè)優(yōu)先級較低的通知,系統(tǒng)會在通知抽屜中添加一條消息,提醒用戶應(yīng)用程序使用了前臺服務(wù)。
后臺啟動限制
針對Android 12 (API級別31)或更高版本的應(yīng)用程序不能在后臺運(yùn)行時(shí)啟動前臺服務(wù),除非有一些特殊情況。如果應(yīng)用程序在后臺運(yùn)行時(shí)試圖啟動前臺服務(wù),而前臺服務(wù)不滿足其中一個(gè)例外情況,系統(tǒng)將拋出ForegroundServiceStartNotAllowedException異常。
注意:如果一個(gè)應(yīng)用程序調(diào)用Context.startForegroundService()來啟動另一個(gè)應(yīng)用程序擁有的前臺服務(wù),這些限制只適用于兩個(gè)應(yīng)用程序都針對Android 12或更高版本。
檢查應(yīng)用程序是否執(zhí)行后臺啟動
為了更好地理解當(dāng)你的應(yīng)用程序試圖在后臺運(yùn)行時(shí)啟動前臺服務(wù),你可以啟用每當(dāng)這種行為發(fā)生時(shí)出現(xiàn)的通知。為此,在連接到測試設(shè)備或模擬器的開發(fā)機(jī)器上執(zhí)行以下ADB命令:
adb shell device_config put activity_manager \
default_fgs_starts_restriction_notification_enabled true
免除后臺啟動限制
在以下情況下,即使你的應(yīng)用程序在后臺運(yùn)行,你的應(yīng)用程序也可以啟動前臺服務(wù):
- 你的應(yīng)用從一個(gè)用戶可見的狀態(tài)轉(zhuǎn)換,比如一個(gè)活動。
- 你的應(yīng)用程序可以從后臺啟動一個(gè)活動,除非應(yīng)用程序在一個(gè)現(xiàn)有任務(wù)的后臺堆棧中有一個(gè)活動。
- 您的應(yīng)用程序使用Firebase云消息接收高優(yōu)先級消息。
注意:如果應(yīng)用程序沒有使用高優(yōu)先級消息向用戶顯示時(shí)間敏感的內(nèi)容,系統(tǒng)可以將高優(yōu)先級消息降級為正常優(yōu)先級。如果消息的優(yōu)先級被降級,你的應(yīng)用程序不能啟動前臺服務(wù),并且試圖啟動前臺服務(wù)會導(dǎo)致ForegroundServiceStartNotAllowedException異常。
-
因此,在嘗試啟動前臺服務(wù)之前,建議檢查RemoteMessage.getPriority() 的結(jié)果,并確認(rèn)它是PRIORITY_HIGH。
-
用戶在與應(yīng)用程序相關(guān)的UI元素上執(zhí)行操作。例如,他們可能與氣泡、通知、小部件或活動交互。
-
應(yīng)用程序調(diào)用精確的警報(bào)來完成用戶請求的操作。
-
你的應(yīng)用程序是設(shè)備當(dāng)前的輸入法。
-
你的應(yīng)用程序接收到一個(gè)與地理圍欄或活動識別轉(zhuǎn)換相關(guān)的事件。
-
在設(shè)備重啟并在廣播接收器中接收到ACTION_BOOT_COMPLETED、ACTION_LOCKED_BOOT_COMPLETED或action_my_package_replace意圖動作后。
-
你的應(yīng)用程序在廣播接收器中接收ACTION_TIMEZONE_CHANGED、ACTION_TIME_CHANGED或ACTION_LOCALE_CHANGED意圖動作。
-
您的應(yīng)用程序接收藍(lán)牙廣播,需要BLUETOOTH_CONNECT或BLUETOOTH_SCAN權(quán)限。應(yīng)用程序必須使用伙伴設(shè)備管理器,并聲明request_友情run_in_background或request_友情start_前臺服務(wù)_from_background權(quán)限。
-
具有特定系統(tǒng)角色或權(quán)限的應(yīng)用程序,例如設(shè)備所有者和配置文件所有者。
-
你的應(yīng)用程序使用伴侶設(shè)備管理器,并聲明了request_friend_start_foreground_services_from_background權(quán)限或request_friend_run_in_background權(quán)限。只要可能,使用request_友情start_前臺服務(wù)from_后臺。
-
用戶關(guān)閉應(yīng)用程序的電池優(yōu)化。你可以幫助用戶找到這個(gè)選項(xiàng),將他們發(fā)送到系統(tǒng)設(shè)置中的應(yīng)用程序信息頁面。為此,調(diào)用包含ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS意圖動作的意圖。
從前臺刪除一個(gè)服務(wù)
要從前臺刪除服務(wù),請調(diào)用stopForeground()。此方法接受一個(gè)布爾值,該值指示是否也刪除狀態(tài)欄通知。注意,服務(wù)將繼續(xù)運(yùn)行。如果在前臺運(yùn)行時(shí)停止服務(wù),則會刪除其通知。
@Override
public void onDestroy() {
super.onDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(Service.STOP_FOREGROUND_REMOVE);
}
Log.i("Myservice","onDestroy");
}
此時(shí)通知也會被移除。文章來源:http://www.zghlxwxcb.cn/news/detail-621440.html
總結(jié)
本文介紹了,如何使用前臺服務(wù),并介紹了前臺服務(wù)的特性。包括從開啟到停止,下一篇文章將介紹綁定服務(wù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-621440.html
到了這里,關(guān)于Android service(服務(wù))中的前臺服務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!