framework层Service的基础知识和常见问题

简介

framework层Service相关的逻辑主要在ActiveServices类中。

分类

  1. 启动方式
    • start方式启动:onCreate() → onStartCommand() → stopService → onDestroy()
    • bind方式启动:onCreate() → onBind() → unbindService → onUnbind() → onDestroy()
  2. 运行方式(优先级)
    • 前台Service:会在通知栏显示通知,优先级较高;anr超时时间为20s
    • 后台Service:没有通知,优先级较低;anr超时时间为200s

常见的adb命令

  1. 启动指定Service
    • adb shell am startservice -n serviceName
  2. 查看Service的dump信息
    • adb shell dumpsys activity services

Service启动失败原因

一般都有如下log打印:

Slog.w(TAG, “Background start not allowed: service “+ service(启动的Intent) + " to " + r.shortInstanceName(Service名字 )+ " from pid=” + callingPid(调用方的pid) + " uid=” + callingUid(调用方的uid)+ " pkg=" + callingPackage(调用方的包名) + " startFg?=" + fgRequired(是否是前台Service));

具体的逻辑在ActiveServices的startServiceLocked方法里

启动后台Service
	// startRequested表示是否start方式启动(此处首次启动暂未赋值所以为false,且service stop后会赋值为false),fgRequired表示是否前台Service
   if (forcedStandby || (!r.startRequested && !fgRequired)) {
        final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName, r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            Slog.w(TAG, "Background start not allowed: service " + service + " to " + r.shortInstanceName + " from pid=" + callingPid + " uid=" + callingUid  + " pkg=" + callingPackage + " startFg?=" + fgRequired);
			......
	}
后台启动Service && 不在电池优化白名单

如果当前app的uid不是active(处于后台),即使启动的是前台Service,也可能会失败,例如:

ActivityManager: Background start not allowed: service Intent { act=geofence_trigered cmp=com.xiaomi.smarthome/.scene.activity.GeoActionService (has extras) } to com.xiaomi.smarthome/.scene.activity.GeoActionService from pid=9233 uid=10270 pkg=com.xiaomi.smarthome startFg?=true

原理代码如下,下面的就是上面1中列出的代码:

    final boolean bgLaunch = !mAm.isUidActiveLocked(r.appInfo.uid);
    boolean forcedStandby = false;
    if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
        forcedStandby = true;
     }

另外,如果进程的procState < 8 ,他在后台保留1min后就会进入idle状态,相应的Service也会被stop

Service发生ANR的原因

10s内未调用startForeground导致的ANR

I am_anr : [0,16206,com.android.mms,952745573,Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{3201640 u0 com.android.mms/com.xiaomi.mms.transaction.MxActivateService}]

