美文网首页
Service:Unable to start service

Service:Unable to start service

作者: 码农翻身记 | 来源:发表于2020-01-22 13:44 被阅读0次

崩溃回顾

项目中有不少场景用了Service(其实有些地方根据Google推荐可以改为JobScheduler),其中有个Service是在进入首页的onPostCreate启动,是显式启动,然后收到了少量崩溃日志,主要分为两种:
崩溃一:java.lang.SecurityException: Unable to start service Intent

Caused by: java.lang.SecurityException: Unable to start service Intent { act=start_for_mainpage cmp=xxx/.xxxService (has extras) }: 
Unable to launch app xxx/10098 for service Intent { act=start_for_mainpage cmp=xxx/.xxxService }: 
user 0 is restricted\n\tat android.app.ContextImpl.startServiceCommon(ContextImpl.java:1729)\n\tat
android.app.ContextImpl.startService(ContextImpl.java:1702)\n\tat
android.content.ContextWrapper.startService(ContextWrapper.java:494)\n\tat

崩溃二:java.lang.IllegalStateException: Not allowed to start service Intent

java.lang.IllegalStateException: Not allowed to start service Intent { act=start_for_mainpage cmp=xxx/.xxxService (has extras) }: 
app is in background uid UidRecord{70cfbb u0a159 TPSL idle procs:1 seq(0,0,0)}

崩溃分析
第一个类型崩溃率比第二个高,接近百万分一(启动次数作为分母),都是OPPO手机(系统版本为4.4.4和5.1.1,机型有A31c,A31,A33,A53m,A33m),第二个崩溃都是8.0系统,品牌有小米和华为,分析源码也能看到针对8.0以上系统确实有限制后台启动。但是两个类型崩溃我都重现不了,模拟在后台启动也重现不了。

解决方案
后来修改为启动服务的时机,在首页的onResume启动,并且加了try catch,如果抛异常,再重试。后面也收到这样的崩溃日志了,可能是加了try catch的原因,也没收到功能上有问题的反馈。
另外,对于第一个异常,也有开发者碰到,主要是OPPO手机针对电量优化,息屏后一段时间禁止自启动导致。
bug--service--Caused by java.lang.SecurityException: Unable to start service Intent { }:user 0 is restricted

崩溃源码分析

追踪api26 的startService的源码,可以找到以上两个崩溃对应的逻辑。

  • ContextImpl.startServiceCommon
    查看ContextImpl的startServiceCommon方法:
 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(), 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 new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
}

如果ActivityService返回的ComponentName名!和!!,则抛Not allowed to start service的SecurityException异常。
如果ActivityService返回的ComponentName名为?,则抛Not allowed to start service的IllegalStateException异常。
什么情况会返回名为!、!!和?的ComponentName,继续看ActivityService的startServiceLocked方法:

  • ActivityService.startServiceLocked
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
        int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
        throws TransactionTooLargeException {
       if (caller != null) {
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
                callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
    } else {
        callerFg = true;
    }
    // 从mServiceMap中查询SerivceRecord缓存,如果没有则创建一个
    ServiceLookupResult res =
        retrieveServiceLocked(service, resolvedType, callingPackage,
                callingPid, callingUid, userId, true, callerFg, false);
    if (res == null) {
        return null;
    }
    // 没有权限,返回的Service记录为空,返回名为!的ComponentName
    if (res.record == null) {
        return new ComponentName("!", res.permission != null
                ? res.permission : "private to package");
    }

    ServiceRecord r = res.record;

    if (!mAm.mUserController.exists(r.userId)) {
        Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
        return null;
    }

    // If this isn't a direct-to-foreground start, check our ability to kick off an
    // arbitrary service
    if (!r.startRequested && !fgRequired) {
        // Before going further -- if this app is not allowed to start services in the
        // background, then at this point we aren't going to let it period.
        // 判断是否允许后台启动
        final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                r.appInfo.targetSdkVersion, callingPid, false, false);
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            Slog.w(TAG, "Background start not allowed: service "
                    + service + " to " + r.name.flattenToShortString()
                    + " from pid=" + callingPid + " uid=" + callingUid
                    + " pkg=" + callingPackage);
            if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                // In this case we are silently disabling the app, to disrupt as
                // little as possible existing apps.
                return null;
            }
            // This app knows it is in the new model where this operation is not
            // allowed, so tell it what has happened.
            UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
            // 不允许后台启动,返回名为?的ComponentName
            return new ComponentName("?", "app is in background uid " + uidRec);
        }
    }

    NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
            callingUid, r.packageName, service, service.getFlags(), null, r.userId);

    // If permissions need a review before any of the app components can run,
    // we do not start the service and launch a review activity if the calling app
    // is in the foreground passing it a pending intent to start the service when
    // review is completed.
    if (mAm.mPermissionReviewRequired) {
        if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
                callingUid, service, callerFg, userId)) {
            return null;
        }
    }

    if (unscheduleServiceRestartLocked(r, callingUid, false)) {
        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
    }
    r.lastActivity = SystemClock.uptimeMillis();
    r.startRequested = true;
    r.delayedStop = false;
    r.fgRequired = fgRequired;
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
            service, neededGrants, callingUid));

    final ServiceMap smap = getServiceMapLocked(r.userId);
    boolean addToStarting = false;
    if (!callerFg && !fgRequired && r.app == null
            && mAm.mUserController.hasStartedUserState(r.userId)) {
        ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    return cmp;
}

