(四十二)Context.startForegroundService() did not then call Service.startForeground?

前言:最近在处理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);
    }
}

效果图:

(四十二)Context.startForegroundService() did not then call Service.startForeground?_第1张图片

贴一下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)

你可能感兴趣的:(Android)