フロントサービスに関するstartForegroundServiceインタフェースとstartForegroundインタフェース
15360 ワード
AndroidはSDK
起動サービスインタフェースstartForegroundServiceが呼び出されると、
Line 37:42サービスレコードのfgRequiredがtrue(フロントサービスの起動が要求される)、fgWaitingがfalse(startForeground呼び出しの待機が開始されていない)、isForegroundがfalse(すなわちフロントサービスになっていない)の場合、
この関数は、クラスActiveServicesで定義された、
メッセージがタイムアウトすると、
この関数は、サービスを終了するANR異常を放出する.
サービスが起動後にstartForegroundを呼び出すと、関数
この関数は、ServiceRecordのfgRequiredをfalseに更新し、fgWaitingをfalseに更新し、isForegroundをtrueに更新し、遅延メッセージSERVICE_を削除します.FOREGROUND_TIMEOUT_MSG.従ってstartForegroundが5秒以内に呼び出す限り、遅延メッセージは送信されず、対応するメッセージ処理関数は呼び出されず、ANRも投げ出す.
startForegroundService起動サービスを呼び出すと、
そのIntentの
adb shellでは、パケットのサービスがフロントサービスであるかどうかを
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