进行Android应用的自动化测试时,遇到一个奇怪的问题:尽管设备上的存储空间充足,但是仍然无法安装新的应用。尝试安装应用时,我们会收到如下错误信息:INSTALL_FAILED_INSUFFICIENT_STORAGE:Package could not be assigned a valid UID。这个错误信息可能会让我们误以为是由于设备的存储空间不足导致的问题,但实际上,问题的根源在于UID资源的耗尽。正常使用很难触及到这个问题,因为我们使用的设备有不同的业务和工具不间断的执行测试,有些设备在上架仅仅一周后就出现了问题,即使删除安装的应用也无法恢复,在没有发现原因之前只能通过刷机来解决。

UID

在Android系统中,每个应用在安装时都会被分配一个唯一的用户标识符(UID)。UID是一个整数,系统通过这个整数来区分和隔离不同的应用程序。不同的应用会拥有不同的UID,相同的应用在不同的设备上的UID也可能不同。

UID的主要用途是为了在操作系统层面实现应用的隔离。因为每个应用有自己的UID,所以系统可以控制一个应用能访问哪些系统资源,包括文件、网络等。这种基于UID的权限控制机制,是Android系统安全模型的重要组成部分。

在大部分情况下,应用的UID是在应用首次安装时由系统自动分配的。系统会保证每个新安装的应用都能得到一个在当前设备上唯一的UID。Android系统在分配UID时,通常会找到当前已分配的UID的最大值,然后给新应用分配这个最大值+1。然而,UID的数量是有限的,当设备上安装的应用数量过多,使得可用的UID不足时,就会出现无法为新应用分配UID的问题。

在Android系统中,UID的范围是被分配的。在Android的源代码libcutils/include/cutils/misc.h中,我们可以看到这个分配的范围:

/* This is the range of UIDs (and GIDs) that are reserved
 * for assigning to applications.
 */
#define FIRST_APPLICATION_UID 10000
#define LAST_APPLICATION_UID 99999

根据以上的源代码,可供应用程序使用的UID范围是10000到99999。这意味着系统可以支持最多90000个不同的应用程序。

解决方法

在解决过程中,我首先运行了命令 adb shell su -c cat /data/system/packages.list | awk '{print $2}' | sort -n来查看当前系统中所有应用的UID并将它们按数值排序的。删除这个UID对应的应用后重启即可释放这个UID。新安装的应用可以正常安装了。

但是我发现即使删除了所有高UID的应用,新安装的应用依然会占据一个高UID,这说明这个问题并未得到解决。这时,我观察到新应用所处的UID,向前一位的UID应该是需要删除的应用。但是在 packages.list 文件中并不存在这个UID,这说明存在一些"幽灵"应用没有在该文件中列出。仅仅通过查看 packages.list 文件可能是不够的,还需要通过 dumpsys 命令来查找那些没有在 packages.list 文件中列出的"幽灵"应用。

为了找到这些"幽灵"应用,通过 adb shell dumpsys package | grep <UID>命令找到这些"幽灵"应用,之后使用 adb uninstall <package_name> 命令删除这些应用,重启后,设备就能恢复正常。

如果设备没有root,在查找所有应用时可以通过ps找到用户应用的进程ID,再通过进程状态查找该进程对应的UID:

adb shell ps -A | grep u0 
adb shell cat /proc/<PID>/status | grep Uid  

防止UID资源耗尽

为了避免在未来再次遇到UID资源耗尽的问题,应该定期清理不再需要的应用和"幽灵"应用。例如将上面的过程自动化,在设备接入时将高UID应用删除,根据业务情况来处理即可。