フロントサービスに関するstartForegroundServiceインタフェースとstartForegroundインタフェース

15360 ワード

AndroidはSDK 26からサービスを開始する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->scheduleServiceForegroundTransitionTimeoutLockedに呼び出されます.この関数の実装は(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 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 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;
            }
        }
    }

Line 37:42サービスレコードのfgRequiredがtrue(フロントサービスの起動が要求される)、fgWaitingがfalse(startForeground呼び出しの待機が開始されていない)、isForegroundがfalse(すなわちフロントサービスになっていない)の場合、SERVICE_FOREGROUND_TIMEOUT_MSGがさらに呼び出され、この関数の実装は(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);
}

この関数は、クラスActiveServicesで定義された、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;

メッセージがタイムアウトすると、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.
 *
 * 

For security reasons, the {@link android.content.Intent} * you supply here should almost always be an explicit intent, * that is specify an explicit component to be delivered to through * {@link Intent#setClass(android.content.Context, Class) Intent.setClass}

* * @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は、sendによって送信されたINTENT_SENDER_FOREGROUND_SERVICEに設定、作成された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