美文网首页AndroidWay
BackgroundProcess-startService的问

BackgroundProcess-startService的问

作者: 抠脚大汗 | 来源:发表于2018-08-22 15:17 被阅读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);
        }
    

    相关文章

      网友评论

        本文标题:BackgroundProcess-startService的问

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