以startForegroundService方式启动的Service,则需要在10s内调用startForeground方法,否则会发生ANR。一般会将该方法放到Service的onStartCommand中去执行,因为这种类型的ANR超时时间是从framework层开始通知app端执行Service的onStartCommand开始计算的。如果将该方法放到Service的onCreate中去执行,onStartCommand方法返回值是START_NOT_STICKY,进程被杀后Service重启不会回调onCreate方法,但是还会走ANR超时逻辑。

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) throws TransactionTooLargeException {
    .......
    while (r.pendingStarts.size() > 0) {
      .....
        if (r.fgRequired && !r.fgWaiting) {
            if (!r.isForeground) {
                scheduleServiceForegroundTransitionTimeoutLocked(r);
            } 
        }

前台Service

设置前台Service

如果需要提高进程的优先级,避免由于低内存或其他厂商定制策略被系统查杀,可以设置Service为前台Service,app端通过调用startForeground方法实现。具体流程如下图1:
在这里插入图片描述

app端Service中调用startForeground
  • 传入Notication的id和Notification的实例,前台Service的类型
  • 如果是用startForegroundService方式启动的,则需要在service的onStartCommand方法中调用该startForeground方法,避免anr
//Service.java
public final void startForeground(int id, @NonNull Notification notification, @ForegroundServiceType int foregroundServiceType) {
    try {
        mActivityManager.setServiceForeground(new ComponentName(this, mClassName), mToken, id,
                notification, 0, foregroundServiceType);
    } catch (RemoteException ex) {
    }
}
AMS中作中转,调用ActiveServices.java中的方法(因为app端进程只有AMS或ATMS的binder对象)
  • 该操作持有AMS大锁
  • 真正实现地方在ActiveServices中
// AMS.java
@Override
public void setServiceForeground(ComponentName className, IBinder token,
        int id, Notification notification, int flags, int foregroundServiceType) {
    synchronized(this) {
        mServices.setServiceForegroundLocked(className, token, id, notification, flags, foregroundServiceType);
    }
}
具体实现逻辑

下面可能会省略部分代码

public void setServiceForegroundLocked(ComponentName className, IBinder token,
        int id, Notification notification, int flags, int foregroundServiceType) {
    final int userId = UserHandle.getCallingUserId();
	// 清除calling uid,避免uid权限校验通不过,清除后callingUid就时system server的uid 1000
    final long origId = Binder.clearCallingIdentity();
    try {
		// 通过component ,token ,userId获取到service record
        ServiceRecord r = findServiceLocked(className, token, userId);
        if (r != null) {
            setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType);
        }
    } finally {
		// 恢复之前的calling uid
        Binder.restoreCallingIdentity(origId);
    }
}
  • 传入的notification id为0表示当前service退出前台状态
  • P或P之后前台service需要申请FOREGROUND_SERVICE权限
  • 移除前台service的10s超时消息
  • 更新isForeground为true
  • 通知异步执行入队列
  /**
     * @param id Notification ID.  Zero === exit foreground state for the given service.
     */
    private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
            Notification notification, int flags, int foregroundServiceType) {
        if (id != 0) {
            if (notification == null) {
                throw new IllegalArgumentException("null notification");
            }
            // Instant apps need permission to create foreground services.
            if (r.appInfo.isInstantApp()) {
            .......
            } else {
				// P或P之后前台service需要申请FOREGROUND_SERVICE权限
                if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
                    mAm.enforcePermission(
                            android.Manifest.permission.FOREGROUND_SERVICE,
                            r.app.pid, r.appInfo.uid, "startForeground");
                }
				// 一般传入的service type默认为FOREGROUND_SERVICE_TYPE_MANIFEST,而manifestType默认为0(none)
                int manifestType = r.serviceInfo.getForegroundServiceType();
                if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_MANIFEST) {
                    foregroundServiceType = manifestType;
                }
             	// 这种异常一般较少
                if ((foregroundServiceType & manifestType) != foregroundServiceType) {
                    throw new IllegalArgumentException("foregroundServiceType "
                        + String.format("0x%08X", foregroundServiceType)
                        + " is not a subset of foregroundServiceType attribute "
                        +  String.format("0x%08X", manifestType)
                        + " in service element of manifest file");
                }
                // 后台启动的前台Service不得拥有“使用中权限”,下面有讲到
                if (!r.mAllowWhileInUsePermissionInFgs) {
                    Slog.w(TAG,"Foreground service started from background can not have "
                                    + "location/camera/microphone access: service "
                                    + r.shortInstanceName);
                }
            }
            boolean alreadyStartedOp = false;
            boolean stopProcStatsOp = false;
			//移除前台service 10s的超时消息。重置fgRequired 和 fgWaiting
            if (r.fgRequired) { 
                r.fgRequired = false;
                r.fgWaiting = false;
                alreadyStartedOp = stopProcStatsOp = true;
                mAm.mHandler.removeMessages(
                        ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
            }

            try {
                boolean ignoreForeground = false;
                ......

                // service的app不在电池优化白名单内&不是前台app启动,ignoreForeground赋值为true
                if (!ignoreForeground
                        && !appIsTopLocked(r.appInfo.uid)
                        && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
                    Slog.w(TAG,
                            "Service.startForeground() not allowed due to bg restriction: service "
                            + r.shortInstanceName);
                    updateServiceForegroundLocked(r.app, false);
                    ignoreForeground = true;
                }

                if (!ignoreForeground) {
					// 取消可能存在的notification
                    if (r.foregroundId != id) {
                        cancelForegroundNotificationLocked(r);
                        r.foregroundId = id;
                    }
					// 给service的notification相关变量赋值
                    notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
                    r.foregroundNoti = notification;
                    r.foregroundServiceType = foregroundServiceType;
					// 前台service第一次设置会为false
                    if (!r.isForeground) {
                        final ServiceMap smap = getServiceMapLocked(r.userId);
                        if (smap != null) {
							// 统计每个app前台service的数量
                            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 && r.app.uidRecord != null) {
                                    active.mAppOnTop = active.mShownWhileTop =
                                            r.app.uidRecord.getCurProcState()
                                                    <= ActivityManager.PROCESS_STATE_TOP;
                                }
                                active.mStartTime = active.mStartVisibleTime
                                        = SystemClock.elapsedRealtime();
                                smap.mActiveForegroundApps.put(r.packageName, active);
                                requestUpdateActiveForegroundAppsLocked(smap, 0);
                            }
                            active.mNumActive++;
                        }
						// 设置isForeground为true,第二次执行该方法后不必执行这些代码,且表示当前service在前台
                        r.isForeground = true;
                       ........
                    }
					// 每次执行该方法都会重新post通知,异步执行
                    r.postNotification();
                    if (r.app != null) {
						// 更新进程优先级
                        updateServiceForegroundLocked(r.app, true);
                    }
                    getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
                    mAm.notifyPackageUse(r.serviceInfo.packageName,
                            PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
                } else {
                  
                }
            } finally {
              .......
			}
        } else {
		// notification id为0 ,是取消前台service的流程,下面讲
    }
