一、Android权限概念介绍
1.基本概念
因为 Android 应用是沙箱隔离的,它们只能访问自己的文件和一些设备上全局可访问的资源。为了使应用具备更丰富的功能, Android 可以赋子应用额外的、更细粒度的访问权限(permission),它们可以控制硬件设备、网络连接、数据或OS 服务的访问。而且,通过权限系统,安卓能够确保应用程序只能在用户明确授权的情况下访问敏感数据和使用设备资源。这对于安卓的安全架构来说很重要。
2.权限的本质
在安卓中,一个权限简单来说只是一个字符串。这个字符串表示执行特定操作的能力。这些能力可以是任何操作,从访问一个物理资源(比如设备的SD卡),或是共享数据(比如注册的联系人列表),到启动或访问一个第三方应用中的组件。
访问一个物理资源(比如设备的SD卡),或是共享数据(比如注册的联系人列表),到启动或访间一个第三方应用中的组件。Android 內置了一组预定义的权限。新版本引入的一些新特性,都会有相对应的权限添加进来。
权限名称通常以定义它的包名做前缀,再接上.permission 字符串。因为安卓内置权限都是在 andnoid包内定义的,所以它们的名称都是以 android.permission打头。
使用以下命令可以查看当前系统已知的权限列表。在命令后加-f参数,可以打印权限的额外信息,包括定义的包名、标签、描述和保护级别(注意-f参数的输出不会包含未分组的权限信息。要显示未分组的权限信息需要使用-g参数)。
pm list permission
3.权限的等级分类
普通权限(Normal Permissions):
这些权限涉及应用程序对设备功能或用户数据的访问,这种访问对用户隐私或设备操作的风险较低。例如,访问互联网或设置壁纸等操作。这类权限由系统在安装时自动授予,无需用户明确同意。
危险权限(Dangerous Permissions):
这些权限允许应用程序访问对用户隐私或设备操作有较大影响的数据或功能,例如读取联系人、位置、相机等。应用程序在运行时请求这些权限,用户需要在运行时明确同意。
签名权限(Signature Permissions):
这些权限只授予由相同开发者签名的应用程序,通常用于同一个开发者的不同应用之间的通信。当应用的签名与声明该权限的应用程序的签名匹配时,系统就才授予该权限。
特权权限(Privileged Permissions):
这些权限授予设备制造商或运营商预装的系统应用程序,这类权限能够访问系统级功能或敏感数据。只有预装的系统应用才能获取这些权限,一般应用无法请求。
签名与系统权限(SignatureOrSystem Permissions):
这些权限授予拥有相同签名或被安装在系统分区的应用程序,通常用于设备厂商或系统组件之间的通信(Android 6.0以后,这个权限等级逐渐被弃用)。
二、权限注册与申请
1.安卓静态注册或使用权限
静态方法注册的权限在安装应用时会被安卓系统的包管理器(Package Manager,负责安装、卸载、更新和管理应用程序)记录并管理(/data/system/packages.xml)。
当用户安装应用时,包管理器会解析AndroidManifest.xml
,提取出所有静态注册的权限和静态声明的权限。
包管理器会将这些权限与应用关联起来,并在应用的生命周期内管理这些权限的授予或拒绝。对于安卓6.0(API级别23)及以上版本,某些敏感权限(如摄像头、位置等)需要在运行时动态申请,包管理器会在运行时检查这些权限是否已授予。
当应用尝试使用某个静态注册的权限时,系统会通过包管理器验证该权限是否已经授予。如果权限已经授予,应用可以使用相关功能;否则,使用该功能将会失败,或抛出异常。当应用更新时,包管理器会再次检查AndroidManifest.xml
中权限声明的变化。如果有新权限声明,用户可能需要在更新时同意这些权限。卸载应用时,包管理器会移除与该应用相关的权限记录。
1. 1 静态申请使用权限
这是指在AndroidManifest.xml
中用<uses-permission>标签声明应用需要的权限,这种声明方式在应用的生命周期内保持不变。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- Activities, Services, etc. -->
</application>
</manifest>
1.2 静态注册权限
注册权限通常用于自定义权限的定义和使用,主要用于控制应用之间的交互。注册的权限可以限制其他应用访问某些功能或数据。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- 定义自定义权限 -->
<permission
android:name="com.example.app.MY_CUSTOM_PERMISSION"
android:protectionLevel="signature"
android:label="@string/permission_label"
android:description="@string/permission_description" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<!-- 需要自定义权限的组件 -->
<activity
android:name=".MyProtectedActivity"
android:permission="com.example.app.MY_CUSTOM_PERMISSION">
</activity>
</application>
</manifest>
在这个例子中,com.example.app.MY_CUSTOM_PERMISSION
是一个自定义权限,用于保护MyProtectedActivity,并且保护级别被设置成了signature,即
只有签署了相同证书的应用才能启动此Activity。
1.3 特殊权限的静态申请
某些特殊权限需要在AndroidManifest.xml
中静态声明,同时在运行时通过代码动态申请,例如 SYSTEM_ALERT_WINDOW
或 WRITE_SETTINGS
。虽然需要通过 manifest 文件静态声明,但这只是声明应用需要这些权限,并不能自动获得这些权限的使用权。这些权限声明后,应用在运行时还需要通过代码来申请这些权限的实际使用权限。这些特殊权限属于AppOps
机制管理的权限类型。
1.4 权限组的使用
通过权限组,可以将多个权限组织在一起,方便权限的管理和使用。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<permission-group android:name="com.example.app.PERMISSION_GROUP"
android:label="@string/permission_group_label"
android:description="@string/permission_group_description" />
<permission android:name="com.example.app.PERMISSION_1"
android:permissionGroup="com.example.app.PERMISSION_GROUP"
android:protectionLevel="dangerous"
android:label="@string/permission_1_label"
android:description="@string/permission_1_description" />
<uses-permission android:name="com.example.app.PERMISSION_1" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<!-- Other components -->
</application>
</manifest>
1.5 权限的导出与保护
某些组件如ContentProvider
、Service
等,可能需要导出给其他应用使用,此时可以通过注册权限来保护这些组件。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<permission android:name="com.example.app.READ_PROVIDER"
android:protectionLevel="dangerous"
android:label="@string/permission_label"
android:description="@string/permission_description" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<provider
android:name=".MyContentProvider"
android:authorities="com.example.app.provider"
android:exported="true"
android:permission="com.example.app.READ_PROVIDER" />
</application>
</manifest>
在这个例子中,MyContentProvider
被声明为导出组件,并且只有持有com.example.app.READ_PROVIDER
权限的应用才能访问。
2. 安卓动态注册使用权限的方法
在Android 6.0(API级别23)及更高版本中,有时候应用需要在运行时请求权限。这以下是实现动态注册使用权限的方法:
1. 检查权限
在执行需要权限的操作之前,首先需要检查应用是否已经拥有该权限。可以使用ContextCompat.checkSelfPermission()
方法来检查权限状态,这个方法基于UID检查:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.YOUR_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
// 权限未被授予
}
2. 请求权限
如果权限未被授予,需要向用户请求权限。可以使用ActivityCompat.requestPermissions()
方法来请求权限:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.YOUR_PERMISSION},
REQUEST_CODE);
其中,REQUEST_CODE
是一个整数,用于标识权限请求,可以在onRequestPermissionsResult()
回调方法中使用。
3. 处理权限请求结果
当用户响应权限请求时,系统会调用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) {
// 权限被授予
} else {
// 权限被拒绝
}
}
}
4. URI权限管理
在某些情况下,应用需要与其他应用共享文件或数据。为了确保数据的安全性,Android提供了URI权限管理机制。可以使用grantUriPermission
方法来授予其他应用对特定URI的访问权限。
4.1 授予URI权限
使用grantUriPermission
方法授予其他应用对特定URI的访问权限:
grantUriPermission("com.example.otherapp", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
其中,"com.example.otherapp"
是目标应用的包名,uri
是要共享的URI,Intent.FLAG_GRANT_READ_URI_PERMISSION
是授予的权限标志。该授权方式属于持久授权,除非授权方主动调用revokeUriPermission进行取消授权。
4.2 撤销URI权限
使用revokeUriPermission
方法撤销对特定URI的访问权限:
revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
5. 使用Intent传递URI权限
在某些情况下,应用需要通过Intent
传递URI权限给其他应用。可以使用Intent.addFlags()
方法来添加权限标志。
5.1 添加权限标志
使用Intent.addFlags()
方法添加权限标志:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
三、安卓四大组件的权限执行
1. Activity和Service权限执行
如果传递到 Context.startActivity()或 startActivityForResult()方法的 intent,解析到一个声明了权限的 activity 时,就需要执行 activity 的权限检查。如果调用者不具备该权限,则抛出 SecurityException 昇常。因为 Android 服务可以被启动、终止和绑定,如果目标服务声明权限的话,对其 Context.startService()、stopService()和 bindService()方法的调用都会受到权限检查。
2. content provider 权限执行
content provider 权限可以保护整个组件或者特定的导出 URI,并且可以分别为读写指定不同的权限。如果已为读写分别指定了不同权限,那么读权限控制谁可以调用目标provider 或 URI 的 ContentResolver.query()方法,而写权限控制谁可以对目标 provider 或其某个暴露的 URI 调用 ContentResolver.insert()、ContentResolver.update()和ContentResolver.delete()方法。当某个方法被调用时,权限检查操作被同步执行。
3. Broadcast权限执行
当发送一个广播(Broadcast)时,应用程序可使用 Context.sendBroadcast (Intentintent,String receiverpermission)方法,来要求接收者应具有某一个特定权限。因为广播是异步的,所以调用这个方法时,是不进行权限检查的。权限检查操作在 intent 被传递到已注册的广播接收者时执行。如果目标接收者不具有该权限,直接忽略,不会接收广播,也不会有异常抛出。反过来,广播接收者可以要求广播发送者必须具有某个指定权限,以便于锁定发送者。 接收者所要求的权限可由 manifest 文件指定,也可在动态注册广播时指定。权限检查也在广播传递时执行,而且同样不会抛出 SecurityException 异常。因此,收发广播可能需要两个权限检查
1:检查广播发送者(由接收者指定权限)。
2:检查广播接收者(由发送者指定)。
3.1 protected 广播和 sticky 广播
一些系统广播被声明为 protected(例如 BOOT_COMPLETED 和 PACKAGE_INSTALLED),并且只能由系统进程发送,这些系统程序必须运行在以下 UID:SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果以其他 UID 进行的进程试图发送一个 protected广播,那么当它调用 sendBroadcast()方法时,会收到 SecurityException 异常。发送 sticky 广播(如果标记为sticky,系统在广播结束之后,还会保留发送的 Intent 对象),需要发送者具有BROADCAST_STICKY权限,否则也不会抛出异常,也不会被发送出去。
4. 共享UID的权限执行
共享UID是指在AndroidManifest.xml
文件中,通过sharedUserId
属性将多个应用程序配置为共享相同的用户ID。共享UID的应用程序可以访问彼此的私有数据和资源,就像它们是同一个应用程序一样。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app1"
android:sharedUserId="com.example.shareduserid">
当多个应用程序共享相同的UID时,它们可以继承和共享彼此的权限。这意味着,如果一个应用程序请求了某个权限,另一个共享相同UID的应用程序也可以使用该权限。即使应用程序共享相同的UID,某些情况下它们仍然需要动态请求权限。