那什么情况会返回非APP_START_MODE_NORMAL?继续看appServicesRestrictedInBackgroundLocked方法.

  • ActivityService.appServicesRestrictedInBackgroundLocked
 int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Persistent app?
        if (mPackageManagerInt.isPackagePersistent(packageName)) {
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "App " + uid + "/" + packageName
                        + " is persistent; not restricted in background");
            }
            return ActivityManager.APP_START_MODE_NORMAL;
        }
 
        // Non-persistent but background whitelisted?
        if (uidOnBackgroundWhitelist(uid)) {
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "App " + uid + "/" + packageName
                        + " on background whitelist; not restricted in background");
            }
            return ActivityManager.APP_START_MODE_NORMAL;
        }
 
        // Is this app on the battery whitelist?
        if (isOnDeviceIdleWhitelistLocked(uid)) {
            if (DEBUG_BACKGROUND_CHECK) {
                Slog.i(TAG, "App " + uid + "/" + packageName
                        + " on idle whitelist; not restricted in background");
            }
            return ActivityManager.APP_START_MODE_NORMAL;
        }
 
        // None of the service-policy criteria apply, so we apply the common criteria
        return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}

上面逻辑中,分为几道判断,如果返回APP_START_MODE_NORMAL,则表示运行后台启动:

  • 是否Persistent app(系统应用?)
  • 是否在后台运行白名单
  • 是否电量优化的白名单
    这3个条件满足其一都能启动,但是一般应用默认是不满足的,继续后续的判断。
 int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Apps that target O+ are always subject to background check
        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;
        }
        // ...and legacy apps get an AppOp check
        int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
                uid, packageName);
        if (DEBUG_BACKGROUND_CHECK) {
            Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
        }
        switch (appop) {
            case AppOpsManager.MODE_ALLOWED:
                return ActivityManager.APP_START_MODE_NORMAL;
            case AppOpsManager.MODE_IGNORED:
                return ActivityManager.APP_START_MODE_DELAYED;
            default:
                return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}