发送通知
  • 通知入队等操作异步进行,避免死锁
  • 通知没有对应的icon,打印警告信息,并给设置一个简陋的通知模板,告知用户当前正在运行该进程
    V/ActivityManager: Attempted to start a foreground service (com.mfashiongallery.emag/.lks.minusonescreen.overlay.OverlayService) with a broken notification (no icon: Notification(channel=gallery_overlay_windows shortcut=null contentView=null vibrate=null sound=null defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE))
// ServiceRecord.java
    public void postNotification() {
        final int appUid = appInfo.uid;
        final int appPid = app.pid;
        if (foregroundId != 0 && foregroundNoti != null) {
            // Do asynchronous communication with notification manager to
            // avoid deadlocks.
            final String localPackageName = packageName;
            final int localForegroundId = foregroundId;
            final Notification _foregroundNoti = foregroundNoti;
            final ServiceRecord record = this;
            ams.mHandler.post(new Runnable() {
                public void run() {
                    NotificationManagerInternal nm = LocalServices.getService(NotificationManagerInternal.class);
                    if (nm == null) {
                        return;
                    }
                    Notification localForegroundNoti = _foregroundNoti;
                    try {
                        if (localForegroundNoti.getSmallIcon() == null) {
                            // 通知没有对应icon,打印警告信息
                            Slog.v(TAG, "Attempted to start a foreground service (" + shortInstanceName + ") with a broken notification (no icon: " + localForegroundNoti + ")");

                            CharSequence appName = appInfo.loadLabel(ams.mContext.getPackageManager());
                            if (appName == null) {
                                appName = appInfo.packageName;
                            }
                            Context ctx = null;
                            try {
                                ctx = ams.mContext.createPackageContextAsUser(
                                        appInfo.packageName, 0, new UserHandle(userId));

								// 重新构建通知相关信息
                                Notification.Builder notiBuilder = new Notification.Builder(ctx,
                                        localForegroundNoti.getChannelId());

                                // 设置icon为app应用信息的icon
                                notiBuilder.setSmallIcon(appInfo.icon);

                                // mark as foreground
                                notiBuilder.setFlag(Notification.FLAG_FOREGROUND_SERVICE, true);

                               // 构建pendingIntent
                                runningIntent.setData(Uri.fromParts("package", appInfo.packageName, null));
                                PendingIntent pi = PendingIntent.getActivityAsUser(ams.mContext, 0,
                                        runningIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
                                        UserHandle.of(userId));
								// 设置系统的通知颜色
                                notiBuilder.setColor(ams.mContext.getColor(
                                        com.android.internal
                                                .R.color.system_notification_accent_color));
								// 设置通知标题为“xxx 正在运行”
                                notiBuilder.setContentTitle(
                                        ams.mContext.getString(
                                                com.android.internal.R.string
                                                        .app_running_notification_title,
                                                appName));
								// 设置通知的内容"点按即可了解详情或停止应用",miui定制国内版本为"可能导致系统卡顿,降低待机时间,点按关闭"
                                // MIUI MOD START:
                                // notiBuilder.setContentText(
                                //         ams.mContext.getString(
                                //                 com.android.internal.R.string
                                //                         .app_running_notification_text,
                                //                 appName));
                                if (miui.os.Build.IS_INTERNATIONAL_BUILD) {
                                    notiBuilder.setContentText(
                                            ams.mContext.getString(
                                                    com.android.internal.R.string
                                                            .app_running_notification_text,
                                                    appName));
                                } else {
                                    notiBuilder.setContentText(
                                            ams.mContext.getString(
                                                    com.android.internal.R.string
                                                            .app_running_notification_tip_text,
                                                    appName));
                                }
                                // END
                                notiBuilder.setContentIntent(pi);

                                localForegroundNoti = notiBuilder.build();
                            } catch (PackageManager.NameNotFoundException e) {
                            }
                        }
                        if (nm.getNotificationChannel(localPackageName, appUid,
                                localForegroundNoti.getChannelId()) == null) {
                            int targetSdkVersion = Build.VERSION_CODES.O_MR1;
                            try {
                                final ApplicationInfo applicationInfo =
                                        ams.mContext.getPackageManager().getApplicationInfoAsUser(
                                                appInfo.packageName, 0, userId);
                                targetSdkVersion = applicationInfo.targetSdkVersion;
                            } catch (PackageManager.NameNotFoundException e) {
                            }
                            if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
                                throw new RuntimeException(
                                        "invalid channel for service notification: "
                                                + foregroundNoti);
                            }
                        }
                        if (localForegroundNoti.getSmallIcon() == null) {
                            // Notifications whose icon is 0 are defined to not show
                            // a notification, silently ignoring it.  We don't want to
                            // just ignore it, we want to prevent the service from
                            // being foreground.
                            throw new RuntimeException("invalid service notification: "
                                    + foregroundNoti);
                        }
						// 通知入队
                        nm.enqueueNotification(localPackageName, localPackageName,
                                appUid, appPid, null, localForegroundId, localForegroundNoti,
                                userId);

                        foregroundNoti = localForegroundNoti; // save it for amending next time
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Error showing notification for service", e);
                        // 出现问题会stop server,阿里巴巴app就经常因为这个原因被杀
                        ams.mServices.killMisbehavingService(record,
                                appUid, appPid, localPackageName);
                    }
                }
            });
        }
    }

