android前台service
① Android基礎:Service —— 默默為你服務
Service有兩種啟動方式,分別為 context.startService() 和 context.bindService() 。這里要提到Service的生命周期,兩種不同的啟動方式有不同的生命周期:
Tips:
首先創建自己的Service類,重寫其生命周期,並在mainfest.xml中進行注冊。
必須注冊Service,不然不會調用。簡單注冊:
補充下Service在manifest中的屬性以及作用:
接下來,我們就可以用下面的兩個方法來啟動和停止服務。
首先在我們的Activity中創建Service連接對象,重寫連接和斷開的方法。創建自定義的Binder對象,在 onServiceConnected() 中賦值然後可以調用自定義Binder中的方法。使用下方的bind()方法來綁定服務,使用 unBind() 來解綁服務。
這里會用到Service的 onBind() 和 onUnbind() 的生命周期,我們在TestService中重寫之。這里要注意的是,使用bindService()方法啟動的Service,不會調用 onStartCommand() 的生命周期。此外,創建自定義Binder類和對象。
這樣,當我們使用Activity中的bind()方法來綁定服務,會自動啟動服務,而我們又重寫了 onServiceConnected() 方法並使用myBinder來調用方法。這樣我們就可以用它來Activity和Service來進行通信。
特別Tips:
如果先使用 startService() 來開啟服務和 bindService() 來綁定服務,當使用 unbindService() 解綁時,Service並不會被銷毀。而是使用 stopService() 才能銷毀服務。
前台服務和後台服務的區別:
在Service中進行操作,將服務類型以前台的方式運行顯示在通知欄。
運行效果:
暫時引用吧,有空再實現一個:
參考資料:
② Android重學系列 Service 啟動和綁定原理
我們已經了解了BroadcastReceiver的原理,我們再來看看四大組件之一的Service是怎麼啟動的,以及怎麼運行的原理。
如果遇到什麼問題可以來到 https://www.jianshu.com/p/c4927c0b80a9 本文下進行交流
啟動Service的入口就是startService和bindService方法。我們先來看看startService在ContextImpl中做了什麼。
文件:/ frameworks / base / core / java / android / app / ContextImpl.java
此時調用的就是AMS的startService方法。
mServices是一個ActiveServices對象。這個對象是在AMS的構造函數中初始化好的。
這里調用了ActiveServices的startServiceLocked。
文件:/ frameworks / base / services / core / java / com / android / server / am / ActiveServices.java
核心流程有如下三個:
注意這里addToStarting是一個比較關鍵的判斷,addToStarting默認為false。
如果此時不是啟動前台服務,則需要進一步進行處理。如果ProcessRecord為空或者curProcState大於PROCESS_STATE_RECEIVER這個優先順序數值;也就是優先順序更小。
為了避免此時App應用是沒有任何的前台ui,或者App應用還沒有聲明。避免有的App通過startService進行應用的包活或者拉起應用。就會進行如下能夠存在的最大後台服務數量,則放入mDelayedStartList中進行延時啟動後台服務,現在直接返回了。
不然則說明能夠允許啟動後台服務, 就設置為addToStarting為true。
通過ComponentName也就是包名和類名查找ServiceRecord;通過Intent意圖過濾找到ServiceRecord。·
核心方法是bringUpServiceLocked。如果bringUpServiceLocked返回了異常,就返回一個特殊的ComponentName對象。
addToStarting為true,說明此時是一個能夠啟動的後台服務,則ServiceRecord添加到mStartingBackground中。如果mStartingBackground的數量為0,則直接調用ServiceMap的rescheleDelayedStartsLocked啟動後台服務。
這幾個關鍵的步驟,讓我們依次的考察,先來看看scheleCreateService中做了什麼。
如果第一次啟動就走第一個if的分支:
能看到和BroadcastReceiver的ANR思路一樣,通過一個延時的Handler,如果達到時間了還沒有移除這個Handler消息則報ANR異常。
這里根據啟動前台和後台分為兩種超時時間:
前台分別是10秒,後台服務不屬於後台進程組(判斷adj是否在SCHED_GROUP_BACKGROUND)是20秒,後台服務屬於後台進程組 200秒。
把數據封裝成CreateServiceData後,通過Handler調用如下方法:
透三點的核心原理可以看我寫的的Application創建和BroadcastReceiver原理兩篇文章。來看看Service中都做了什麼?
很簡單就是保存了傳遞過來的參數,值得注意的是這個IBinder對象其實就是指ServiceRecord對象。
接著執行Service.onCreate這個空實現的方法。
本質上還是調用了ActiveServices的serviceDoneExecutingLocked方法。
type是SERVICE_DONE_EXECUTING_ANON,所不會做更多的處理。 最後執行了serviceDoneExecutingLocked方法。
這個方法不斷的循環遍歷List<ServiceStartArgs>分發SERVICE_ARGS消息,這個消息通過主線程的Looper調用handleServiceArgs。
bindService在開發中用的不是很多,這里稍微提一下他的使用。
首先申明一個ServiceConnection對象,用於綁定服務端的Service。
調用bindService的方法綁定到某個Service中。當服務端的service成功回調onBind方法,我們只需要返回對應的Binder對象。就能使用調用bindService的客戶端在ServiceConnection的onServiceConnected的回調中獲得Binder對象。
之後就能通過這個Binder調用本進程或者其他進程的的方法了。實際上我們可以把這個過程看成一個多對多的服務-客戶端模型。多個客戶端通過Binder向多服務端Service通信,每一次我們都可以通過ComponentName判斷不同服務返回來的Binder對象。
這裡面很簡單,和BroadcastReceiver的思路很像。動態注冊的BroadcastReceiver會封裝成一個ReceiverDispatcher,而這里把ServiceConnection封裝成LoadedApk.ServiceDispatcher對象。
並且會把ServiceDispatcher作為value,ServiceConnection作為key緩存一個map中。並且以context為key,把這個臨時的map作為value緩存起來。這樣一個Context就映射有了多個ServiceDispatcher對象,也就可以注冊多個監聽被綁定Service狀態的監聽者了。
③ Android 如何進行進程保活
每一個 Android 應用啟動後至少對應一個進程,有的是多個進程,而且主流應用中多個
進程的應用比例較大
對於任何一個進程,我們都可以通過 adb shell ps|grep <package_name>的方式來查看
它的基本信息
Android 中的進程跟封建社會一樣,分了三流九等,Android 系統把進程的劃為了如下
幾種(重要性從高到低),網上多位大神都詳細總結過(備註:嚴格來說是劃分了 6 種)。
場景:
1.某個進程持有一個正在與用戶交互的 Activity 並且該 Activity 正處於 resume 的
狀態。
2.某個進程持有一個 Service,並且該 Service 與用戶正在交互的 Activity 綁定。
3.某個進程持有一個 Service,並且該 Service 調用 startForeground()方法使之位於前台運行。
4.某個進程持有一個 Service,並且該 Service 正在執行它的某個生命周期回調方法,比如 onCreate()、 onStart()或 onDestroy()。
5.某個進程持有一個 BroadcastReceiver,並且該 BroadcastReceiver 正在執行其onReceive()方法。用戶正在使用的程序,一般系統是不會殺死前台進程的,除非用戶強制停止應用或者系統內存不足等極端情況會殺死。
場景:
1.擁有不在前台、但仍對用戶可見的 Activity(已調用 onPause())。
2.擁有綁定到可見(或前台)Activity 的 Service
用戶正在使用,看得到,但是摸不著,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程
不包含任何前台組件,一般系統也是不會殺死可見進程的,除非要在資源吃緊的情況下,
要保持某個或多個前台進程存活
場景
1.某個進程中運行著一個 Service 且該 Service 是通過 startService()啟動的,與用戶看見的界面沒有直接關聯。
在內存不足以維持所有前台進程和可見進程同時運行的情況下,服務進程會被殺死
場景:
在用戶按了"back"或者"home"後,程序本身看不到了,但是其實還在運行的程序,
比如 Activity 調用了 onPause 方法系統可能隨時終止它們,回收內存
場景:
某個進程不包含任何活躍的組件時該進程就會被置為空進程,完全沒用,殺了它只有好處沒壞處,第一個干它!
上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,app 在退到
後台時系統並不會真正的 kill 掉這個進程,而是將其緩存起來。打開的應用越多,後台緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制
來判斷要 kill 掉哪些進程,以騰出內存來供給需要的 app, 這套殺進程回收內存的機制
就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,我們可以使用
cat /sys/mole/lowmemorykiller/parameters/minfree 來查看某個手機的內存閾值。
其實系統在進程回收跟內存回收類似也是有一套嚴格的策略,可以
自己去了解,大概是這個樣子的,oom_adj 越大,佔用物理內存越多會被最先 kill 掉,OK,那麼現在對於進程如何保活這個問題就轉化成,如何降低 oom_adj 的值,以及如
何使得我們應用占的內存最少。
據說這個是手 Q 的進程保活方案,基本思想,系統一般是不會殺死前台進程的。所以要
使得進程常駐,我們只需要在鎖屏的時候在本進程開啟一個 Activity,為了欺騙用戶,
讓這個 Activity 的大小是 1 像素,並且透明無切換動畫,在開屏幕的時候,把這個 Activity
關閉掉,所以這個就需要監聽系統鎖屏廣播,我試過了,的確好使,如下。
如果直接啟動一個 Activity,當我們按下 back 鍵返回桌面的時候,oom_adj 的值是 8,
上面已經提到過,這個進程在資源不夠的情況下是容易被回收的。現在造一個一個像素
的 Activity。
為了做的更隱藏,最好設置一下這個 Activity 的主題,當然也無所謂了
在屏幕關閉的時候把 LiveActivity 啟動起來,在開屏的時候把 LiveActivity 關閉掉,所以
要監聽系統鎖屏廣播,以介面的形式通知 MainActivity 啟動或者關閉 LiveActivity。
現在 MainActivity 改成如下
按下 back 之後,進行鎖屏,現在測試一下 oom_adj 的值
果然將進程的優先順序提高了。
但是還有一個問題,內存也是一個考慮的因素,內存越多會被最先 kill 掉,所以把上面
的業務邏輯放到 Service 中,而 Service 是在另外一個 進程中,在 MainActivity 開啟這
個服務就行了,這樣這個進程就更加的輕量,
OK,通過上面的操作,我們的應用就始終和前台進程是一樣的優先順序了,為了省電,
系統檢測到鎖屏事件後一段時間內會殺死後台進程,如果採取這種方案,就可以避免了
這個問題。但是還是有被殺掉的可能,所以我們還需要做雙進程守護,關於雙進程守護,
比較適合的就是 aidl 的那種方式,但是這個不是完全的靠譜,原理是 A 進程死的時候,
B 還在活著,B 可以將 A 進程拉起來,反之,B 進程死的時候,A 還活著,A 可以將 B
拉起來。所以雙進程守護的前提是,系統殺進程只能一個個的去殺,如果一次性殺兩個,
這種方法也是不 OK 的。
事實上
那麼我們先來看看 Android5.0 以下的源碼,ActivityManagerService 是如何關閉在應用
退出後清理內存的
在應用退出後,ActivityManagerService 不僅把主進程給殺死,另外把主進程所屬的進
程組一並殺死,這樣一來,由於子進程和主進程在同一進程組,子進程在做的事情,也
就停止了。所以在 Android5.0 以後的手機應用在進程被殺死後,要採用其他方案。
這種大部分人都了解,據說這個微信也用過的進程保活方案,移步微信 Android 客戶端
後台保活經驗分享,這方案實際利用了 Android 前台 service 的漏洞。
原理如下
對於 API level < 18 :調用 startForeground(ID, new Notification()),發送空的
Notification ,圖標則不會顯示。
對於 API level >= 18:在需要提優先順序的 service A 啟動一個 InnerService,兩個服務
同時 startForeground,且綁定同樣的 ID。Stop 掉 InnerService ,這樣通知欄圖標即
被移除。
public class KeepLiveService extends Service{
public static final int NOTIFICATION_ID=0x11;
public KeepLiveService() {
}
@Override
public IBinder onBind(Intent intent){
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate(); //API 18 以下,直 接發 送 Notification 並 將 其 置 為 前 台
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(NOTIFICATION_ID,new Notification());
} else { //API 18 以上,發送 Notification 並將其置為前台後,啟動 InnerService
Notification.Builder builder=new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}
public class InnerService extends Service{
@Override public IBinder onBind(Intent intent) {
return null;
}
@Override public void onCreate() {
super.onCreate(); //發送與 KeepLiveService中 ID 相同的 Notification,然後將其取消並取消自己的前台顯示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);startForeground(NOTIFICATION_ID,
builder.build());
new Handler().postDelayed(new Runnable() {
@Override public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},
100);
}
}
}
在沒有採取前台服務之前,啟動應用,oom_adj 值是 0,按下返回鍵之後,變成 9(不
同 ROM 可能不一樣)
相互喚醒的意思就是,假如你手機里裝了支付寶、淘寶、天貓、UC 等阿里系的 app,
那麼你打開任意一個阿里系的 app 後,有可能就順便把其他阿里系的 app 給喚醒了。
這個完全有可能的。此外,開機,網路切換、拍照、拍視頻時候,利用系統產生的廣播
也能喚醒 app,不過 Android N 已經將這三種廣播取消了。
如果應用想保活,要是 QQ,微信願意救你也行,有多少手機上沒有 QQ,微信呢?或
者像友盟,信鴿這種推送 SDK,也存在喚醒 app 的功能。
拉活方法
JobSheler是作為進程死後復活的一種手段,
native進程方式最大缺點是費電,Native
進程費電的原因是感知主進程是否存活有兩種實現方式,在 Native 進程中通過死循環
或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次 5.0 以上系統
不支持。 但是 JobSheler 可以替代在 Android5.0 以上 native 進程方式,這種方式即
使用戶強制關閉,也能被拉起來,親測可行。
JobSheler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheler();
}
public void startJobSheler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5); builder.setPersisted(true); JobScheler jobScheler =(JobScheler)
this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheler.schele(builder.build());
}
catch
(Exception ex)
{ ex.printStackTrace(); } }
@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
} @Override public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
這個是系統自帶的,onStartCommand 方法必須具有一個整形的返回值,這個整形的返
回值用來告訴系統在服務啟動完畢後,如果被 Kill,系統將如何操作,這種方案雖然可
以,但是在某些情況 or 某些定製 ROM 上可能失效,我認為可以多做一種保保守方案。
1.START_STICKY
如果系統在 onStartCommand 返回後被銷毀,系統將會重新創建服務並依次調用
onCreate 和 onStartCommand(注意:根據測試 Android2.3.3 以下版本只會調用
onCreate 根本不會調用 onStartCommand,Android4.0 可以辦到),這種相當於服務
又重新啟動恢復到之前的狀態了)。
2.START_NOT_STICKY
如果系統在 onStartCommand 返回後被銷毀,如果返回該值,則在執行完
onStartCommand 方法後如果 Service 被殺掉系統將不會重啟該服務
3.START_REDELIVER_INTENT
START_STICKY 的兼容版本,不同的是其不保證服務被殺後一定能重啟。
4.相比與粘性服務與系統服務捆綁更厲害一點,這個來自愛哥的研究,這里說的系統服務
很好理解,比如 NotificationListenerService,NotificationListenerService 就是一個監聽
通知的服務,只要手機收到了通知,NotificationListenerService 都能監聽到,即時用戶
把進程殺死,也能重啟,所以說要是把這個服務放到我們的進程之中,那麼就可以呵呵
了
所以你的應用要是有消息推送的話,那麼可以用這種方式去欺騙用戶。
④ 如何讓Android service進程變成前台進程
這里只是修改了Service中onCreate()方法的代碼。可以看到,我們首先創建了一個Notification對象,然後調用了它的setLatestEventInfo()方法來為通知初始化布局和數據,並在這里設置了點擊通知後就打開MainActivity。然後調用startForeground()方法就可以讓MyService變成一個前台Service,並會將通知的圖片顯示出來。
現在重新運行一下程序, Service就會以前台Service的模式啟動了,並且在系統狀態欄會彈出一個通欄圖標,下拉狀態欄後可以看到通知的詳細內容
⑤ Android應用組件 —— Service
官方原文: 地址
本文摘錄自官方原文,方便自己觀看。
service 是一個可以在後台長時間運行的操作而不提供用戶界面的應用組件。服務可以由其他應用組件啟動,而且即使用戶切換到其他應用程序,服務仍將在後台繼續運行。此外,組件可以綁定到服務,以與之進行交互,甚至執行進程間的通信(IPC)
服務基本分為兩種形式:
啟動
綁定
上述雖然分開概括這兩種服務,但是服務可以同時以這兩種方式運行,也就是說,他既可以是啟動服務(以無限期運行),也允許綁定。問題在於是否實現了一組回調方法: onStartCommand() (允許組件啟動服務)和 onBing() (允許綁定服務)。
無論應用是出於啟動狀態還是綁定狀態,亦或處於啟動並且綁定狀態,任何應用組件均可以像使用Activity那麼調用Itent來使用服務(即使此服務來自另一應用)。 不過,您可以通過清單文件將服務聲明為私有服務,並阻止其他應用訪問。 使用清單文件聲明服務部分將對此做更詳盡的闡述。
注意:
服務在其託管進程的主線程中運行,它既不創建自己的線程,也不在單獨的進程中運行(除非另行指定)。這意味著,如果服務將執行任何CPU密集型工作或者阻止性操作(我理解為耗時操作,例如 MP3 播放或聯網),則應在服務內創建新線程來完成這項工作。通過使用單獨的線程,可以降低發生「應用無響應」(ANR) 錯誤的風險,而應用的主線程仍可繼續專注於運行用戶與 Activity 之間的交互。
要創建服務,您必須創建 Service 的子類(或使用它的一個現有子類)。在實現中,您需要重寫一些回調方法,以處理服務生命周期的某些關鍵方面並提供一種機制將組件綁定到服務(如適用)。 應重寫的最重要的回調方法包括:
onStartCommand()
onBind()
onCreate()
onDestroy()
如果組件通過調用 startService() 啟動服務(這會導致對 onStartCommand() 的調用),則服務將一直運行,直到服務使用 stopSelf() 自行停止運行,或由其他組件通過調用 stopService() 停止它為止。
如果組件是通過調用 bindService() 來創建服務(且未調用 onStartCommand() ,則服務只會在該組件與其綁定時運行。一旦該服務與所有客戶端之間的綁定全部取消,系統便會銷毀它。
如同 Activity(以及其他組件)一樣,您必須在應用的清單文件中聲明所有服務。
要聲明服務,請添加 <service> 元素作為 <application> 元素的子元素。例如:
為了確保應用的安全性, 請始終使用顯式 Intent 啟動或綁定 Service,且不要為服務聲明 Intent 過濾器。 啟動哪個服務存在一定的不確定性,而如果對這種不確定性的考量非常有必要,則可為服務提供 Intent 過濾器並從 Intent 中排除相應的組件名稱,但隨後必須使用 setPackage() 方法設置 Intent 的軟體包,這樣可以充分消除目標服務的不確定性。
此外,還可以通過添加 android:exported 屬性並將其設置為 "false" ,確保服務僅適用於您的應用。這可以有效阻止其他應用啟動您的服務,即便在使用顯式 Intent 時也如此
Service
IntentService
簡單地說,服務是一種即使用戶未與應用交互也可在後台運行的組件。 因此,您應僅在必要時才創建服務。
如需在主線程外部執行工作,不過只是在用戶正在與應用交互時才有此需要,則應創建新線程而非服務。 例如,如果您只是想在 Activity 運行的同時播放一些音樂,則可在 onCreate() 中創建線程,在 onStart() 中啟動線程,然後在 onStop() 中停止線程。您還可以考慮使用 AsyncTask 或 HandlerThread,而非傳統的 Thread 類。
前台服務被認為是用戶主動意識到的一種服務,因此在內存不足時,系統也不會考慮將其終止。 前台服務必須為狀態欄提供通知,放在「正在進行」標題下方,這意味著除非服務停止或從前台移除,否則不能清除通知。
要請求讓服務運行於前台,請調用 startForeground() 。此方法採用兩個參數:唯一標識通知的整型數和狀態欄的 Notification 。例如:
注意 :提供給 startForeground() 的整型 ID 不得為 0。
要從前台移除服務,請調用 stopForeground() 。此方法採用一個布爾值,指示是否也移除狀態欄通知。 此方法不會停止服務。 但是,如果您在服務正在前台運行時將其停止,則通知也會被移除。
與 Activity 類似,服務也擁有生命周期回調方法,您可以實現這些方法來監控服務狀態的變化並適時執行工作。 以下框架服務展示了每種生命周期方法:
注 :與 Activity 生命周期回調方法不同,您 不 需要調用這些回調方法的超類實現。
注 :盡管啟動服務是通過調用 stopSelf() 或 stopService() 來停止,但是該服務並無相應的回調(沒有 onStop() 回調)。因此,除非服務綁定到客戶端,否則在服務停止時,系統會將其銷毀 — onDestroy() 是接收到的唯一回調。
⑥ Android的startForeground前台Service如何去掉通知顯示
首先寫一個BootstarpService,顧名思義,這個service只是起引導作用,幹完活就退出了。最精華的部分其實就是這句stopSelf(),說白了這個service其實還沒起起來就被停掉了,這樣onDestroy()里就會調用stopForeground(),通知欄的常駐通知就會被消掉。
[java]viewplain
{
@Override
publicvoidonCreate(){
super.onCreate();
startForeground(this);
//
stopSelf();
}
@Override
publicvoidonDestroy(){
super.onDestroy();
stopForeground(true);
}
(Servicecontext){
NotificationManagernm=(NotificationManager)context.getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builderbuilder=newNotificationCompat.Builder(context);
builder.setContentTitle("I'mrunning")
.setContentText("")
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_MIN)
.setSmallIcon(R.drawable.notification_icon)
.setAutoCancel(true);
Notificationnotification=builder.build();
context.startForeground(8888,notification);
}
}
接下來寫我們的主service,主service會先調用一次startForeground(),然後再啟動BootstrapService。
[java]viewplain
{
@Override
publicvoidonCreate(){
super.onCreate();
BootstrapService.startForeground(this);
//
Intentintent=newIntent(this,BootstrapService.class);
startService(intent);
}
@Override
publicvoidonDestroy(){
super.onDestroy();
stopForeground(true);
}
}
看到這里大家應該已經明白了,說白了就是兩個service共用一個notificationID,第一個service起來的時候會顯示通知欄,然後第二個service停掉的時候去除通知欄。
最後再doubleconfirm一下主service是前台運行的:
[plain]viewplain
root@hammerhead:/#mpsysactivityservices|grepxinxin
*ServiceRecord{f393843u0com.xinxin.startforeground/.MainService}
intent={cmp=com.xinxin.startforeground/.MainService}
packageName=com.xinxin.startforeground
processName=com.xinxin.startforeground
baseDir=/data/app/com.xinxin.startforeground-1/base.apk
dataDir=/data/user/0/com.xinxin.startforeground
app=ProcessRecord{6af09dd22261:com.xinxin.startforeground/u0a122}
isForeground=trueforegroundId=8888foregroundNoti=Notification(pri=-2contentView=com.xinxin.startforeground/0x1090085vibrate=nullsound=nulldefaults=0x0flags=0x72color=0x00000000vis=PRIVATE)
可以看到isForeground=true,主service是不受影響的。
說到底我覺得這應該算是個漏洞,我要是google工程師給每個notification加個referencecount你就跪得筆挺挺的...不過不管怎麼說,目前這個方法還是能夠解決問題的。
⑦ Android 服務的限制
Google官網將Android服務分為了三種,前台服務,後台服務和綁定服務:
前台服務執行一些用戶能注意到的操作。例如,音頻應用會使用前台服務來播放音頻曲目。前台服務必須顯示 通知 。即使用戶停止與應用的交互,前台服務仍會繼續運行。
後台服務執行用戶不會直接注意到的操作。例如,如果應用使用某個服務來壓縮其存儲空間,則此服務通常是後台服務。
當應用組件通過調用 bindService() 綁定到服務時,服務即處於 綁定 狀態。綁定服務會提供客戶端-伺服器介面,以便組件與服務進行交互、發送請求、接收結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。僅當與另一個應用組件綁定時,綁定服務才會運行。多個組件可同時綁定到該服務,但全部取消綁定後,該服務即會被銷毀。
我個人理解服務可以分為兩種, 前台 和 後台 ,而 綁定 應該是被當作一種狀態,因為 前台服務 和 後台服務 都可以進行綁定。
基於這個理解,我們將限制分成了前台和後台兩個部分:
從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調用 bindService() ,則系統會拋出異常。為確保應用的安全性,在啟動 Service 時,請始終使用顯式 Intent,且不要為服務聲明 Intent 過濾器。
在後台中運行的 Service 會消耗設備資源,這可能會降低用戶體驗。 為了緩解這一問題,系統對這些 Service 施加了一些限制。
處於前台時,應用可以自由創建和運行前台與後台 Service。
Android 8.0 開始:系統不允許後台應用創建後台 Service。否則該函數將引發一個 IllegalStateException。
Android 8.0 開始:進入後台時,在一個持續數分鍾的時間窗內,應用仍可以創建和使用 Service。 在該時間窗結束後,應用將被視為處於 空閑 狀態。 此時,系統將停止應用的後台 Service,就像應用已經調用 Service 的 Service.stopSelf() 方法一樣。
為了解除這種限制,可以使用 JobScheler 作業替換後台 Service。
在 Android 8.0 之前,創建前台 Service 的方式通常是先創建一個後台 Service,然後將該 Service 推到前台。
而在Android 8.0 之後,系統不允許後台應用創建後台 Service。
解決方案:調用 startForegroundService() ,以在前台啟動新 Service。
在系統創建 Service 後,應用有五秒的時間來調用該 Service 的 startForeground() 方法以顯示新 Service 的用戶可見通知。 如果應用在此時間限制內 未 調用 startForeground() ,則系統將停止此 Service 並聲明此應用為 ANR 。
前台服務必須顯示優先順序為 PRIORITY_LOW 或更高的 狀態欄通知 ,這有助於確保用戶知道應用正在執行的任務。如果某操作不是特別重要,因而您希望使用最低優先順序通知,則可能不適合使用服務;相反,您可以考慮使用 計劃作業 。
在 Android 9 (API 28)之後,使用前台服務必須申請 FOREGROUND_SERVICE 許可權,否則會報 SecurityException 。 這是普通許可權,因此,系統會自動為請求許可權的應用授予此許可權。
每個運行服務的應用都會給系統帶來額外負擔,從而消耗系統資源。如果應用嘗試使用低優先順序通知隱藏其服務,則可能會降低用戶正在主動交互的應用的性能。因此,如果某個應用嘗試運行擁有最低優先順序通知的服務,則系統會在抽屜式通知欄的底部調用出該應用的行為。
以 Android 12 為目標平台的應用在後台運行時無法再啟動 前台服務 。
在 Android 11 及以後,系統對前台服務何時可以訪問設備的位置、攝像頭或麥克風進行了限制。
如果您的應用以 Android 11 或更高版本為目標平台,且在前台服務中訪問攝像頭或麥克風,則必須添加 前台服務類型 camera 和 microphone 。
如果你的應用 在後台運行時啟動了某項前台服務 :
如果某服務的功能(位置、麥克風 和 相機)受到了限制,則Logcat中會列印如下語句:
⑧ Android中怎麼啟動關閉Service及功能解釋
下面根據問題,作出詳細解答:
Service不是分離開的進程,除非其他特殊情況,它不會運行在自己的進程,而是作為啟動運行它的進程的一部分。
Service不是線程,這意味著它將在主線程里勞作。
啟動service有兩種方法:
Context.startService()調用者與服務之間沒有關聯,即使調用者退出,服務仍可運行
Context.bindService() 調用者與服務綁定在一起,調用者一旦退出,服務也就終止
Service的生命周期
如果使用startService()啟動service,系統將通過傳入的Intent在底層搜索相關符合Intent裡面信息的service。如果服務沒有啟動則先運行onCreate,然後運行onStartCommand (可在裡面處理啟動時傳過來的Intent和其他參數),直到明顯調用stopService或者stopSelf才將停止Service。無論運行startService多少次,只要調用一次stopService或者stopSelf,Service都會停止。使用stopSelf(int)方法可以保證在處理好intent後再停止。
控制service運行的主要方式有兩種,主要是根據onStartCommand方法返回的數值。方法:
START_STICKY
START_NOT_STICKY or START_REDELIVER_INTENT
這里主要解釋這三個變數的意義:
START_STICKY
在運行onStartCommand後service進程被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新創建,因為保留在開始狀態,在創建 service後將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent
START_NOT_STICKY
在運行onStartCommand後service進程被kill後,並且沒有新的intent傳遞給它。Service將移出開始狀態,並且直到新的明顯的方法(startService)調用才重新創建。因為如果沒有傳遞任何未決定的intent那麼service是不會啟動,也就是期間onstartCommand不會接收到任何null的intent。
START_REDELIVER_INTENT
在運行onStartCommand後service進程被kill後,系統將會再次啟動service,並傳入最後一個intent給onstartCommand。直到調用stopSelf(int)才停止傳遞intent。如果在被kill後還有未處理好的intent,那被kill後服務還是會自動啟動。因此onstartCommand不會接收到任何null的intent。
客戶端也可以使用bindService來保持跟service持久關聯。謹記:如果使用這種方法,那麼將不會調用onstartCommand(跟startService不一樣,下面例子注釋也有解析,大家可試試)。客戶端將會在onBind回調中接收到IBinder介面返回的對象。通常IBinder作為一個復雜的介面通常是返回aidl數據。
Service也可以混合start和bind一起使用。
要運行service,首先必須在AndroidManifest.xml里申明<service>標簽。
Service能夠保護個人的IPC調用,所以在執行實現該調用時前先使用checkCallingPermission(String) 方法檢查是否有這個許可權。
進程生命周期
當service運行在低內存的環境時,將會kill掉一下存在的進程。因此進程的優先順序將會很重要:
如果service當前正在執行onCreate、onStartCommand、onDestroy方法,主進程將會成為前台進程來保證代碼可以執行完成避免被kill
如果service已經啟動了,那麼主進程將會比其他可見的進程的重要性低,但比其他看不見的進程高。因為只有少部分進程始終是用戶可見的,因此除非在極度低內存的時候,不然 service是不會被kill的。
如果有客戶端關聯到service,那麼service永遠比客戶端重要。也就是說客戶端可見,那麼service也可見(我理解這里的可見並不是可以看到,而是重要性,因為可見往往就表示重要性高)。
Service可以使用startForeground API將service放到前台狀態。這樣在低內存時被kill的幾率更低,但是文檔後面又寫了,如果在極度極度低內存的壓力下,該service理論上還是會被kill掉。但這個情況基本不用考慮。
當然如果service怎麼保持還是被kill了,那你可以通過重寫onStartCommand返回變數來設置它的啟動方式。比如:START_STICKY、START_REDELIVER_INTENT等等,前面已經討論了它們的作用,這里就不再累贅了
另外:
service 的onCreate和onStartCommand 是運行在主線程的,所以如果裡面有處理耗時間的任務。兩種處理:
請將它們都挪到新的線程里。
用系統提供的IntentService,它繼承了Service,它處理數據是用自身新開的線程。
⑨ Android的startForeground前台Service如何去掉通知顯示
一、正常的前台Service
我們都知道,Service幾乎都是在後台運行的,所以Service的系統優先順序還是比較低的,當系統出現內存不足情況時,就有可能回收掉正在後台運行的Service。如果你希望Service可以一直保持運行狀態,而不會由於系統內存不足的原因導致被回收,那麼就要提高Service的優先順序,而提高優先順序的方法有多種,其中一種就是考慮使用前台Service。
如何把Service設置為前台Service?很簡單,使用startForeground即可。要取消前台,使用stopForeground即可。
不多說,直接上代碼,非常的簡單,不解釋:
public class MyService extends Service {
private static final String TAG = "wxx";
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.d(TAG, "MyService: onCreate()");
//定義一個notification
Notification notification = new Notification();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, "My title", "My content", pendingIntent);
//把該service創建為前台service
startForeground(1, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Log.d(TAG, "MyService: onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}
}
有留心的朋友會發現,每當啟動該前台Service的時候,手機都會收到一個通知,下拉通知欄,會看到一個通知如「XXService正在運行。」,如下圖:
查看文檔,知道,當SDK<18時,系統不會有該通知,當SDK>=18時,系統有顯示該通知。系統顯示該通知,應該是為了防止濫用「startForeground」。
那,如果我就是不想顯示該通知給用戶?怎麼搞?
二、去掉startForeground的通知
本人之前因接觸過一些通知相關的內容,於是,大膽假設:把2個同進程的Service都用startForeground設置為前台進程,但他們使用相同的Notification ID,那麼他們只會產生一個通知,然後把其中一個Service取消前台效果,那麼就會把通知關閉,剩下的那個Service就是前台Service了,而且通知欄沒有通知。
有了假設,當然就要驗證是否可行~~
看代碼吧。。。
先看最後要保留的那個Service的代碼:
public class MyService extends Service {
private static final String TAG = "wxx";
private final int PID = android.os.Process.myPid();
private AssistServiceConnection mConnection;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.d(TAG, "MyService: onCreate()");
setForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Log.d(TAG, "MyService: onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d(TAG, "MyService: onDestroy()");
}
public void setForeground() {
// sdk < 18 , 直接調用startForeground即可,不會在通知欄創建通知
if (VERSION.SDK_INT < 18) {
this.startForeground(PID, getNotification());
return;
}
if (null == mConnection) {
mConnection = new AssistServiceConnection();
}
this.bindService(new Intent(this, AssistService.class), mConnection,
Service.BIND_AUTO_CREATE);
}
private class AssistServiceConnection implements ServiceConnection {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "MyService: onServiceDisconnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.d(TAG, "MyService: onServiceConnected");
// sdk >=18
// 的,會在通知欄顯示service正在運行,這里不要讓用戶感知,所以這里的實現方式是利用2個同進程的service,利用相同的notificationID,
// 2個service分別startForeground,然後只在1個service里stopForeground,這樣即可去掉通知欄的顯示
Service assistService = ((AssistService.LocalBinder) binder)
.getService();
MyService.this.startForeground(PID, getNotification());
assistService.startForeground(PID, getNotification());
assistService.stopForeground(true);
MyService.this.unbindService(mConnection);
mConnection = null;
}
}
private Notification getNotification() {
// 定義一個notification
Notification notification = new Notification();
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
notification.setLatestEventInfo(this, "My title", "My content",
pendingIntent);
return notification;
}
再看那個輔助消除通知的Service的代碼,非常的簡單:
public class AssistService extends Service {
private static final String TAG = "wxx";
public class LocalBinder extends Binder {
public AssistService getService() {
return AssistService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "AssistService: onBind()");
return new LocalBinder();
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d(TAG, "AssistService: onDestroy()");
}
}
代碼就這么多,上面大體思路是:一個最後保留的MyService,一個輔助消除通知的AssistService, 利用MyService去綁定AssistService,在關聯函數onServiceConnected()中實現兩個Service調用startForeground變為前台服務,注意一定要使用一樣的Notification ID,然後AssistService取消前台效果stopForeground從而刪除通知。
⑩ Android的startForeground前台Service如何去掉通知顯示
設置成前台的service一般的清理軟體就殺不掉service了~~//,SERVICE_=newNotification();status.flags|=Notification.FLAG_FOREGROUND_SERVICE;startForeground(SERVICE_STATUS,status);