大家好,我是阳哥!在uid、userId和appId之间不得不说的事(一)中,阳哥介绍了uid是如何生成的以及系统进程uid和用户名USER之间是如何关联在一起的。本篇文章,阳哥将为大家介绍userId是如何生成的,并详细剖析uid、userId和appId三者之间的关系。

我们知道Linux是一个多用户操作系统,而Android是一个基于Linux内核的操作系统,按道理它也能够支持多用户。事实上,直到Android 4.2 (API 17)版本才有了多用户的概念。

Android 支持在一台Android 设备上添加多个用户,Android的用户类型分为以下三种:

  • 主要用户:添加到设备的第一个用户。除非恢复出厂设置,否则无法移除主要用户;此外,即使其他用户在前台运行,主要用户也会始终处于运行状态。此类用户还拥有只有自己可以设置的特殊权限和设置。
  • 次要用户:除主要用户之外添加到设备的任何用户。次要用户可以移除(由用户自行移除或由主要用户移除),且不会影响设备上的其他用户。此类用户可以在后台运行且可以继续连接到网络
  • 访客用户:临时的次要用户。系统提供了删除访客用户的明确选项,当访客用户不再有用时,可快速将其删除。一次只能有一个访客用户。

所以对于一个Android设备使用者来说,其所能分配到的用户类型主要为次要用户。次要用户可以直接通过主要用户界面(如:设置)进行创建,也可以通过设备管理应用(从Android 5.0开始)进行创建。

无论通过何种方式创建,最终都会使用到UserManagerService(以下简称UMS)的API。接下来,我们就看一看UMS是如何创建用户的。

// 本文使用的源码版本为Android N
// UserManagerService#createUser
public UserInfo createUser(String name, int flags) {
    // 检查应用是否有创建或管理用户的权限
    checkManageOrCreateUsersPermission(flags);
    return createUserInternal(name, flags, UserHandle.USER_NULL);
}
// UserManagerService#createUserInternal
private UserInfo createUserInternal(String name, int flags, int parentId) {
    // 检查是否允许当前用户创建新用户
    if (hasUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.getCallingUserId())) {
        Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
        return null;
    }
    return createUserInternalUnchecked(name, flags, parentId);
}

可以看到UMS在创建新用户时,首先会检查调用应用是否拥有创建或管理用户的权限,接着会检查当前用户是否为受限用户。只有通过这两步的检查,才可以执行后续的新用户创建流程。对于设备管理应用来说,它在创建应用时则是直接调用createUserInternalUnchecked方法。我们来看一看这个方法都做了哪些工作:

// UserManagerService#createUserInternalUnchecked
private UserInfo createUserInternalUnchecked(String name, int flags, int parentId) {
    // 访客用户
    final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
    // 受管理个人资料
    final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
    // 受限个人资料
    final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
    final long ident = Binder.clearCallingIdentity();
    final int userId;
    try {
        synchronized (mPackagesLock) {
            ......
            // 检查是否能创建更多的受管理个人资料
            if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
                Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
                return null;
            }
            // 检查是否能创建新的次要用户
            if (!isGuest && !isManagedProfile && isUserLimitReached()) {
                // If we're not adding a guest user or a managed profile and the limit has
                // been reached, cannot add a user.
                return null;
            }
            // If we're adding a guest and there already exists one, bail.
            if (isGuest && findCurrentGuestUser() != null) {
                return null;
            }
            ......
            // 获取新用户ID
            userId = getNextAvailableId();
            ......
        }
        ......
        // 更新mUserIds数组
        updateUserIds();
        ......
        // 通知系统一个新用户已添加
        Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
        addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
                android.Manifest.permission.MANAGE_USERS);
        ......
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
    return userInfo;
}

createUserInternalUnchecked方法在创建用户的时候会依据用户类型,执行不同的判断。以创建次要用户为例,它会通过isUserLimitReached方法检查当前用户数是否已经达到上限。

// UserManagerService#isUserLimitReached
private boolean isUserLimitReached() {
    int count;
    synchronized (mUsersLock) {
        count = getAliveUsersExcludingGuestsCountLU();
    }
    return count >= UserManager.getMaxSupportedUsers();
}

