Android系统 理解/sys/目录权限和UID和GID?
参考学习: Android 中的权限
Android权限的目的是为了保护用户数据和设备。Android系统中有两种类型的权限普通权限和危险权限 , 为了方便用户管理危险权限又分为不同的权限组 从Android 6.0开始,又引入了运行时权限机制。除了普通权限和危险权限之外 存在系统级别或签名级别的特殊权限。反正不同Android版本间的权限都有差异 越新版本权限越严格(S/B)。
核心点 | 详细描述 |
权限目的 | 保护用户的数据和设备安全。 |
权限类型 | - 普通权限: 不会对用户隐私或设备造成大的影响。如访问网络。 - 危险权限: 可能对用户隐私或设备造成大的影响。如读取联系人。 |
权限组 | 危险权限被分为不同的权限组,如电话、存储、位置等。授予组内任一权限会自动授予该组其他权限。 |
运行时权限 | 从Android 6.0开始,应用在使用危险权限时需在运行时请求用户授权,而非安装时。 |
特殊权限 | 系统级别或签名级别的权限,如修改系统设置。通常只由系统应用或相同签名的应用使用,但有例外情况。 |
版本差异 | 不同Android版本间的权限有差异,比如从6.0引入运行时权限,10.0开始限制后台位置访问。 |
权限组
权限组的概念和作用
权限组是一种将相关的危险权限进行分类和分组的方式,每个危险权限都属于一个或多个权限组,如存储、相机等。权限组的作用是方便用户管理危险权限,减少用户频繁地处理授权请求,也让用户更清楚地知道应用需要什么样的功能。
Android系统中有以下几个权限组:
- 存储:允许应用访问外部存储空间,如读取和写入文件、获取存储信息等。
- 相机:允许应用使用设备的相机功能,如拍照、录像、获取相机参数等。
每个权限组中包含了一个或多个危险权限,可以在这里查看每个权限组中具体包含哪些危险权限。
如何定义和使用权限组
在开发应用时,需要在AndroidManifest.xml文件中声明需要的权限,无论是普通权限还是危险权限。可以使用<uses-permission>
标签来声明一个单独的权限,也可以使用<uses-permission-group>
标签来声明一个整个的权限组。例如:
<!-- 声明单独的危险权限 --> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 声明整个的权限组 --> <uses-permission-group android:name="android.permission-group.CAMERA" /> <uses-permission-group android:name="android.permission-group.MICROPHONE" />
如果声明了一个整个的权限组,那么不需要再声明该组中的单独的危险权限了。但是如果只声明了该组中的某些危险权限,那么就只能使用这些声明过的危险权限,而不能使用该组中其他没有声明过的危险权限。例如:
<!-- 声明存储组中的写入外部存储危险权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 不能使用存储组中其他没有声明过的危险权限,如读取外部存储 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
在运行时,需要向用户请求授权需要的危险权限,可以使用ActivityCompat.requestPermissions()
方法来请求一个或多个危险权限,也可以使用ActivityCompat.requestPermissionsFromGroup()
方法来请求一个整个的权限组。例如:
// 请求单独的危险权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, REQUEST_CODE); // 请求整个的权限组 ActivityCompat.requestPermissionsFromGroup(this, Manifest.permission-group.CAMERA, REQUEST_CODE);
如果请求了一个整个的权限组,那么用户只需要一次授权就可以授予该组中的所有危险权限。但是如果只请求了该组中的某些危险权限,那么用户只会授予这些请求过的危险权限,而不会授予该组中其他没有请求过的危险权限。例如:
// 请求存储组中的写入外部存储危险权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); // 用户只会授予写入外部存储危险权限,而不会授予存储组中其他没有请求过的危险权限,如读取外部存储
在请求权限时,需要提供一个请求码,用于标识请求。在用户做出授权选择后,可以在onRequestPermissionsResult()
方法中接收用户的选择结果,并根据结果进行相应的处理。例如:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 判断请求码是否匹配 if (requestCode == REQUEST_CODE) { // 判断授权结果是否成功 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授权成功,执行相应的操作 Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show(); } else { // 授权失败,提示用户或者退出应用 Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show(); } } }
在使用危险权限时,需要先检查是否已经获得了该权限,可以使用ContextCompat.checkSelfPermission()
方法来检查某个单独的危险权限,也可以使用ContextCompat.checkPermissionGroup()
方法来检查某个整个的权限组。例如:
// 检查单独的危险权限 int cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); int audioPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO); // 检查整个的权限组 int cameraGroup = ContextCompat.checkPermissionGroup(this, Manifest.permission-group.CAMERA); int microphoneGroup = ContextCompat.checkPermissionGroup(this, Manifest.permission-group.MICROPHONE);
如果检查结果为PackageManager.PERMISSION_GRANTED
,则表示已经获得了该权限或者该组中的所有权限,可以直接使用。如果检查结果为PackageManager.PERMISSION_DENIED
,则表示没有获得了该权限或者该组中的任何一个权限,需要向用户请求授权。例如:
// 检查存储组中的写入外部存储危险权限 int writePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (writePermission == PackageManager.PERMISSION_GRANTED) { // 已经获得了写入外部存储危险权限,可以直接使用 saveFileToExternalStorage(); } else { // 没有获得了写入外部存储危险权限,需要向用户请求授权 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); }
权限管理
Android系统的权限管理机制
Android的权限管理机制涉及应用如何请求、获得和使用权限,以及用户如何授予和管理这些权限。早期版本(6.0以下)在安装时请求权限,而6.0及以上版本在运行时请求危险权限。从11.0开始,用户可以授予应用一次性的使用权,特定于当前会话。而从10.0开始,应用要在后台访问位置信息,除了需要精确位置权限外,还需要一个特殊的后台位置访问权限。
权限管理类型 | 描述 | 适用版本 |
安装时权限 | 应用在安装时展示所需权限,用户选择是否接受。权限可以在系统设置中启用或禁用,但不能单独控制。 | 6.0以下 |
运行时权限 | 应用在运行时请求危险权限。用户可以在系统设置中查看或修改,可以单独或整组控制。 | 6.0及以上 |
一次性权限 | 用户可以授予应用当前会话的一次性使用权。权限在应用退出或设备锁屏后失效。 | 11.0及以上 |
后台位置访问 | 应用需要特殊的后台位置访问权限才能在后台获取位置信息,否则只能在前台访问。 | 10.0及以上 |
如何在应用中实现权限管理
在开发Android应用时,为了提高功能、安全性和用户体验,开发者应仅请求必要的权限,明确告知用户为何需要某权限,尊重用户的选择和隐私,并适配不同版本的权限特性和规则。
建议 | 描述 |
只请求必要的权限 | 根据应用功能和需求确定所需权限,避免请求不必要或冗余的权限,以增加用户信任。 |
解释权限需求 | 在请求危险权限前,明确告知用户为何需要该权限及其用途,以减少用户疑虑。 |
尊重用户选择和隐私 | 接受用户的授权决策,无论同意或拒绝。避免强迫、诱导或反复请求。确保不滥用或泄露用户数据。 |
适配版本的权限特性 | 考虑到Android不同版本间的权限差异,如6.0的运行时权限和10.0的后台位置访问限制,确保应用在各版本上正常运行。 |
以确保应用在各个版本上都能正常运行。可以使用Build.VERSION.SDK_INT
来判断当前设备的系统版本,并根据版本进行相应的处理。例如:
// 判断当前设备是否为6.0及以上版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 是6.0及以上版本,使用运行时权限机制 // 检查是否已经获得某个危险权限 int cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); if (cameraPermission == PackageManager.PERMISSION_GRANTED) { // 已经获得了危险权限,可以直接使用 openCamera(); } else { // 没有获得了危险权限,需要向用户请求授权 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE); } } else { // 是6.0以下版本,使用安装时权限机制 // 不需要检查或请求危险权限,可以直接使用 openCamera(); }
权限兼容性
Android不同版本间的权限差异
Android系统在不同版本中对权限有所变化。从6.0开始,引入了运行时权限机制,要求应用在运行时请求危险权限。11.0版本引入了一次性权限,允许用户为敏感权限授予一次性使用权。从10.0开始,应用在后台访问位置信息需要特殊的权限,并引入了分区存储模式,限制应用直接访问外部存储。
特性/变化 | 描述 | 适用版本 |
运行时权限机制 | 应用在运行时请求危险权限,而非安装时。 | 6.0及以上 |
一次性权限 | 用户可以为敏感权限(如位置、相机)授予一次性使用权,仅在当前会话有效。 | 11.0及以上 |
后台位置访问 | 应用在后台访问位置需要ACCESS_BACKGROUND_LOCATION 权限,而非仅ACCESS_FINE_LOCATION 。 |
10.0及以上 |
分区存储 | 应用只能访问自己的沙盒存储,访问其他存储需通过特定API或权限。 | 10.0及以上 |
如何处理权限的兼容性问题
开发Android应用时,为确保在各版本正常运行,开发者需考虑权限差异。建议设置最新的目标SDK版本,根据设备系统版本判断权限需求,并使用Jetpack兼容库简化权限处理。
建议 | 描述 | 示例/备注 |
设置目标SDK版本 | 在AndroidManifest.xml中设置应用的目标SDK版本,尽量选择最新版本以适配新权限特性。 | 若目标SDK低于6.0,应用使用安装时权限机制,可能降低透明度和信任度。 |
判断当前系统版本 | 在请求/使用权限前,检查设备系统版本以确定权限需求。 | 使用Build.VERSION.SDK_INT 判断。例如,10.0及以上版本需请求后台位置访问权限。 |
使用兼容库 | 利用Android Jetpack的兼容库简化不同版本间的权限处理。 | 使用PermissionCompat请求危险权限,无需判断版本。 |
可以使用Build.VERSION.SDK_INT
来判断当前设备的系统版本,并根据版本进行相应的处理。例如:
// 判断当前设备是否为10.0及以上版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // 是10.0及以上版本,需要请求后台位置访问危险权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, REQUEST_CODE); } else { // 是10.0以下版本,不需要请求后台位置访问危险权限 // 可以直接使用位置信息 getLocation(); }
可以使用Android Jetpack中提供的一些兼容库来简化和统一对于不同版本间权限差异的处理。例如,可以使用PermissionCompat类来请求一个或多个危险权限,而不需要判断当前设备是否为6.0及以上版本。例如:
// 使用PermissionCompat类来请求危险权限 PermissionCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE);
如何请求和使用特殊权限
在开发应用时,如果需要使用某些特殊权限来实现一些高级或者特殊的功能,需要以下几个步骤:
- 声明特殊权限:需要在AndroidManifest.xml文件中声明需要使用的特殊权限,使用
<uses-permission>
标签来声明一个单独的特殊权限。例如:
<!-- 声明修改系统设置特殊权限 --> <uses-permission android:name="android.permission.WRITE_SETTINGS" />
- 检查特殊权限:需要在使用某个特殊权限之前,检查是否已经获得了该特殊权限,可以使用
Settings.System.canWrite()
方法来检查修改系统设置特殊权限,使用Settings.canDrawOverlays()
方法来检查悬浮窗特殊权限,使用DevicePolicyManager.isAdminActive()
方法来检查设备管理器特殊权限,使用NotificationManager.isNotificationPolicyAccessGranted()
方法来检查通知监听特殊权限,使用AccessibilityManager.isEnabled()
方法来检查辅助功能特殊权限,使用PackageManager.canRequestPackageInstalls()
方法来检查安装未知来源应用特殊权限。例如:
// 检查修改系统设置特殊权限 boolean canWrite = Settings.System.canWrite(this); // 检查悬浮窗特殊权限 boolean canDraw = Settings.canDrawOverlays(this); // 检查设备管理器特殊权限 DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE); ComponentName componentName = new ComponentName(this, MyDeviceAdminReceiver.class); boolean isAdminActive = devicePolicyManager.isAdminActive(componentName); // 检查通知监听特殊权限 NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); boolean isNotificationAccessGranted = notificationManager.isNotificationPolicyAccessGranted(); // 检查辅助功能特殊权限 AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE); boolean isAccessibilityEnabled = accessibilityManager.isEnabled(); // 检查安装未知来源应用特殊权限 PackageManager packageManager = getPackageManager(); boolean canRequestPackageInstalls = packageManager.canRequestPackageInstalls();
如果检查结果为true
,则表示已经获得了该特殊权限,可以直接使用。如果检查结果为false
,则表示没有获得了该特殊权限,需要向用户请求授权。
- 请求特殊权限:需要在运行时向用户请求授权需要的特殊权限,以使用
startActivityForResult()
方法来启动一个系统提供的设置界面,让用户在该界面中授予或拒绝请求。需要提供一个请求码,用于标识请求。在用户做出授权选择后,可以在onActivityResult()
方法中接收用户的选择结果,并根据结果进行相应的处理。例如:
// 请求修改系统设置特殊权限 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE); // 请求悬浮窗特殊权限 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE); // 请求设备管理器特殊权限 Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName); intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "请授予设备管理器权限"); startActivityForResult(intent, REQUEST_CODE); // 请求通知监听特殊权限 Intent intent = new Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS); startActivityForResult(intent, REQUEST_CODE); // 请求辅助功能特殊权限 Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); startActivityForResult(intent, REQUEST_CODE); // 请求安装未知来源应用特殊权限 Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE);
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); // 判断请求码是否匹配 if (requestCode == REQUEST_CODE) { // 判断授权结果是否成功 if (canWrite || canDraw || isAdminActive || isNotificationAccessGranted || isAccessibilityEnabled || canRequestPackageInstalls) { // 授权成功,执行相应的操作 Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show(); } else { // 授权失败,提示用户或者退出应用 Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show(); } } }
希望这篇文章能对您有所帮助。如果还有其他问题或建议,请留言与私信。