取消前台service

app端调用

取消前台service的流程和设置前台service的流程基本相同,传入的参数不同,如图2
在这里插入图片描述

  • 传入的notification id为0
  • notifications为null
  • flags为1表示移除通知,0表示不移除
   public final void stopForeground(@StopForegroundFlags int flags) {
        try {
            mActivityManager.setServiceForeground(
                    new ComponentName(this, mClassName), mToken, 0, null,
                    flags, 0);
        } catch (RemoteException ex) {
        }
    }
ActiveServices中的流程

setServiceForegroundInnerLocked方法中notification id为0的else逻辑

  • 自减mActiveForegroundApps中对应app的mNumActive,代表当前app的前台service -1
  • 如果app的mNumActive <= 0,则将该app从mActiveForegroundApps中移除,并更新adj
  • 传入的flags为1,则需要取消notification
	// 取消前台service,如果当前service是前台service
            if (r.isForeground) {
                final ServiceMap smap = getServiceMapLocked(r.userId);
                if (smap != null) {
					// 更新mActiveForegroundApps中对应app的mNumActive
                    decActiveForegroundAppLocked(smap, r);
                }
				// 重置isForeground属性
                r.isForeground = false; 
               .......
                if (r.app != null) {
                    mAm.updateLruProcessLocked(r.app, false, null);
                    updateServiceForegroundLocked(r.app, true);
                }
            }
			// 如果传入的flags为1,则要取消通知
            if ((flags & Service.STOP_FOREGROUND_REMOVE) != 0) {
                cancelForegroundNotificationLocked(r);
                r.foregroundId = 0;
                r.foregroundNoti = null;
            } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                r.stripForegroundServiceFlagFromNotification();
                if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
                    r.foregroundId = 0;
                    r.foregroundNoti = null;
                }
            }
        }
