有关前台服务的startForegroundService接口和startForeground接口

Android从SDK26开始增加了启动服务的startForegroundService接口, 该接口需要在服务启动后调用startForeground接口,服务将成为前台服务,其相对普通服务,有更高的优先级.如果服务启动后在指定的时间内没有调用startForeground, 服务将被终止,并且抛出ANR. 出错信息类似如下:

E/AndroidRuntime: FATAL EXCEPTION: main
                                Process: com.foregroundservice.demo, PID: 19005
                                android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
                                    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1800)
                                    at android.os.Handler.dispatchMessage(Handler.java:106)
                                    at android.os.Looper.loop(Looper.java:164)
                                    at android.app.ActivityThread.main(ActivityThread.java:6566)
                                    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)

当调用启动服务接口startForegroundService时, 会依次调用到bringUpServiceLocked–>realStartServiceLocked–>sendServiceArgsLocked , 该函数的实现为(ActiveServices.java):

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
        final int N = r.pendingStarts.size();
        if (N == 0) {
            return;
        }

        ArrayList<ServiceStartArgs> args = new ArrayList<>();

        while (r.pendingStarts.size() > 0) {
            ServiceRecord.StartItem si = r.pendingStarts.remove(0);
            if (DEBUG_SERVICE) {
                Slog.v(TAG_SERVICE, "Sending arguments to: "
                        + r + " " + r.intent + " args=" + si.intent);
            }
            if (si.intent == null && N > 1) {
                // If somehow we got a dummy null intent in the middle,
                // then skip it.  DO NOT skip a null intent when it is
                // the only one in the list -- this is to support the
                // onStartCommand(null) case.
                continue;
            }
            si.deliveredTime = SystemClock.uptimeMillis();
            r.deliveredStarts.add(si);
            si.deliveryCount++;
            if (si.neededGrants != null) {
                mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
                        si.getUriPermissionsLocked());
            }
            mAm.grantEphemeralAccessLocked(r.userId, si.intent,
                    r.appInfo.uid, UserHandle.getAppId(si.callingId));
            bumpServiceExecutingLocked(r, execInFg, "start");
            if (!oomAdjusted) {
                oomAdjusted = true;
                mAm.updateOomAdjLocked(r.app, true);
            }
            if (r.fgRequired && !r.fgWaiting) {
                if (!r.isForeground) {
                    if (DEBUG_BACKGROUND_CHECK) {
                        Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r);
                    }
                    scheduleServiceForegroundTransitionTimeoutLocked(r);
                } else {
                    if (DEBUG_BACKGROUND_CHECK) {
                        Slog.i(TAG, "Service already foreground; no new timeout: " + r);
                    }
                    r.fgRequired = false;
                }
            }
            int flags = 0;
            if (si.deliveryCount > 1) {
                flags |= Service.START_FLAG_RETRY;
            }
            if (si.doneExecutingCount > 0) {
                flags |= Service.START_FLAG_REDELIVERY;
            }
            args.add(new ServiceStartArgs(si.taskRemoved, si.id, flags, si.intent));
        }

        ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
        slice.setInlineCountLimit(4);
        Exception caughtException = null;
        try {
            r.app.thread.scheduleServiceArgs(r, slice);
        } catch (TransactionTooLargeException e) {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
                    + " args, first: " + args.get(0).args);
            Slog.w(TAG, "Failed delivering service starts", e);
            caughtException = e;
        } catch (RemoteException e) {
            // Remote process gone...  we'll let the normal cleanup take care of this.
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
            Slog.w(TAG, "Failed delivering service starts", e);
            caughtException = e;
        } catch (Exception e) {
            Slog.w(TAG, "Unexpected exception", e);
            caughtException = e;
        }

        if (caughtException != null) {
            // Keep nesting count correct
            final boolean inDestroying = mDestroyingServices.contains(r);
            for (int i = 0; i < args.size(); i++) {
                serviceDoneExecutingLocked(r, inDestroying, inDestroying);
            }
            if (caughtException instanceof TransactionTooLargeException) {
                throw (TransactionTooLargeException)caughtException;
            }
        }
    }

Line37:42 如果该ServiceRecord的fgRequired为true(即要求启动前台服务), fgWaiting为false(即还没有开始等待startForeground调用),isForeground为false(即还没有成为前台服务),则进一步调用scheduleServiceForegroundTransitionTimeoutLocked,该函数的实现为(ActiveServices.java):

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);
}

该函数将延迟发送消息SERVICE_FOREGROUND_TIMEOUT_MSG, 延迟时间间隔为SERVICE_START_FOREGROUND_TIMEOUT, 定义在类ActiveServices中:

// 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;

该消息超时后将执行serviceForegroundTimeout,其实现为(ActiveServices.java):

void serviceForegroundTimeout(ServiceRecord r) {
    ProcessRecord app;
    synchronized (mAm) {
        if (!r.fgRequired || r.destroying) {
            return;
        }

        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "Service foreground-required timeout for " + r);
        }
        app = r.app;
        r.fgWaiting = false;
        stopServiceLocked(r);
    }

    if (app != null) {
        mAm.mAppErrors.appNotResponding(app, null, null, false,
                "Context.startForegroundService() did not then call Service.startForeground()");
    }
}

该函数将终止服务, 并且抛出ANR异常.

如果服务在启动后调用了startForeground, 其将执行函数setServiceForegroundInnerLocked, 其实现为(ActiveServices.java):

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.appInfo.isInstantApp()) {
            final int mode = mAm.mAppOpsService.checkOperation(
                    AppOpsManager.OP_INSTANT_APP_START_FOREGROUND,
                    r.appInfo.uid,
                    r.appInfo.packageName);
            switch (mode) {
                case AppOpsManager.MODE_ALLOWED:
                    break;
                case AppOpsManager.MODE_IGNORED:
                    Slog.w(TAG, "Instant app " + r.appInfo.packageName
                            + " does not have permission to create foreground services"
                            + ", ignoring.");
                    return;
                case AppOpsManager.MODE_ERRORED:
                    throw new SecurityException("Instant app " + r.appInfo.packageName
                            + " does not have permission to create foreground services");
                default:
                    try {
                        if (AppGlobals.getPackageManager().checkPermission(
                                android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
                                r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
                                        != PackageManager.PERMISSION_GRANTED) {
                            throw new SecurityException("Instant app " + r.appInfo.packageName
                                    + " does not have permission to create foreground"
                                    + "services");
                        }
                    } catch (RemoteException e) {
                        throw new SecurityException("Failed to check instant app permission." ,
                                e);
                    }
            }
        }
        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;
            }
        }
    }
}

该函数将ServiceRecord的fgRequired更新为false, fgWaiting更新为false,isForeground更新为true, 并移除延迟消息SERVICE_FOREGROUND_TIMEOUT_MSG.因此只要startForeground在5秒中内被调用, 则延迟的消息就不会被发送,相应的消息处理函数就不会被调用, 也不会抛出ANR.

我们可以推测在调用startForegroundService启动服务时, 其ServiceRecord类的fgRequired字段应该为true, 事实也是如此, 因为以这种方式启动服务时,创建的PendingIntent为(PendingIntent.java):

/**
 * Retrieve a PendingIntent that will start a foreground service, like calling
 * {@link Context#startForegroundService Context.startForegroundService()}.  The start
 * arguments given to the service will come from the extras of the Intent.
 *
 * <p class="note">For security reasons, the {@link android.content.Intent}
 * you supply here should almost always be an <em>explicit intent</em>,
 * that is specify an explicit component to be delivered to through
 * {@link Intent#setClass(android.content.Context, Class) Intent.setClass}</p>
 *
 * @param context The Context in which this PendingIntent should start
 * the service.
 * @param requestCode Private request code for the sender
 * @param intent An Intent describing the service to be started.
 * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
 * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
 * {@link #FLAG_IMMUTABLE} or any of the flags as supported by
 * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
 * of the intent that can be supplied when the actual send happens.
 *
 * @return Returns an existing or new PendingIntent matching the given
 * parameters.  May return null only if {@link #FLAG_NO_CREATE} has been
 * supplied.
 */
public static PendingIntent getForegroundService(Context context, int requestCode,
        @NonNull Intent intent, @Flags int flags) {
    return buildServicePendingIntent(context, requestCode, intent, flags,
            ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE);
}

其Intent的serviceKind被设置为INTENT_SENDER_FOREGROUND_SERVICE,该intent被send发送后,创建的ServiceRecord类中fgRequired字段被赋值为true, 具体可以参考函数startServiceLocked.

使用startService启动服务后, 再调用startForeground还是可以将服务设置为前台服务,但是这种方式启动服务,就不会设置超时事件, 也不会抛出ANR.

在adb shell下可以通过dumpsys查看某个包下的服务是否为前台服务,在adb shell中输入如下命令:

$ dumpsys activity services -p com.foregroundservice.demo

-p后面是指定的包名, 如果服务是前台服务,其输出结果中会有类似的信息:

isForeground=true foregroundId=1 foregroundNoti=Notification
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值