AndroidWay

BackgroundProcess-startService的问

2018-08-22  本文已影响267人  抠脚大汗

环境

在androidO上编译的项目,项目会监听锁屏/开屏的广播,调起独立进程的server,运行至此crash报错


image.png

报错关键字

Not allowed to start service Intentapp is in background uid UidRecord

分析

在androidO之前,从未发生过类似报错,初步怀疑是兼容性问题;从报错信息来看,能猜到因为app在一个后台的uid中导致不能开启一个服务;从栈信息来看很明显是startServiceAPI这个接口报的错;

那思路从追查日志报错的位置出发,ContextImpl方法startService

   @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }

warnIfCallingFromSystemProcess();查看

    /**
     * Logs a warning if the system process directly called a method such as
     * {@link #startService(Intent)} instead of {@link #startServiceAsUser(Intent, UserHandle)}.
     * The "AsUser" variants allow us to properly enforce the user's restrictions.
     */
    private void warnIfCallingFromSystemProcess() {
        if (Process.myUid() == Process.SYSTEM_UID) {
            Slog.w(TAG, "Calling a method in the system process without a qualified user: "
                    + Debug.getCallers(5));
        }
    }

这个对应到crash信息中一个warning,主要是由于监听开锁屏广播,被调起的BroadcastReciver属于满足了Process.myUid() == Process.SYSTEM_UID,提示了一个waring.

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("?")) {
                    //在这里看到了crash日志,错误类型也匹配,进入else的前提是cn.getPackageName().equals("?")
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

这个方法在调用的时候,就比androidO之前的版本多了一个boolean requireForeground参数,这是问题点。
return cn;cn是通过Binder方式跨进程获取的ActivityManagerNative.getDefault().startService,直接到ActivityManagerService中查看startService方法

    @Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage, int userId)
            throws TransactionTooLargeException {
        …………
        synchronized(this) {
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res;
            try {
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }

mServices是ActiveServices的实例,查看startServiceLocked方法

    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
         …………
        // 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);
                //在这里又看到另外一段日志,并且给要返回的compoentName赋值了一个?作为packageName了
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }
        …………
    }

看一下进入条件是!r.startRequested && !fgRequired
r是ServiceRecord的实例,startRequested意义是否有明确的组件调用为flase。我们传入的fgRequired为false。

解决

问题的解决思路是把fgRequired改为true,就不会走到异常,返回一个package为"?"的ComponentName的对象,返回了正常的Component组件也不会出现crash的报错了。
AndroidO提供了一个新的接口调起Service

    @Override
    public ComponentName startForegroundService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, true, mUser);
    }

这里调用startServiceCommon直接传true.在后续一系列的Binder调用中,直到分析问题的ActiveServices类startServiceLocked方法,requireForeground是AndroidO新加入的参数。
如下兼容就可以了如果要在低版本编译,需要反射调用startForegroundService

    if (Build.VERSION.SDK_INT >= 26) {//Android8.0
         context.startForegroundService(service);
    } else {
         context.startService(service);
    }
上一篇下一篇

猜你喜欢

热点阅读