移除通知
    private void cancelForegroundNotificationLocked(ServiceRecord r) {
		// notification id不为0
        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.mServicesByInstanceName.size() - 1; i >= 0; i--) {
                    ServiceRecord other = sm.mServicesByInstanceName.valueAt(i);
                    if (other != r && other.foregroundId == r.foregroundId
                            && other.packageName.equals(r.packageName)) {
                        // Found one!  Abort the cancel.
                        return;
                    }
                }
            }
            r.cancelNotification();
        }
    }

    public void cancelNotification() {
        // 异步执行,避免死锁
        final String localPackageName = packageName;
        final int localForegroundId = foregroundId;
        final int appUid = appInfo.uid;
        final int appPid = app != null ? app.pid : 0;
        ams.mHandler.post(new Runnable() {
            public void run() {
                NotificationManagerInternal nm = LocalServices.getService(
                        NotificationManagerInternal.class);
                if (nm == null) {
                    return;
                }
                try {
                    nm.cancelNotification(localPackageName, localPackageName, appUid, appPid,
                            null, localForegroundId, userId);
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Error canceling notification for service", e);
                }
            }
        });
    }

后台启动的前台Service不得拥有使用中权限

android会限制从后台启动的前台service拥有“使用中”权限,包括位置、摄像头、麦克风等,否则会打印出如下的警告信息。
W ActivityManager: Foreground service started from background can not have location/camera/microphone access: service com.mi.health/.scenerecognition.SceneForegroundService

start或bind方式启动都会去给mAllowWhileInUsePermissionInFgs赋值

        if (!r.mAllowWhileInUsePermissionInFgs) {
            r.mAllowWhileInUsePermissionInFgs =
                    shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid,
                            callingUid, service, r, allowBackgroundActivityStarts);
        }