// UserManager#getMaxSupportedUsers
public static int getMaxSupportedUsers() {
    // Don't allow multiple users on certain builds
    if (android.os.Build.ID.startsWith("JVP")) return 1;
    // Svelte devices don't get multi-user.
    if (ActivityManager.isLowRamDeviceStatic()) return 1;
    return SystemProperties.getInt("fw.max_users",
            Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
}

isUserLimitReached方法首先会去获取已创建的非访客用户的数量,接着检查它是否超过系统能支持的最大用户数。这个最大用户数是由config_multiuserMaximumUsers属性控制的,取值默认为1。所以,事实上Android的多用户功能默认处于停用状态。要启用这项功能,设备制造商必须定义一个资源叠加层,以替换 frameworks/base/core/res/res/values/config.xml 中的以下值:

<!--  Maximum number of supported users -->
<integer name="config_multiuserMaximumUsers">1</integer>
<!--  Whether Multiuser UI should be shown, since LOLLIPOP -->
<bool name="config_enableMultiUserUI">false</bool>

开发者可以通过UserManager提供的supportsMultipleUsers方法来检查当前系统是否支持多用户功能。

// UserManager#supportsMultipleUsers
public static boolean supportsMultipleUsers() {
    return getMaxSupportedUsers() > 1
           && SystemProperties.getBoolean("fw.show_multiuserui",
           Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));
}

可以看到,仅当config_multiuserMaximumUsers 的取值大于 1且config_enableMultiUserUI 的取值为 true时,系统的多用户功能才会开启。

若当前用户数未达到上限,createUserInternalUnchecked方法就会通过getNextAvailableId来获取新用户ID。

// UserManagerService#getNextAvailableId
private int getNextAvailableId() {
    synchronized (mUsersLock) {
        // 10
        int i = MIN_USER_ID;
        // Integer.MAX_VALUE / UserHandle.PER_USER_RANGE
        // = 21474
        while (i < MAX_USER_ID) {
            if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
                return i;
            }
            i++;
        }
    }
    throw new IllegalStateException("No user id available!");
}

从getNextAvailableId的执行过程我们可以知道,userId的取值范围为[10, 21474)。getNextAvailableId从小到大依次遍历该取值范围,若发现某个ID尚未分配,则将其作为新用户的ID返回;否则,抛出异常。

到这里,userId的创建过程就分析完了。那么,userId是如何与用户进程uid关联起来的呢?我们很自然就能联想到进程启动的时候系统是不是做了什么操作。下面就以Activity的启动过程为例来探寻userId和uid的关联时机,这里只关注与userId、uid以及进程启动相关的步骤。


// ActivityStarter#startActivityMayWait
final int startActivityMayWait(IApplicationThread caller, int callingUid,
    String callingPackage, Intent intent, String resolvedType,
    IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
    IBinder resultTo, String resultWho, int requestCode, int startFlags,
    ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
    Bundle bOptions, boolean ignoreTargetSecurity, int userId,
    IActivityContainer iContainer, TaskRecord inTask) {
    // 解析ResolveInfo
    ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
    // Collect information about the target of the Intent.
    ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
    ......
    synchronized (mService) {
        ......
        int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                aInfo, rInfo, voiceSession, voiceInteractor,
                resultTo, resultWho, requestCode, callingPid,
                callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                options, ignoreTargetSecurity, componentSpecified, outRecord, container,
                inTask);
        ......
        return res;
    }
}

startActivityMayWait在启动Activity之前首先会依据Intent、userId等信息来向ActivityStackSupervisor查询其对应的ResolveInfo。而ActivityStackSupervisor的resolveIntent方法则是通过直接调用PackageManagerService#resolveIntent来实现的。

// PackageManagerService#resolveIntent
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
        int flags, int userId) {
    try {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
        ......
        final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
                flags, userId);
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        // 选择最佳匹配的ResolveInfo
        final ResolveInfo bestChoice =
                chooseBestActivity(intent, resolvedType, flags, query, userId);
        ......
        return bestChoice;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

// PackageManagerService#queryIntentActivitiesInternal
private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
        String resolvedType, int flags, int userId) {
    ......
    ComponentName comp = intent.getComponent();
    if (comp == null) {
        if (intent.getSelector() != null) {
            intent = intent.getSelector();
            comp = intent.getComponent();
        }
    }
    if (comp != null) {
        final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
        // 获取目标ActivityInfo
        final ActivityInfo ai = getActivityInfo(comp, flags, userId);
        if (ai != null) {
            final ResolveInfo ri = new ResolveInfo();
            ri.activityInfo = ai;
            list.add(ri);
        }
        return list;
    }
    ......
}

