zoukankan      html  css  js  c++  java
  • 转Android service 启动篇之 startForegroundService

    前言:

    在官方文档 Android 8.0 行为变更 中有这样一段话:

    Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。

    在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。

    如果应用在此时间限制内调用 startForeground(),则系统将停止服务并声明此应用为 ANR

    Android service 启动篇之 startService 中对整个start 过程进行了梳理,其中startService 和startForegroundService 最终调用调用的接口时一样的,只是其中要求foreground 启动service。基于上一篇博文,这里对于前台服务进行详细的解析。

    1 startServiceLocked

    流程同Android service 启动篇之 startService ,最终调用接口为ActivieServices 中startServiceLocked:

            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));

    初始化ServiceRecord,其中fgRequired 为true。

    然后将需要start 的service 添加到pendingStarts 中,Android service 启动篇之 startService 中知道最后会在bringUpServiceLocked的函数中进行最终启动。

    对于前台服务 sendServiceArgsLocked() 函数中会拉起一个timeout,时长为 5 秒,也就是说5s 后会抛出ANR的异常。

    详细看下面第 4 点

    从这里我们知道在Context.startForegroundService() 之后必须要调用Service.startForeground,也就是说在foreground 的启动接口调用后的 5 秒内必须要在service 中调用startForeground() 接口来解除timeout。

    2 startFroeground

    来看下是否是这样设计的,来看下startFroeground():

        public final void startForeground(int id, Notification notification) {
            try {
                mActivityManager.setServiceForeground(
                        new ComponentName(this, mClassName), mToken, id,
                        notification, 0);
            } catch (RemoteException ex) {
            }
        }

    在函数的上面有段注释:

         * @param id The identifier for this notification as per
         * {@link NotificationManager#notify(int, Notification)
         * NotificationManager.notify(int, Notification)}; must not be 0.
         * @param notification The Notification to be displayed.
         * 
         * @see #stopForeground(boolean)
         */

    一共 5 个参数,其中id 和notification 是需要通过service 传入的。id 是用于notification notify 使用。

    3 setServiceForegroundInnerLocked

    3.1 取消timeout

    接着来看AMS 中的接口,最终调用的是ActiveServices 中的setServiceForegroundInnerLocked():

                if (r.fgRequired) {
                    if (DEBUG_SERVICE || DEBUG_BACKGROUND_CHECK) {
                        Slog.i(TAG, "Service called startForeground() as required: " + r);
                    }
                    r.fgRequired = false;
                    r.fgWaiting = false;
                    mAm.mHandler.removeMessages(
                            ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
                }

    fgRequired 在这里会被置成false,意味了这个请求已经被安全处理。

    这里看到会取消掉foreground 的timeout,但是,前提条件是:

            if (id != 0) {
                if (notification == null) {
                    throw new IllegalArgumentException("null notification");
                }

    要求startFroeground() 中的id 不能为0,而且notification不能为null。

    注意:

    上面提到sendServiceArgsLocked() 的时候会schedule 一个timeout,时长为5秒,5秒过了之后会出现ANR。那需要注意的是函数sendServiceArgsLocked() 是在onCreate() 之后,并且是在onStartCommand() 之前调用的,这就给了我们取消的空间。虽然说都是异步的操作,但是为了正常流程考虑,一般会将startFroeground() 加到onStartCommand() 中执行。

    3.2 隐藏notification

                if (r.foregroundId != id) {
                    cancelForegroundNotificationLocked(r);
                    r.foregroundId = id;
                }

    code 中在foreground 的id 发生变化的时候,会将原来的notification 隐藏掉。

    那有种可能,有可能两个service 公用一个notification,这个时候不需要将notification cancel。

        private void cancelForegroundNotificationLocked(ServiceRecord r) {
            if (r.foregroundId != 0) {
                // First check to see if this app has any other active foreground services
                // with the same notification ID.  If so, we shouldn't actually cancel it,
                // because that would wipe away the notification that still needs to be shown
                // due the other service.
                ServiceMap sm = getServiceMapLocked(r.userId);
                if (sm != null) {
                    for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
                        ServiceRecord other = sm.mServicesByName.valueAt(i);
                        if (other != r && other.foregroundId == r.foregroundId
                                && other.packageName.equals(r.packageName)) {
                            // Found one!  Abort the cancel.
                            return;
                        }
                    }
                }
                r.cancelNotification();
            }
        }

    3.3 将service 设为前台服务

        if (!r.isForeground) {
            final ServiceMap smap = getServiceMapLocked(r.userId);
            if (smap != null) {
                ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
                if (active == null) {
                    active = new ActiveForegroundApp();
                    active.mPackageName = r.packageName;
                    active.mUid = r.appInfo.uid;
                    active.mShownWhileScreenOn = mScreenOn;
                    if (r.app != null) {
                        active.mAppOnTop = active.mShownWhileTop =
                                r.app.uidRecord.curProcState
                                        <= ActivityManager.PROCESS_STATE_TOP;
                    }
                    active.mStartTime = active.mStartVisibleTime
                            = SystemClock.elapsedRealtime();
                    smap.mActiveForegroundApps.put(r.packageName, active);
                    requestUpdateActiveForegroundAppsLocked(smap, 0);
                }
                active.mNumActive++;
            }
            r.isForeground = true;
        }
    

    4 异常处理

    4.1 ANR

    上面已经说过,如果在 5 秒内没有调用startForeground(),timeout 就会触发,会报出ANR:

        void serviceForegroundTimeout(ServiceRecord r) {
            ProcessRecord app;
            synchronized (mAm) {
                if (!r.fgRequired || r.destroying) {
                    return;
                }
    
                if (DEBUG_BACKGROUND_CHECK) {
                    Slog.i(TAG, "Service foreground-required timeout for " + r);
                }
                app = r.app;
                r.fgWaiting = false;
                stopServiceLocked(r);
            }
    
            if (app != null) {
                mAm.mAppErrors.appNotResponding(app, null, null, false,
                        "Context.startForegroundService() did not then call Service.startForeground()");
            }
        }

    log 如下:

    11-06 02:01:59.616  3877  3893 E ActivityManager: ANR in com.shift.phonemanager.permission.accesslog
    11-06 02:01:59.616  3877  3893 E ActivityManager: PID: 1369
    11-06 02:01:59.616  3877  3893 E ActivityManager: Reason: Context.startForegroundService() did not then call Service.startForeground()
    11-06 02:01:59.616  3877  3893 E ActivityManager: Load: 0.0 / 0.0 / 0.0
    11-06 02:01:59.616  3877  3893 E ActivityManager: CPU usage from 7945ms to 0ms ago (2007-11-06 02:01:51.418 to 2007-11-06 02:01:59.363):
    11-06 02:01:59.616  3877  3893 E ActivityManager:   60% 3877/system_server: 35% user + 25% kernel / faults: 3744 minor 6 major
    11-06 02:01:59.616  3877  3893 E ActivityManager:   25% 1042/com.android.launcher3: 20% user + 4.9% kernel / faults: 11190 minor 9 major
    11-06 02:01:59.616  3877  3893 E ActivityManager:   18% 1149/android.process.media: 13% user + 5.3% kernel / faults: 6130 minor
    11-06 02:01:59.616  3877  3893 E ActivityManager:   15% 1420/adbd: 3.6% user + 11% kernel / faults: 5074 minor
    11-06 02:01:59.616  3877  3893 E ActivityManager:   9.7% 255/logd: 2.7% user + 6.9% kernel / faults: 5 minor
    11-06 02:01:59.616  3877  3893 E ActivityManager:   9.2% 3814/surfaceflinger: 4.4% user + 4.8% kernel / faults: 658 minor

    4.2 crash

    上面看到如果timeout 触发,会报出ANR,但是code 中也有另外一个地方限制,要求service 一旦startForegroundService() 启动,必须要在service 中startForeground(),如果在这之前stop 或stopSelf,那就会用crash 来代替ANR。

    详细看bringDownServiceLocked()。

            if (r.fgRequired) {
                Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                        + r);
                r.fgRequired = false;
                r.fgWaiting = false;
                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;
                    mAm.mHandler.sendMessage(msg);
                }
            }

    这里的r.fgRequired 必须要处理掉,不然stop 的时候会触发bringDown,然后会将timeout 的remove,换成了crash。

    log 如下:

    --------- beginning of crash
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: FATAL EXCEPTION: main
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: Process: com.shift.phonemanager.permission.accesslog, PID: 3106
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1771)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:106)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:164)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6518)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    11-06 02:06:05.307  3106  3106 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
    11-06 02:06:05.320  3118  3118 D ExtensionsFactory: No custom extensions.

    5 总结

    • 8.0 以后不希望后台应用运行后台服务,除非特殊条件
    • 一旦通过startForegroundService() 启动前台服务,必须在service 中有startForeground() 配套,不然会出现ANR 或者crash
    • startForeground() 中的id 和notification 不能为0 和 null
  • 相关阅读:
    接口测试--apipost中cookie管理器的使用
    python解释器换了路径,导致pip安装失败解决方法
    Jmeter之Bean shell使用(二)
    Jmeter之Bean shell使用(一)
    BeanShell生成随机数
    Jmeter之Json 提取器
    Jmeter全面信息学习笔记
    python模块之codecs
    open()和with open()的区别
    【图像处理】第二次实验:二维快速傅里叶变换与离散余弦变换
  • 原文地址:https://www.cnblogs.com/mwl523/p/14209777.html
Copyright © 2011-2022 走看看