需求:在 Android 设备 USB Host 模式下,三方 APK 需实现向 U 盘写数据(未使用 SAF 方式)。

系统版本:Android 14
需求场景:APK 未采用 SAF(Storage Access Framework),需读写 U 盘数据

xxx:/ $ ps -A | grep testu0_a131 82594751387170412727600 S com.testxxx:/ $ cat proc/8259/status | grep -i groupsGroups: 30013002300399972013150131路径:frameworks/base/data/etc/platform.xml
添加如下内容:
<group gid="1077" /> <!-- 或者 --><group gid="external_storage" />#define AID_EXTERNAL_STORAGE 1077/* Full external storage access including USB OTG volumes */Android 解析
相关代码解析如下:
frameworks/base/services/core/java/com/android/server/SystemConfig.javaswitch(name){case"group": { ... String gidStr = parser.getAttributeValue(null, "gid"); ...int gid = android.os.Process.getGidForName(gidStr); ... } break;android_os_Process_getGidForName 这个函数的核心任务是:
frameworks/base/core/jni/android_util_Process.cppjint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name){...for (size_t i=0; i<N; i++) {if (str[i] < '0' || str[i] > '9') {// 只要发现一个字符不是数字,就判定这是一个“组名”(如 "external_storage") struct group* grp = getgrnam(str);if (grp == NULL) {return -1; }return grp->gr_gid; // 返回从系统数据库查到的 ID } }return atoi(str);}return -1;}getgrnam(str): 这是一个标准的 POSIX C 函数。它会去查找系统的组数据库。在 Android 中,它不仅查找文件,还会查找硬编码在 bionic 库(Android 的 C 库)中的系统组定义(如 AID_INET)。
atoi(str): 如果整个字符串全是数字,它就直接把字符串转成整数返回。
<permission name="android.permission.INTERNET" > <group gid="inet" /> <group gid="1077" /></permission>选择 android.permission.INTERNET,因其为普通权限,APK 只要在 AndroidManifest.xml 声明即可自动获取。 如果写在了运行时权限中,需要用户允许权限后,才会将权限对应的gid加入应用所在的gids。
<uses-permission android:name="android.permission.INTERNET" />xxx:/ $ cat proc/3115/status | grep -i groupsGroups: 107730013002300399972013150131例如:
3001:AID_NET_BT_ADMIN,对应 android.permission.BLUETOOTH_ADMIN
3002:AID_NET_BT,对应 android.permission.BLUETOOTH
其他如 20131、50131 等超过 10000 的大数 GID,通常与多用户或工作资料机制相关。
prebuilt_etc { name: "platform.xml", sub_dir: "permissions", src: "platform.xml",}adb shell 进去查看system/etc/permissions/目录:
xxx:/system/etc/permissions $ ls...platform.xml...frameworks/base/services/core/java/com/android/server/SystemConfig.java
privatevoidreadAllPermissions(){ readPermissions(parser, Environment.buildPath( Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);}publicvoidreadPermissions(final XmlPullParser parser, File libraryDir, int permissionFlag){ ...for (File f : libraryDir.listFiles()) { ...// We'll read platform.xml lastif (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f;continue; } } ...// Read platform permissions last so it will take precedenceif (platformFile != null) { readPermissionsFromXml(parser, platformFile, permissionFlag); }}privatevoidreadPermissionsFromXml(final XmlPullParser parser, File permFile,int permissionFlag){ ...switch (name) {case"group": {if (allowAll) { String gidStr = parser.getAttributeValue(null, "gid");if (gidStr != null) {int gid = android.os.Process.getGidForName(gidStr); mGlobalGids = appendInt(mGlobalGids, gid); ...case"permission": {if (allowPermissions) { String perm = parser.getAttributeValue(null, "name"); ... readPermission(parser, perm); ... } break;}voidreadPermission(XmlPullParser parser, String name)throws IOException, XmlPullParserException {... mPermissions.put(name, perm);...}publicint[] getGlobalGids() {return mGlobalGids;}将上述代码拆开来看,主要做了两件事
---将platform.xml文件中解析出来的全部gid存入mGlobalGids
mGlobalGids = appendInt(mGlobalGids, gid);
--将permission对应的gid存入permission
eg:
<permission name="android.permission.BLUETOOTH" > <group gid="net_bt" /></permission>mPermissions.put(name, perm);
name为android.permission.BLUETOOTH,perm是一个permissionEntry,里面存放了permission对应的gids。
PMS 解析 AndroidManifest.xml,收集所有声明的权限,写入 /data/system/packages.xml
提取字符串:解析器找到所有
构建对象:这些字符串被保存在内存中的 AndroidPackage(或老版本的 PackageParser.Package)对象的 requestedPermissions 列表中。
持久化:PMS 将这些信息写入 /data/system/packages.xml。在这个文件里,你可以看到每个 App 被授予的权限名。
AMS 发起请求:ActivityManagerService 准备启动进程,它向 PMS 请求该 App 对应的 ProcessInfo。
获取 GID 数组:PMS 调用内部方法(如 getAppGids(uid)),返回一个包含所有该 App 应有 GID 的 int[] 数组。
发送给 Zygote:AMS 通过 Socket 将这个 int[] 数组连同 UID、PID 传给 Zygote。
内核落子:Zygote fork 出子进程后,在子进程中调用 setgroups(gids.length, gids)
java.lang.Exception at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:1973) at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:1950) at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2336) at com.android.server.am.ProcessList.startProcessLocked(ProcessList.java:2458) at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:3237) at com.android.server.am.ActivityManagerService$LocalService.startProcess(ActivityManagerService.java:20022)关注startProcessLocked方法
frameworks/base/services/core/java/com/android/server/am/ProcessList.javabooleanstartProcessLocked(ProcessRecord app, HostingRecord hostingRecord,int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks, String abiOverride){ ...int[] gids = null; ...final IPackageManager pm = AppGlobals.getPackageManager(); permGids = pm.getPackageGids(app.info.packageName, //第一步获取权限对应的permGids MATCH_DIRECT_BOOT_AUTO, app.userId); ... gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess); //第二部根据permGids获取gids ... app.setGids(gids);//第三步 将gids设置到ProcessRecord.java的mGids属性 ...}重点关注pm.getPackageGids这个方法,他的调用逻辑如下:
IPackageManager.aidl getPackageGids() --> IPackageManagerBase.java getPackageGids()
--> Computer.java getPackageGids() --> ComputerEngine.java getPackageGids()
frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.javapublicint[] getPackageGids(@NonNull String packageName,@PackageManager.PackageInfoFlagsBits long flags, @UserIdIntint userId) { ...return mPermissionManager.getGidsForUid( UserHandle.getUid(userId, ps.getAppId())); } }returnnull;}frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java publicPermissionManagerServiceImpl(@NonNull Context context, @NonNull ArrayMap<String, FeatureInfo> availableFeatures){ ... SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); //获取mGlobalGids ...// propagate permission configurationfinal ArrayMap<String, SystemConfig.PermissionEntry> permConfig = SystemConfig.getInstance().getPermissions(); //获取Permissions ... Permission bp = mRegistry.getPermission(perm.name); ...if (perm.gids != null) { bp.setGids(perm.gids, perm.perUser); // 将pid设置在Permission对象的mGids属性中 }}publicint[] getGidsForUid(int uid) {//根据uid过滤出应用所在gids ...return uidState.computeGids(mGlobalGids, userId);}frameworks/base/services/core/java/com/android/server/pm/permission/UidPermissionState.java@NonNullpublicint[] computeGids(@NonNullint[] globalGids, @UserIdIntint userId) { IntArray gids = IntArray.wrap(globalGids);if (mPermissions == null) {return gids.toArray(); }finalint permissionsSize = mPermissions.size();for (int i = 0; i < permissionsSize; i++) { PermissionState permissionState = mPermissions.valueAt(i);if (!permissionState.isGranted()) {continue; }finalint[] permissionGids = permissionState.computeGids(userId);if (permissionGids.length != 0) { gids.addAll(permissionGids); } }return gids.toArray();}通过在 platform.xml 为普通权限(如 android.permission.INTERNET)添加
该机制基于 Android 权限体系和进程组管理,适用于需要直接访问 USB OTG 存储设备的场景。
配置完成后,重启设备并重新安装 APK 即可生效。
如有更多细节需求,可进一步查阅相关源码或系统文档。