如果TargestSdk高于Android O,直接返回APP_START_MODE_DELAYED_RIGID,也就是不允许启动后台服务,另外,如果低于TargestSdk 没有到Android O的应用,会通过 AppOpsManager 确认是否可以启动后台服务,而AppOpsManager 中默认是允许的。

  • ActivityService.startServiceInnerLocked
    上面还没分析到什么情况返回名为"!!"的ComponentName,继续看ActivityService方法startServiceLocked最后一个调用startServiceInnerLocked:
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    ServiceState stracker = r.getTracker();
    if (stracker != null) {
        stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
    }
    r.callStart = false;
    synchronized (r.stats.getBatteryStats()) {
        r.stats.startRunningLocked();
    }
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
    if (error != null) {
        return new ComponentName("!!", error);
    }

    if (r.startRequested && addToStarting) {
        boolean first = smap.mStartingBackground.size() == 0;
        smap.mStartingBackground.add(r);
        r.startingBgTimeout = SystemClock.uptimeMillis() + mAm.mConstants.BG_START_TIMEOUT;
        if (DEBUG_DELAYED_SERVICE) {
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r, here);
        } else if (DEBUG_DELAYED_STARTS) {
            Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r);
        }
        if (first) {
            smap.rescheduleDelayedStartsLocked();
        }
    } else if (callerFg || r.fgRequired) {
        smap.ensureNotStartingBackgroundLocked(r);
    }

    return r.name;
}

可以看到如果方法bringUpServiceLocked返回error不为空,则返回名为!!的ComponentName。

  • ActivityService.bringUpServiceLocked
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
        boolean whileRestarting, boolean permissionsReviewRequired)
        throws TransactionTooLargeException {
    //Slog.i(TAG, "Bring up service:");
    //r.dump("  ");
    //Service还没创建前,这里的r.app为null 
    //在后面的realStartServiceLocked方法里面设置的
    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(r, execInFg, false);
        return null;
    }
    // We are now bringing the service up, so no longer in the
    // restarting state.
    if (mRestartingServices.remove(r)) {
        clearRestartingIfNeededLocked(r);
    }
    // Make sure this service is no longer considered delayed, we are starting it now.
    if (r.delayed) {
        if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r);
        getServiceMapLocked(r.userId).mDelayedStartList.remove(r);
        r.delayed = false;
    }
    // Make sure that the user who owns this service is started.  If not,
    // we don't want to allow it to run.
    if (!mAm.mUserController.hasStartedUserState(r.userId)) {
        String msg = "Unable to launch app "
                + r.appInfo.packageName + "/"
                + r.appInfo.uid + " for service "
                + r.intent.getIntent() + ": user " + r.userId + " is stopped";
        Slog.w(TAG, msg);
        bringDownServiceLocked(r);
        return msg;
    }

    // Service is now being launched, its package can't be stopped.
    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                r.packageName, false, r.userId);
    } catch (RemoteException e) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + r.packageName + ": " + e);
    }

    final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
    final String procName = r.processName;
    String hostingType = "service";
    ProcessRecord app;

    if (!isolated) {
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
        if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                    + " app=" + app);
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
          //关键代码,调用了realStartServiceLocked方法
                realStartServiceLocked(r, app, execInFg);
                return null;
            } catch (TransactionTooLargeException e) {
                throw e;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting service " + r.shortName, e);
            }
        }
    } else {
                app = r.isolatedProc;
        if (WebViewZygote.isMultiprocessEnabled()
                && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
            hostingType = "webview_service";
        }
    }
    if (app == null && !permissionsReviewRequired) {
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                hostingType, r.name, false, isolated, false)) == null) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            Slog.w(TAG, msg);
            bringDownServiceLocked(r);
            return msg;
        }
        if (isolated) {
            r.isolatedProc = app;
        }
    }
    if (r.delayedStop) {
        // Oh and hey we've already been asked to stop!
        r.delayedStop = false;
        if (r.startRequested) {
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                    "Applying delayed stop (in bring up): " + r);
            stopServiceLocked(r);
        }
    }

    return null;
}

到这个方法终于看到崩溃日志中出现的Unable to launch app的信息,条件是方法mAm.mUserController.hasStartedUserState(r.userId)返回false,猜测OPPO的崩溃就是在这里返回了false。

!(https://img.haomeiwen.com/i13079595/c8a7f3beb5753709.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

相关文章

网友评论

      本文标题:Service:Unable to start service

      本文链接:https://www.haomeiwen.com/subject/epyuzctx.html