一. 了解服务(Service)的概念
service是安卓开发中一个很重要组件,意为“服务”。与我们常见的activity不同,“服务”是默默的在背后进行工作的,通常,它用于在后台为我们执行一些耗时,或者需要长时间执行的一些操作的。通常的,我们使用 Intent来启动一个服务(需要在manifest文件中注册,也可以像注册activity一样,给它分配进程)。Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。
- Service生命周期:
public class MyService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return startCommandReturnId;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
二. 问题背景
产品需要在我们的业务启动之后,在状态栏展示一个“xx应用正在进行中”中的一个通知,同时,要求app退后台后也不能被结束,即需要保活。于是,我们将目光投向了service。
....// 业务启动流程结束
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (guildInfo != null)) {
getApp().applicationContext.startForegroundService(serviceIntent)
} else {
getApp().applicationContext.startService(serviceIntent)
}
// Service类中
public int onStartCommand(Intent intent, int flags, int startId) {
....
mAudioNotification = createAudioNotification(); //创建通知栏展示内容
startForeground(NID, mAudioNotification); // 展示服务通知
....
}
于是我们的业务中出现了这样的代码,乍看之下 好像也没什么问题。可是外发之后,收到的crash反馈却只增不减。
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord
这类堆栈引起了我们的注意。原来我们使用了新提供的 startForegroundService,而这个API比较特殊,它要求我们在调用之后,收到 onStartCommand 回调后 5s内必须调用 startForeground, 否则会有ANR,而如果在调用 startForeground 之前,调用了 stopService 或者 stopSelf ,则会直接抛出 crash 我们这里问题的原因就是,在startForeground之前,调用了 stopService。问题找到了,是业务自己调用导致的。
但,似乎这个API没有提供给我们一个可以合适取消service的时机呢。既然这个API 限制这么多,我们又为什么要选择它呢?它和之前常用的startService又有什么区别呢?
三. 源代码分析
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
@Override
public ComponentName startForegroundService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, true, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user)
这两个API最终的都是调用的 startServiceCommon,区别只在于 其中的参数 requireForeground 字段赋值不同。那么这个字段究竟做了哪些处理呢?
在Android8.0的行为变更说明中,我们看到,不在允许随意创建 后台服务,所以改为 调用 startForegroundService的形式。
- 不满足条件(如:O以上版本后台启动服务)调用startService会抛异常
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
getOpPackageName(), getAttributionTag(), user.getIdentifier());
if (cn != null) {
// 异常匹配
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
throw ServiceStartNotAllowedException.newInstance(requireForeground,
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
....
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (forcedStandby || (!r.startRequested && !fgRequired)) {
// 调用startService,(!r.startRequested && !fgRequired) 条件为true
final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
return null;
}
if (forcedStandby) {
if (fgRequired) {
return null;
}
}
UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
// Unified app-op and target sdk check
@GuardedBy(anyOf = {"this", "mProcLock"})
int appRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
// 安卓8限制
if (packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
.....
}
2. 提高服务优先级
我们知道,在安卓系统内存紧张时,前台应用是有高优先级的,不会被清理掉。所以,我们需要将“不可见”的服务 升级为前台服务,前台服务是被认为用于已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。
所以 我们在启动service后,收到onStartCommand时,调用 startForeground,同时传入需要展示在前台的notification
3. 为什么调用startForgroundService后,再调用stop或者没有及时调用startForeground会crash/ANR呢?
startServiceCommon
-> AMS.startService
-> ActiveServices.startServiceLocked
-> startServiceInnerLocked
-> bringUpServiceLocked
->realStartServiceLocked
-> sendServiceArgsLocked
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) throws TransactionTooLargeException {
...
ArrayList<ServiceStartArgs> args = new ArrayList<>();
while (r.pendingStarts.size() > 0) {
ServiceRecord.StartItem si = r.pendingStarts.remove(0);
...
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
<!--监听是否5S内startForeground-->
scheduleServiceForegroundTransitionTimeoutLocked(r);
} ...
try {
r.app.thread.scheduleServiceArgs(r, slice);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
msg.obj = r;
r.fgWaiting = true;
mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);
}
/**
* How long the Context.startForegroundService() grace period is to get around to
* calling Service.startForeground() before we generate ANR.
*/
volatile int mServiceStartForegroundTimeoutMs = DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS;
修改 r.fgWaiting = true
启动任务延迟TimeoutMs后发送 SERVICE_FOREGROUND_TIMEOUT_MSG
// handler处理 SERVICE_FOREGROUND_TIMEOUT_MSG
void serviceForegroundTimeout(ServiceRecord r) {
ProcessRecord app;
synchronized (mAm) {
if (!r.fgRequired || !r.fgWaiting || r.destroying) {
return;
}
app = r.app;
if (app != null && app.isDebugging()) {
// The app's being debugged; let it ride
return;
}
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Service foreground-required timeout for " + r);
}
r.fgWaiting = false;
stopServiceLocked(r, false);
}
if (app != null) {
// 就是我们之前遇到的异常
final String annotation = "Context.startForegroundService() did not then call "+ "Service.startForeground(): " + r;
Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);
SomeArgs args = SomeArgs.obtain();
args.arg1 = app;
args.arg2 = annotation;
msg.obj = args;
mAm.mHandler.sendMessageDelayed(msg,
mAm.mConstants.mServiceStartForegroundAnrDelayMs);
}
}
如果在没有调用startForegroun前调用了stop,则会抛出 SERVICE_FOREGROUND_CRASH_MSG 的msg
private final void bringDownServiceLocked(ServiceRecord r) {
...
if (r.fgRequired) {
r.fgRequired = false;
r.fgWaiting = false;
mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
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;
msg.getData().putCharSequence(
ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
mAm.mHandler.sendMessage(msg);
}
}
解决方案 - startForegroundService后 必须按照要求调用 startForground 😊
四. 总结
笔者的业务场景下 其实不需要用这么严格的API,正常的在 应用处于前台时,启动前台服务 就按照常用的startService -> startForeground 调用即可。如果一定需要使用 startForegroundService 就要注意到以上的几个问题。