// PackageManagerService#getActivityInfo
public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
    ......
    synchronized (mPackages) {
        PackageParser.Activity a = mActivities.mActivities.get(component);
        ......
        if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
            PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
            if (ps == null) return null;
            return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                    userId);
        }
        ......
    }
    return null;
}

getActivityInfo方法依据ComponetName查询对应的PackageParser.Activity信息,它是在应用安装过程中生成的。接着,再将Activity信息传递给PackageParser#generateActivityInfo方法,并由它生成最终的ActivityInfo,我们看一看这个方法具体做了什么:

// PackageParser#generateActivityInfo
public static final ActivityInfo generateActivityInfo(Activity a, int flags,
        PackageUserState state, int userId) {
    if (a == null) return null;
    ......
    // Make shallow copies so we can store the metadata safely
    ActivityInfo ai = new ActivityInfo(a.info);
    ai.metaData = a.metaData;
    ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
    return ai;
}

// PackageParser#generateApplicationInfo
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
        PackageUserState state, int userId) {
    if (p == null) return null;
    ......
    // Make shallow copy so we can store the metadata/libraries safely
    ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
    // 更新ApplicationInfo#uid
    ai.initForUser(userId);
    ......
    return ai;
}
// ApplicationInfo#initForUser
public void initForUser(int userId) {
    uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
    ......
}

简单来说,generateActivityInfo方法就是执行了一次拷贝操作。同时,调用initForUser方法将当前用户信息userId同步到与目标Activity对应的ApplicationInfo中。所以,对于不同的用户来说,ApplicationInfo#uid的取值也是不同的。

那么,uid、userId和appId三者之间的关系是怎样的呢?UserHandle给出了答案:

// UserHandle#getUid
public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
    // 默认为true
    if (MU_ENABLED) {
        return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
    } else {
        return appId;
    }
}

// UserHandle#getAppId
public static @AppIdInt int getAppId(int uid) {
    return uid % PER_USER_RANGE;
}

// UserHandle#getUserId
public static @UserIdInt int getUserId(int uid) {
    if (MU_ENABLED) {
        return uid / PER_USER_RANGE;
    } else {
        return UserHandle.USER_SYSTEM;
    }
}

正如uid、userId和appId之间不得不说的事(一)中提到的,对于一个用户来说他所能分配的uid取值范围为UserHandle.PER_USER_RANGE (100000)个。

Activity的启动过程也伴随着应用进程的初始化和启动,Android Framework层生成的uid正是在应用进程启动时同步给Linux内核的。

// ActivityStackSupervisor#startSpecificActivityLocked
void startSpecificActivityLocked(ActivityRecord r,
        boolean andResume, boolean checkConfig) {
    // Is this activity's application already running?
    // 查询应用进程是否已启动
    ProcessRecord app = mService.getProcessRecordLocked(r.processName,
            r.info.applicationInfo.uid, true);
    r.task.stack.setLaunchTime(r);
    // 已启动过
    if (app != null && app.thread != null) {
        try {
            ......
            realStartActivityLocked(r, app, andResume, checkConfig);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting activity "
                    + r.intent.getComponent().flattenToShortString(), e);
        }
        // If a dead object exception was thrown -- fall through to
        // restart the application.
    }
    // 应用进程未启动过,先启动应用进程
    mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
            "activity", r.intent.getComponent(), false, false, true);
}

startSpecificActivityLocked在正式启动Activity之前,会先判断Activity所在的应用进程是否存活。若未启动,则先启动进程。注意这里传递给startProcessLocked函数的参数r.info.applicationInfo中已经包含了当前用户的userId。

