赞
踩
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的形式。
- 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。