原文鏈接: https://blog.csdn.net/sinat_20059415/article/details/80584487
前言:最近在處理Android O的應用crash和anr問題,其中遇到比較多的就是“Context.startForegroundService() did not then call Service.startForeground()”,將自己的處理心得總結回顧一下。
demo:https://github.com/happyjiatai/demo_csdn/tree/master/demo_42_startforegroundservice
-------------------------------------------------------6月21日更新------------------------------------------------------
PS:我比較注重報錯及解決,有願意看代碼弄清代碼層次的前因後果的下面這篇寫的很好
https://blog.csdn.net/lylddinghffw/article/details/80366791
-------------------------------------------------------6月21日更新------------------------------------------------------
1.報錯堆棧
startForegroundService相關報錯堆棧分爲兩種:
1)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: FATAL EXCEPTION: main
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: Process: packageName,,,,,,,,,,,,,,,,,,,,,,,,, PID: 3986
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: java.lang.RuntimeException: java.lang.IllegalStateException: Not allowed to start service Intent { flg=0x1000000 cmp=packagename/.servicename (has extras) }: app is in background uid UidRecord{52db80 u2357s1000 TRNB bg:+2m42s199ms idle procs:3 seq(0,0,0)}
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:112)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.os.Looper.loop(Looper.java:168)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6555)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { flg=0x1000000 cmp=packagename/.servicename (has extras) }: app is in background uid UidRecord{52db80 u2357s1000 TRNB bg:+2m42s199ms idle procs:3 seq(0,0,0)}
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1522)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1478)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:661)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at packageName.ConnectionChangeJobService.onStartJob(ConnectionChangeJobService.java:102)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobService$1.onStartJob(JobService.java:71)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:108)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: ... 6 more
2)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: Process: packagename, PID: 13768
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1803)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.os.Looper.loop(Looper.java:168)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6555)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
在解決問題之前先打一下基礎,看一下Android O對後臺的限制。
2.Android O 後臺執行限制
2.1 後臺執行限制
Android 8.0 爲提高電池續航時間而引入的變更之一是,當您的應用進入已緩存狀態時,如果沒有活動的組件,系統將解除應用具有的所有喚醒鎖。
此外,爲提高設備性能,系統會限制未在前臺運行的應用的某些行爲。具體而言:
現在,在後臺運行的應用對後臺服務的訪問受到限制。
應用無法使用其清單註冊大部分隱式廣播(即,並非專門針對此應用的廣播)。
默認情況下,這些限制僅適用於針對 O 的應用。不過,用戶可以從 Settings 屏幕爲任意應用啓用這些限制,即使應用並不是以 O 爲目標平臺。
Android 8.0 還對特定函數做出了以下變更:
如果針對 Android 8.0 的應用嘗試在不允許其創建後臺服務的情況下使用 startService() 函數,則該函數將引發一個 IllegalStateException。(對應於堆棧一的報錯)
新的 Context.startForegroundService() 函數將啓動一個前臺服務。現在,即使應用在後臺運行,系統也允許其調用 Context.startForegroundService()。不過,應用必須在創建服務後的五秒內調用該服務的 startForeground() 函數。(對應於堆棧二的報錯,需要補充)
2.2 後臺服務限制
在後臺中運行的服務會消耗設備資源,這可能降低用戶體驗。 爲了緩解這一問題,系統對這些服務施加了一些限制。
系統可以區分 前臺 和 後臺 應用。 (用於服務限制目的的後臺定義與內存管理使用的定義不同;一個應用按照內存管理的定義可能處於後臺,但按照能夠啓動服務的定義又處於前臺。)如果滿足以下任意條件,應用將被視爲處於前臺:
- 具有可見 Activity(不管該 Activity 已啓動還是已暫停)。
- 具有前臺服務。
- 另一個前臺應用已關聯到該應用(不管是通過綁定到其中一個服務,還是通過使用其中一個內容提供程序)。 例如,如果另一個應用綁定到該應用的服務,那麼該應用處於前臺:
IME
壁紙服務
通知偵聽器
語音或文本服務
如果以上條件均不滿足,應用將被視爲處於後臺。
綁定服務不受影響
這些規則不會對綁定服務產生任何影響。 如果您的應用定義了綁定服務,則不管應用是否處於前臺,其他組件都可以綁定到該服務。
處於前臺時,應用可以自由創建和運行前臺服務與後臺服務。 進入後臺時,在一個持續數分鐘的時間窗內,應用仍可以創建和使用服務。
在該時間窗結束後,應用將被視爲處於 空閒 狀態。 此時,系統將停止應用的後臺服務,就像應用已經調用服務的“Service.stopSelf()”方法。
在這些情況下,後臺應用將被置於一個臨時白名單中並持續數分鐘。 位於白名單中時,應用可以無限制地啓動服務,並且其後臺服務也可以運行。
處理對用戶可見的任務時,應用將被置於白名單中,例如:
處理一條高優先級 Firebase 雲消息傳遞 (FCM) 消息。
接收廣播,例如短信/彩信消息。
從通知執行 PendingIntent。
在很多情況下,您的應用都可以使用 JobScheduler 作業替換後臺服務。 例如,CoolPhotoApp 需要檢查用戶是否已經從朋友那裏收到共享的照片,即使該應用未在前臺運行。
之前,應用使用一種會檢查其雲存儲的後臺服務。 爲了遷移到 Android 8.0,開發者使用一個計劃作業替換了這種後臺服務,該作業將按一定週期啓動,查詢服務器,然後退出。
在 Android 8.0 之前,創建前臺服務的方式通常是先創建一個後臺服務,然後將該服務推到前臺。
Android 8.0 有一項複雜功能;系統不允許後臺應用創建後臺服務。 因此,Android 8.0 引入了一種全新的方法,即 Context.startForegroundService(),以在前臺啓動新服務。
在系統創建服務後,應用有五秒的時間來調用該服務的 startForeground() 方法以顯示新服務的用戶可見通知。
如果應用在此時間限制內未調用 startForeground(),則系統將停止服務並聲明此應用爲 ANR。
3. 報錯解決方案
3.1 堆棧一報錯解決方案
將 調用 startService啓動Service 改爲調用 startForegroundService,這只是第一步,後續步驟請參考堆棧二報錯解決方案。
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: FATAL EXCEPTION: main
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: Process: packageName,,,,,,,,,,,,,,,,,,,,,,,,, PID: 3986
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: java.lang.RuntimeException: java.lang.IllegalStateException: Not allowed to start service Intent { flg=0x1000000 cmp=packagename/.servicename (has extras) }: app is in background uid UidRecord{52db80 u2357s1000 TRNB bg:+2m42s199ms idle procs:3 seq(0,0,0)}
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:112)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.os.Looper.loop(Looper.java:168)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6555)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { flg=0x1000000 cmp=com.mediatek.providers.drm/.DrmSyncTimeService (has extras) }: app is in background uid UidRecord{52db80 u2357s1000 TRNB bg:+2m42s199ms idle procs:3 seq(0,0,0)}
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1522)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1478)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:661)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at com.mediatek.providers.drm.ConnectionChangeJobService.onStartJob(ConnectionChangeJobService.java:102)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobService$1.onStartJob(JobService.java:71)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:108)
- 05-28 17:49:49.693516 3986 3986 E AndroidRuntime: ... 6 more
3.2 堆棧二報錯解決方案
堆棧二簡單來看就是調用了startForegroundService後需要在Service裏繼續調用Service.startForeground()即可,但有種情況是即使調用了還是報一樣的錯。
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: Process: packagename, PID: 13768
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1803)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.os.Looper.loop(Looper.java:168)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6555)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
- 06-11 15:48:15.602772 13768 13768 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
如果認真debug,會發現Service啓動的時候不會報錯,在Service.stopSelf的時候報錯,並且catch不到異常。5s內不停止服務還會有anr問題。
原因:
看下面的代碼,有種豁然開朗學到什麼東西的感覺,但是Google把這條路封掉了,Google本意就是讓沒有可見通知的應用不可以偷偷啓動服務在後臺幹着見不得人的事,怎麼可能留下後門。
- Notification notification = new Notification.Builder(mContext).build();
- startForeground(0, notification);
-
- * @param id The identifier for this notification as per
- * {@link NotificationManager#notify(int, Notification)
- * NotificationManager.notify(int, Notification)}; must not be 0.
- * @param notification The Notification to be displayed.
- *
- * @see #stopForeground(boolean)
- */
- public final void startForeground(int id, Notification notification) {
結合Service的startForeground api,其中重點強調了must not be 0,即禁止是0,既然使用了0,就不要怪Google讓應用crash了。但是是在Service.stopSelf時crash,代碼分析見後文框架修改解決方案。
3.2.1 正統解決方案
正統解決方案肯定是Google讓怎麼做就怎麼做呀,Google讓新建一個通知那就新建一個通知。
寫了一個demo,包含正確方式(btn1)和錯誤方式(btn2)
activity:
- package com.example.demo_42_startforegroundservice;
-
- import android.content.Intent;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.widget.Button;
-
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = "jiatai";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button btn = findViewById(R.id.btn);
- Button btn2 = findViewById(R.id.btn2);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "start service");
- Intent intent = new Intent(MainActivity.this,MyService.class);
- intent.putExtra("type",1);
- startForegroundService(intent);
- }
- });
-
- btn2.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Log.d(TAG, "start service");
- Intent intent = new Intent(MainActivity.this,MyService.class);
- intent.putExtra("type",2);
- startForegroundService(intent);
- }
- });
- }
- }
Service:
- package com.example.demo_42_startforegroundservice;
-
- import android.app.Notification;
- import android.app.NotificationChannel;
- import android.app.NotificationManager;
- import android.app.Service;
- import android.content.Context;
- import android.content.Intent;
- import android.graphics.Color;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.util.Log;
- import android.widget.Toast;
-
- public class MyService extends Service {
- private static final String TAG = "jiatai";
- private MyHandler handler;
- public MyService() {
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // TODO: Return the communication channel to the service.
- throw new UnsupportedOperationException("Not yet implemented");
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.d(TAG, "service oncreate");
- handler = new MyHandler();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, final int startId) {
- int type = intent.getIntExtra("type",1);
- Log.d(TAG, "the create notification type is " + type + "----" + (type == 1 ? "true" : "false"));
- if(type == 1){
- createNotificationChannel();
- }else{
- createErrorNotification();
- }
- new Thread(){
- @Override
- public void run() {
- super.run();
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- handler.sendEmptyMessage(startId);
- }
- }.start();
- return super.onStartCommand(intent, flags, startId);
- }
-
- private void createErrorNotification() {
- Notification notification = new Notification.Builder(this).build();
- startForeground(0, notification);
- }
-
- private void createNotificationChannel() {
- NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- // 通知渠道的id
- String id = "my_channel_01";
- // 用戶可以看到的通知渠道的名字.
- CharSequence name = getString(R.string.channel_name);
- // 用戶可以看到的通知渠道的描述
- String description = getString(R.string.channel_description);
- int importance = NotificationManager.IMPORTANCE_HIGH;
- NotificationChannel mChannel = new NotificationChannel(id, name, importance);
- // 配置通知渠道的屬性
- mChannel.setDescription(description);
- // 設置通知出現時的閃燈(如果 android 設備支持的話)
- mChannel.enableLights(true); mChannel.setLightColor(Color.RED);
- // 設置通知出現時的震動(如果 android 設備支持的話)
- mChannel.enableVibration(true);
- mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
- // 最後在notificationmanager中創建該通知渠道 //
- mNotificationManager.createNotificationChannel(mChannel);
-
- // 爲該通知設置一個id
- int notifyID = 1;
- // 通知渠道的id
- String CHANNEL_ID = "my_channel_01";
- // Create a notification and set the notification channel.
- Notification notification = new Notification.Builder(this)
- .setContentTitle("New Message") .setContentText("You've received new messages.")
- .setSmallIcon(R.drawable.ic_launcher_foreground)
- .setChannelId(CHANNEL_ID)
- .build();
- startForeground(1,notification);
- }
-
- private class MyHandler extends Handler{
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- stopSelf(msg.what);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.d(TAG, "5s onDestroy");
- Toast.makeText(this, "this service destroy", 1).show();
- stopForeground(true);
- }
- }
效果圖:
貼一下btn2 報錯堆棧:
- 06-16 09:52:12.109 783-907/system_process I/AnrManager: ANR in com.example.demo_42_startforegroundservice, time=689836
- Reason: Context.startForegroundService() did not then call Service.startForeground()
- Load: 10.13 / 9.55 / 5.83
- Android time :[2018-06-16 09:52:12.10] [694.364]
- --------- beginning of crash
- 06-16 09:52:12.132 7015-7015/com.example.demo_42_startforegroundservice E/AndroidRuntime: FATAL EXCEPTION: main
- Process: com.example.demo_42_startforegroundservice, PID: 7015
- android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
- at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1803)
- at android.os.Handler.dispatchMessage(Handler.java:106)
- at android.os.Looper.loop(Looper.java:168)
- at android.app.ActivityThread.main(ActivityThread.java:6555)
- at java.lang.reflect.Method.invoke(Native Method)
- at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
Google有點狠,crash+anr來了個全套,anr其實是我將Service延遲到了5s做完導致的,5s內如果沒有正統的調用startForeground就會anr。
PS: 網上所傳的notification隱藏是否可以?
有的需求是啓動服務,但是又不想有通知,這和Google定的規則有衝突呀,有沒有什麼辦法呢?網上2年前的方案是啓動兩個Service,一個Service幹活,另外一個Service把通知隱藏掉。
Android O Google應該考慮到這個漏洞了:
- private void cancelForegroundNotificationLocked(ServiceRecord r) {
- if (r.foregroundId != 0) {
- // First check to see if this app has any other active foreground services
- // with the same notification ID. If so, we shouldn't actually cancel it,
- // because that would wipe away the notification that still needs to be shown
- // due the other service.
- ServiceMap sm = getServiceMapLocked(r.userId);
- if (sm != null) {
- for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
- ServiceRecord other = sm.mServicesByName.valueAt(i);
- if (other != r && other.foregroundId == r.foregroundId
- && other.packageName.equals(r.packageName)) {
- // Found one! Abort the cancel.
- return;
- }
- }
- }
- r.cancelNotification();
- }
- }
如果前臺服務的通知還有被佔用,那就別想用其他服務把它幹掉了。
3.2.2 框架規避方案
修改方案(僅供參考):ActiveServices.java如下加一個packageName的crash的規避,anr同理,發出消息的地方可以修改爲不發出timeout消息,也可以在startForeground的時候就移除。(如果Service耗時小於5s,Service在stop流程的時候會將anr消息移除,可不修改)
- // Check to see if the service had been started as foreground, but being
- // brought down before actually showing a notification. That is not allowed.
- if (r.fgRequired) {
- Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
- + r);
- r.fgRequired = false;
- r.fgWaiting = false;
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
- if (r.app != null && !"packageName".equals(r.packageName)) {
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
- msg.obj = r.app;
- mAm.mHandler.sendMessage(msg);
- }
- }
修改原理:
編譯一個service.jar,打印報錯堆棧
- 01-01 07:02:17.669 918 1334 W ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{2d44a2d u0 packageName/.servicename}
- 01-01 07:02:17.669 918 1334 W ActivityManager: java.lang.Throwable
- 01-01 07:02:17.669 918 1334 W ActivityManager: at com.android.server.am.ActiveServices.bringDownServiceLocked(ActiveServices.java:2612)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at com.android.server.am.ActiveServices.bringDownServiceIfNeededLocked(ActiveServices.java:2559)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at com.android.server.am.ActiveServices.stopServiceTokenLocked(ActiveServices.java:792)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at com.android.server.am.ActivityManagerService.stopServiceToken(ActivityManagerService.java:18789)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:759)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3080)
- 01-01 07:02:17.669 918 1334 W ActivityManager: at android.os.Binder.execTransact(Binder.java:697)
找到對應拋出Context.startForegroundService() did not then call Service.startForeground()的邏輯代碼:
ActiveServices.java bringDownServiceLocked
- // Check to see if the service had been started as foreground, but being
- // brought down before actually showing a notification. That is not allowed.
- if (r.fgRequired) {
- Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
- + r);
- r.fgRequired = false;
- r.fgWaiting = false;
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
- if (r.app != null) {
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
- msg.obj = r.app;
- mAm.mHandler.sendMessage(msg);
- }
- }
走到這裏面繼而會由ams發出一個service_foreground_crash_msg的消息,導致crash。
至於爲嘛會走到這裏呢,都是id = 0 的過,既沒有走前臺服務的流程也沒有將r.fgRequired設爲false,anr的msg也沒有移除掉。
- private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
- Notification notification, int flags) {
- if (id != 0) {
- if (notification == null) {
- throw new IllegalArgumentException("null notification");
- }
- // Instant apps need permission to create foreground services.
- ...
- if (r.fgRequired) {
- if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
- Slog.i(TAG, "Service called startForeground() as required: " + r);
- }
- r.fgRequired = false;
- r.fgWaiting = false;
- mAm.mHandler.removeMessages(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
- }
- if (r.foregroundId != id) {
- cancelForegroundNotificationLocked(r);
- r.foregroundId = id;
- }
- notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
- r.foregroundNoti = notification;
- if (!r.isForeground) {
- final ServiceMap smap = getServiceMapLocked(r.userId);
- if (smap != null) {
- ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
- if (active == null) {
- active = new ActiveForegroundApp();
- active.mPackageName = r.packageName;
- active.mUid = r.appInfo.uid;
- active.mShownWhileScreenOn = mScreenOn;
- if (r.app != null) {
- active.mAppOnTop = active.mShownWhileTop =
- r.app.uidRecord.curProcState
- <= ActivityManager.PROCESS_STATE_TOP;
- }
- active.mStartTime = active.mStartVisibleTime
- = SystemClock.elapsedRealtime();
- smap.mActiveForegroundApps.put(r.packageName, active);
- requestUpdateActiveForegroundAppsLocked(smap, 0);
- }
- active.mNumActive++;
- }
- r.isForeground = true;
- }
- r.postNotification();
- if (r.app != null) {
- updateServiceForegroundLocked(r.app, true);
- }
- getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
- mAm.notifyPackageUse(r.serviceInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- } else {
- if (r.isForeground) {
- final ServiceMap smap = getServiceMapLocked(r.userId);
- if (smap != null) {
- decActiveForegroundAppLocked(smap, r);
- }
- r.isForeground = false;
- if (r.app != null) {
- mAm.updateLruProcessLocked(r.app, false, null);
- updateServiceForegroundLocked(r.app, true);
- }
- }
- if ((flags & Service.STOP_FOREGROUND_REMOVE) != 0) {
- cancelForegroundNotificationLocked(r);
- r.foregroundId = 0;
- r.foregroundNoti = null;
- } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
- r.stripForegroundServiceFlagFromNotification();
- if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
- r.foregroundId = 0;
- r.foregroundNoti = null;
- }
- }
- }
- }
anr的時限爲嘛是5s呢?
- void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
- if (r.app.executingServices.size() == 0 || r.app.thread == null) {
- return;
- }
- Message msg = mAm.mHandler.obtainMessage(
- ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
- msg.obj = r;
- r.fgWaiting = true;
- mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
- }
- // How long the startForegroundService() grace period is to get around to
- // calling startForeground() before we ANR + stop it.
- static final int SERVICE_START_FOREGROUND_TIMEOUT = 5*1000;
這種timeout流程就很熟悉了。
4. 總結
Android O 後臺應用想啓動服務就老老實實的加個notification給用戶看,表示你自己在後臺佔着資源,殺不殺由用戶決定,偷偷地在後臺跑沒有framework幫忙想都別想,一個anr+crash套餐瞭解一下。
1)activity: Context.startForegroundService()
2)Service:startForeground(int id, Notification notification)(id must not be 0)