// ActivityManagerService#startProcessLocked
final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
        boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
        boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
        String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
    ......
    if (app == null) {
        ......
        // 1.创建新的ProcessRecord
        app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
        ......
    } 
    ......
    checkTime(startTime, "startProcess: stepping in to startProcess");
    // 2.启动新进程
    startProcessLocked(
            app, hostingType, hostingNameStr, abiOverride, entryPoint, entryPointArgs);
    checkTime(startTime, "startProcess: done starting proc!");
    return (app.pid != 0) ? app : null;
}
// ActivityManagerService#newProcessRecordLocked
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
        boolean isolated, int isolatedUid) {
    ......
    int uid = info.uid;
    ......
    final ProcessRecord r = new ProcessRecord(stats, info, proc, uid);
    ......
    return r;
}
// ProcessRecord
ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
              String _processName, int _uid) {
    ......
    uid = _uid;
    userId = UserHandle.getUserId(_uid);
    ......
}

因为当前没有与应用进程对应的ProcessRecord,所以startProcessLocked方法首先创建了一个新的ProcessRecord。可以看到在构建ProcessRecord时,它的uid取值正是ApplicationInfo#uid。接下来就是调用startProcessLocked的重载方法启动新进程:

private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    ......
    int uid = app.uid;
    ......
    Process.ProcessStartResult startResult = Process.start(entryPoint,
            app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
            app.info.dataDir, entryPointArgs);
    ......
}

Process#start方法最终通过startViaZygote来执行新进程的启动:

private static ProcessStartResult startViaZygote(final String processClass, final String niceName, final int uid, final int gid,
        final int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo,
        String abi, String instructionSet, String appDataDir, String[] extraArgs) throws ZygoteStartFailedEx {
    synchronized(Process.class) {
        ......
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
        ......
    }
}

通过分析以上应用进程的启动流程,我们不难发现对于一个应用进程来说,也正如uid、userId和appId之间不得不说的事(一)中提到的,它的uid和gid是相等的。startViaZygote方法最终通过socket通信的方式,将进程初始化参数(uid、gid等)传递给Zygote进程,从而完成新进程的创建和启动。

应用进程启动成功以后,我们就可以通过ps命令来查看进程的相关信息了。那么,对于应用进程来说,它的用户名(比如:u0_a106)和uid之间又是如何转换的呢?Android将它们之间的映射方式定义在/bionic/libc/bionic/stubs.cpp中。

static void print_app_name_from_uid(const uid_t uid, char* buffer, const int bufferlen) {
  const uid_t appid = uid % AID_USER;
  const uid_t userid = uid / AID_USER;
  if (appid >= AID_ISOLATED_START) {
    snprintf(buffer, bufferlen, "u%u_i%u", userid, appid - AID_ISOLATED_START);
  } else if (appid < AID_APP) {
    for (size_t n = 0; n < android_id_count; n++) {
      if (android_ids[n].aid == appid) {
        snprintf(buffer, bufferlen, "u%u_%s", userid, android_ids[n].name);
        return;
      }
    }
  } else {
    snprintf(buffer, bufferlen, "u%u_a%u", userid, appid - AID_APP);
  }
}

print_app_name_from_uid函数中使用的AID_*常量和android_ids数组定义在/system/core/include/private/android_filesystem_config.h中。此外,stubs.cpp也提供了从用户/用户组名到用户/用户组id的转换方法。

事实上,UserHandle也提供了类似的方法:

public static void formatUid(StringBuilder sb, int uid) {
    if (uid < Process.FIRST_APPLICATION_UID) {
        sb.append(uid);
    } else {
        sb.append('u');
        sb.append(getUserId(uid));
        final int appId = getAppId(uid);
        if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
            sb.append('i');
            sb.append(appId - Process.FIRST_ISOLATED_UID);
        } else if (appId >= Process.FIRST_APPLICATION_UID) {
            sb.append('a');
            sb.append(appId - Process.FIRST_APPLICATION_UID);
        } else {
            sb.append('s');
            sb.append(appId);
        }
    }
}

到这里,uid、userId和appId之间的关系就为大家梳理完了,相信你对它们一定有了一个崭新的认识。

参考:

  1. https://developer.android.google.cn/about/versions/android-4.2#MultipleUsers
  2. https://source.android.com/devices/tech/admin/multi-user
  3. https://developer.android.com/work/dpc/dedicated-devices/multiple-users
更多精彩文章,欢迎大家关注阳哥的公众号: 阳哥说技术

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