豁免情况

  • FGS由ROOT_UID,SYSTEM_UID,NFC_UID、SHELL_UID等启动。
  • CallingUid 声明START_ACTIVITIES_FROM_BACKGROUND权限(hide权限)
  • FGS由IME或其他可见应用启动。
  • 在while-in-use白名单内:AttentionService, SystemCaptionService(for live caption)
    private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage,
            int callingPid, int callingUid, Intent intent, ServiceRecord r,
            boolean allowBackgroundActivityStarts) {
        // 一般后台启动前台service限制打开的
        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
            return true;
        }
        // 一般为false
        if (allowBackgroundActivityStarts) {
            return true;
        }
		//部分uid豁免
        boolean isCallerSystem = false;
        final int callingAppId = UserHandle.getAppId(callingUid);
        switch (callingAppId) {
            case ROOT_UID:
            case SYSTEM_UID:
            case NFC_UID:
            case SHELL_UID:
                isCallerSystem = true;
                break;
            default:
                isCallerSystem = false;
                break;
        }

        if (isCallerSystem) {
            return true;
        }

        if (r.app != null) {
            ActiveInstrumentation instr = r.app.getActiveInstrumentation();
            if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
                return true;
            }
        }

        final boolean hasAllowBackgroundActivityStartsToken = r.app != null
                ? !r.app.mAllowBackgroundActivityStartsTokens.isEmpty() : false;
        if (hasAllowBackgroundActivityStartsToken) {
            return true;
        }
		// app声明了START_ACTIVITIES_FROM_BACKGROUND权限豁免
        if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                == PERMISSION_GRANTED) {
            return true;
        }

        // app在top自然允许
        final boolean isCallingUidTopApp = appIsTopLocked(callingUid);
        if (isCallingUidTopApp) {
            return true;
        }
        // Does the calling UID have any visible activity?
        final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
        if (isCallingUidVisible) {
            return true;
        }

        final boolean isWhiteListedPackage =
                mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage);
        if (isWhiteListedPackage) {
            return true;
        }

        // Is the calling UID a device owner app?
        final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);
        if (isDeviceOwner) {
            return true;
        }
        return false;
    }

后台Service

后台Service管控

android对后台进程管控比较严格,app退到后台超过1min就会进入idle状态,并尝试stop相关的Service,所以我们可能会经常见到如下log:

08-27 20:08:12.038 1588 5729 I am_uid_active: 10135

08-27 20:09:13.517 1588 2114 I am_uid_idle: 10135

08-27 20:09:13.517 1588 2114 I am_stop_idle_service: [10135,com.android.htmlviewer/com.android.settings.services.MemoryOptimizationService]

为了便于理解,以下代码都只是节选方法中与之相关的一部分代码

在OomAdjuster.java的updateUidsLocked方法中发送IDLE_UIDS_MSG消息
  • isProcStateBackground 是判断当前UidRecord的procState值是否小于8(PROCESS_STATE_TRANSIENT_BACKGROUND)
  • 后台Service的procState一般是10(PROCESS_STATE_SERVICE)
  • 如果当前进程跟top进程有绑定(如处于top状态的进程访问当前进程的provider等),那他的procState为3(PROCESS_STATE_BOUND_TOP)
  • 如果当前UidRect处于后台且不在临时白名单内,但是上次不处于后台或者在临时白名单内,就会在1min(BACKGROUND_SETTLE_TIME)后将当前的UidRect idle
private void updateUidsLocked(ActiveUids activeUids, final long nowElapsed) {
        for (int i = activeUids.size() - 1; i >= 0; i--) {
            final UidRecord uidRec = activeUids.valueAt(i);
            int uidChange = UidRecord.CHANGE_PROCSTATE;
				// 判断当前procState是否处于后台或者不在临时白名单内 
                if (ActivityManager.isProcStateBackground(uidRec.getCurProcState()) && !uidRec.curWhitelist) {
					// 上次procState不处于后台或者在临时白名单内
                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState) || uidRec.setWhitelist) {
						// 这里传入的elapsedRealtime而不是uptimeMillis,谷歌代码曾出过问。
                        uidRec.lastBackgroundTime = nowElapsed;
                        if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
                            // 发送uid idle消息,处于后台时间BACKGROUND_SETTLE_TIME是1min
                            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,mConstants.BACKGROUND_SETTLE_TIME);
                        }
                    }

在AMS的MainHandler处理该消息并调用idleUids去设置对应的uid idle
  • idle处理逻辑是在system server的main线程中进行的
  • idle时间是在处于后台或不在临时白名单的1min后
  • 遍历active uid时,时间到了就执行doStopUidLocked
  • 时间未到计算更新为最近的一次idle时间并再次发送IDLE_UIDS_MSG消息
