startForegroundService与startService 使用浅析

一. 了解服务(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的形式。

 

  1.  不满足条件(如: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 就要注意到以上的几个问题。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值