final class MainHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            ......
            case IDLE_UIDS_MSG: {
                idleUids();
            } break;

   @GuardedBy("mService")
    void idleUidsLocked() {
        final int N = mActiveUids.size();
        if (N <= 0) {
            return;
        }
        final long nowElapsed = SystemClock.elapsedRealtime();
		// 得到起始后台时间点边界,大于这个值此时不会被idle
        final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
        long nextTime = 0;
        for (int i = N - 1; i >= 0; i--) {
            final UidRecord uidRec = mActiveUids.valueAt(i);
            final long bgTime = uidRec.lastBackgroundTime;
			// 上次处于后台时间 > 0表示当前uid处于后台
            if (bgTime > 0 && !uidRec.idle) {
				// 处于后台时间超过1min
                if (bgTime <= maxBgTime) {
					// 打印event log : am_uid_idle
                    EventLogTags.writeAmUidIdle(uidRec.uid);
					// 给idle标志位赋值
                    uidRec.idle = true;
                    uidRec.setIdle = true;
					// stop 后台Service
                    mService.doStopUidLocked(uidRec.uid, uidRec);
                } else {
					// 计算下次idle消息时间点
                    if (nextTime == 0 || nextTime > bgTime) {
                        nextTime = bgTime;
                    }
                }
            }
        } 
		// 发送下次idle消息
        if (nextTime > 0) {
            mService.mHandler.removeMessages(IDLE_UIDS_MSG);
            mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
                    nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
        }
    }  

stop后台service的逻辑
  • AMS的doStopUidLocked方法会先stop 后台service,再去更新uid并分发uid的change
  • stopInBackgroundLocked回调后,service不一定会真被stop
    //AMS.java
    @GuardedBy("this")
    final void doStopUidLocked(int uid, final UidRecord uidRec) {
        mServices.stopInBackgroundLocked(uid);
        enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
    }

	// ActiveServices.java
    void stopInBackgroundLocked(int uid) {
		// 获取该uid下的所有service
        ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
        ArrayList<ServiceRecord> stopping = null;
        if (services != null) {
			// 遍历所获取到的services
            for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
                ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
				// start方式启动的service
                if (service.appInfo.uid == uid && service.startRequested) {
					// 获取当前service的start 权限,miui会多传入callingPackage,对诸如小米推送拉起的service放开权限
                    if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
                            service.appInfo.targetSdkVersion, -1, false, false, false)
                            != ActivityManager.APP_START_MODE_NORMAL) {
                        if (stopping == null) {
                            stopping = new ArrayList<>();
                        }
                        String compName = service.shortInstanceName;
						// 打印event log am_stop_idle_service
                        EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
						// 打印idle相关的system log
                        StringBuilder sb = new StringBuilder(64);
                        sb.append("Stopping service due to app idle: ");
                        UserHandle.formatUid(sb, service.appInfo.uid);
                        sb.append(" ");
                        TimeUtils.formatDuration(service.createRealTime
                                - SystemClock.elapsedRealtime(), sb);
                        sb.append(" ");
                        sb.append(compName);
                        Slog.w(TAG, sb.toString());
						// 將当前service添加进stoppping列表
                        stopping.add(service);

                        // 如果当前app不在电池优化白名单中,那么前台service也是有可能不允许启动的,所以要取消notification
                        if (appRestrictedAnyInBackground(service.appInfo.uid, service.packageName)) {
                            cancelForegroundNotificationLocked(service);
                        }
                    }
                }
            }
            if (stopping != null) {
                for (int i=stopping.size()-1; i>=0; i--) {
                    ServiceRecord service = stopping.get(i);
                    service.delayed = false;
                    services.ensureNotStartingBackgroundLocked(service);
					// stop service
                    stopServiceLocked(service);
                }
            }
        }
    }

    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
            boolean hasConn) {

        if (isServiceNeededLocked(r, knowConn, hasConn)) {
            return;
        }
        // 如果有新拉起service的需求,本次不会stop该service
        if (mPendingServices.contains(r)) {
            return;
        }
        bringDownServiceLocked(r);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值