这篇文章为你们系统的梳理一下 Android 权限相关的知识,在平常开发中,咱们都用过权限,可是对于权限的一些细节咱们可能掌握的还不够全面,这篇文章会全面的为你们介绍权限相关的知识。固然,本篇文章依然是参考了 Google 的官方文档:应用权限。php
Android 系统设置权限的目的是保护 Android 用户的隐私。对于用户的敏感数据 Android 应用程序必须向用户申请受权后才能访问(如联系人和短信),另外还包括某些系统功能(如摄像头、麦克风)的权限。根据功能的不一样,系统可能会自动授予权限或提示用户批准请求。Android 安全架构的一个核心设计要点是,在默认状况下,没有应用程序能够执行任何可能对其余应用程序、操做系统或用户形成不利影响的操做。这包括读取或写入用户的私人数据(如联系人或电子邮件)、读取或写入另外一个应用程序的文件、执行网络访问等等。java
权限分为几个保护级别。保护级别影响是否须要运行时权限请求:android
须要咱们了解的是正常权限和危险权限。安全
1.正常权限网络
正常的权限覆盖了应用程序须要访问沙箱以外的数据或资源的区域,但这些区域对用户隐私或其余应用程序的操做几乎没有风险。例如,设置时区的权限是正常的权限。 若是应用程序在它的清单中声明它须要一个正常的权限,系统会在安装时自动授予该权限。系统不提示用户授予正常权限,用户也不能撤销这些权限。架构
2.危险权限并发
危险权限包括应用程序须要涉及用户私人信息的数据或资源的区域,也包括可能影响用户存储的数据或其余应用程序的操做的区域。例如,读取用户的联系人是一种危险的权限。若是一个应用程序声明它须要一个危险的权限,用户必须显式地授予该应用程序权限。在用户批准该权限以前,应用程序不能提供依赖于该权限的功能。 要使用危险的权限,应用程序必须在运行时提示用户授予权限。app
应用程序必须经过在清单文件(AndroidManifest.xml
)中使用 <uses-permission>
标记来公布它须要的权限。例如声明网络访问权限:async
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp">
//声明网络访问权限
<uses-permission android:name="android.permission.INTERNET"/>
<application ...>
...
</application>
</manifest>
复制代码
若是在应用程序的清单文件中列出的是正常的权限(即不会对用户隐私或设备操做形成太大风险的权限),系统会自动将这些权限授予给应用程序。ide
若是在应用程序的清单中列出的是危险的权限(便可能影响用户隐私或设备正常操做的权限),则必须通过用户的赞成才能受权相应的权限。
Android 请求用户授予危险权限的方式取决于用户设备上运行的 Android 版本,以及咱们在应用中设置的 targetSdkVersion
。主要有两种处理方式:
若是手机 Android 系统的版本是 6.0 (API级别23) 或者更高,而应用程序的 targetSdkVersion
是 23 或者更高,用户在安装时不会收到任何应用程序权限通知。应用程序必需要求用户在运行时授予危险的权限。当应用程序请求权限时,用户会看到一个系统对话框,告诉用户应用程序试图访问哪一个权限组。对话框包含一个拒绝和容许按钮。
若是用户拒绝权限请求,那么下一次应用程序请求该权限时,对话框将包含一个复选框,选中该复选框后,用户不会再收到权限申请提示。
下面经过申请拍照权限为例:
假如用户选择了“容许”,也不能表示应用就会一直拥有该权限。用户还能够进入系统设置页面,将以前那授予的权限关闭掉,所以,咱们在开发中必须在运行时去检查和申请相应的权限,以防止在运行时出现 SecurityException
的错误,致使应用奔溃。
若是手机 Android 系统的版本是 5.1.1 (API级别22) 或者更低,而应用程序的 targetSdkVersion
是 22 或者更低,系统会自动要求用户在安装时为应用程序授予全部危险的权限。
Accept
,应用程序请求的全部权限都将被授予。若是用户拒绝权限请求,系统将取消应用程序的安装。若是应用程序更新须要额外的权限,用户在更新应用程序以前会被提示接受这些新的权限。
有两个权限的行为不像正常权限和危险权限:SYSTEM_ALERT_WINDOW
和 WRITE_SETTINGS
这是两个特别敏感的权限,因此大多数应用程序不该该使用它们。若是应用程序须要这些权限之一,它必须在清单中声明该权限,并发送一个意图请求用户的受权。系统经过向用户显示详细的管理屏幕来响应这个意图。
以申请 SYSTEM_ALERT_WINDOW
为例:
step 1:首先在清单文件中声明权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
复制代码
step 2:申请权限(6.0 及其以上版本)
//在 6.0 之前的系统版本,悬浮窗权限是默认开启的,直接使用便可。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
return;
}
}
复制代码
Android 系统对全部的危险权限进行了分组,称为 权限组
。
权限组 | 权限 |
---|---|
CALENDAR | READ_CALENDAR WRITE_CALENDAR |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS |
LOCATION | ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
PHONE | READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS |
SENSORS | BODY_SENSORS |
SMS | SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
STORAGE | READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE |
若是手机 Android 系统的版本是 6.0 (API级别23) 或者更高,而应用程序的 targetSdkVersion
是 23 或者更高,则当应用程序请求危险权限时,系统会有以下行为:
若是应用程序当前在权限组中没有任何权限,则系统向描述应用程序但愿访问的权限组的用户显示权限请求对话框。对话框没有描述该组中的特定权限。例如,若是一个应用程序请求READ_CONTACTS权限,系统对话框只告诉应用程序须要访问设备的联系人。若是用户给予批准,系统只会给应用程序它所请求的权限。 若是应用程序已经在同一权限组中被授予了另外一个危险权限,系统会当即授予该权限,而不与用户进行任何交互。例如,若是一个应用程序以前请求并被授予了READ_CONTACTS权限,而后它请求WRITE_CONTACTS,系统当即授予该权限,而不向用户显示权限对话框。
上面是官方的原话,简而言之就是:属于同一组的危险权限将自动合并授予,用户授予应用某个权限组的权限,则应用将得到该权限组下的全部权限(前提是相关权限在 AndroidManifest.xml 中有声明)。
然而事实真的如此吗?咱们来试验一下属于同一个权限组下的 READ_CONTACTS
权限和 WRITE_CONTACTS
权限。按照官方的说法,若是我先受权了 READ_CONTACTS
权限,那么 WRITE_CONTACTS
权限会被自动授予,咱们来看看实际运行的效果:
READ_CONTACTS
权限后,再去申请
WRITE_CONTACTS
权限时,依旧弹出了对话框让用户受权,这明显和官方文档说明的不一致。可是同时官方建议咱们,
不要将应用程序的逻辑创建在这些权限组的结构上,由于在将来的版本中,可能会将一个特定的权限从一个组移动到另外一个组,所以,咱们的代码逻辑不该该依赖权限组,而是应该显式地请求它须要的每一个权限,即便用户已经在同一组中授予了另外一个权限。
每款 Android 应用都在访问受限的沙盒中运行。若是应用须要使用其本身的沙盒外的资源或信息,则必须请求相应权限。 要声明应用须要某项权限,能够在应用清单中列出该权限,而后在运行时请求用户批准每项权限(适用于 Android 6.0 及更高版本)。
不管应用须要什么权限,都须要在清单文件中对权限进行声明。系统会根据声明权限的敏感程度采起不一样的操做。有些权限被视为“常规”权限,系统会在安装应用时当即授予这些权限。还有些则被视为“危险”权限,须要用户明确授予相应访问权限。
若是应用须要一项危险权限,那么每次执行须要该权限的操做时,都必须检查本身是否具备该权限。从 Android 6.0(API 级别 23)开始,用户可随时从任何应用撤消权限,即便应用以较低的 API 级别为目标平台也是如此。所以,即便应用昨天使用了相机,也不能认为它今天仍具备该权限。
要检查应用是否具备某项权限,请调用 ContextCompat.checkSelfPermission()
方法。例如,如下代码段展现了如何检查 Activity 是否具备向日历写入数据的权限:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
复制代码
若是应用具备此权限,该方法将返回 PERMISSION_GRANTED
,而且应用能够继续操做。若是应用不具有此权限,该方法将返回 PERMISSION_DENIED
,且应用必须明确要求用户授予权限。
当应用从 checkSelfPermission()
收到 PERMISSION_DENIED
时,须要提示用户授予该权限。Android 提供了几种可用来请求权限的方法(如 requestPermissions()),以下面的代码段所示。调用这些方法时,会显示一个没法自定义的标准 Android 对话框。
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}
复制代码
在某些状况下,须要帮助用户理解为何应用须要某项权限。例如,若是用户启动一款摄影应用,用户或许不会对该应用请求使用相机的权限感到惊讶,但用户可能不理解为何该应用想要访问用户的位置或联系人。在应用请求权限以前,能够向用户提供解释。一种比较好的作法是在用户以前拒绝过该权限请求的状况下提供解释。咱们经过调用 shouldShowRequestPermissionRationale()
方法来实现。若是用户以前拒绝了该请求,该方法将返回 true。若是用户以前拒绝了该权限而且选中了权限请求对话框中的再也不询问选项,或者若是设备政策禁止该权限,该方法将返回 false(注意,若是用户拒绝了该权限,而且勾选了“再也不询问”,即便在返回false的逻辑中调用了requestPermissions方法,系统也不会再弹出选择框)。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request.
}
}
复制代码
Android 是一个特权分离的操做系统,其中每一个应用程序都使用一个惟一的系统标识(Linux 用户 ID 和 组 ID)运行。系统也被称不一样的部分,每一个部分都有本身的标识。所以,Linux 将应用程序彼此隔离,并与系统隔离。应用程序能够自定义权限来提供给其余应用程序访问本身的功能。
要自定义权限,能够在 AndroidManifest.xml
中使用 <permission>
标签来声明。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" >
<permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" />
...
</manifest>
复制代码
属性解释:
下面咱们来写一个具体的例子:咱们在进程1中定义一个 Activity,并为该 Activity 设置访问权限,而后让进程2来访问它。
进程1:
AndroidManifest.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.chenyouyu.permissiondemo">
//自定义的权限,权限级别为 normal
<permission android:name="com.example.myapp.permission.SECOND_ACTIVITY" android:label="abc" android:description="@string/permdesc_SecondActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="normal" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
//为SecondActivity加上android:permission="com.example.myapp.permission.SECOND_ACTIVITY"
<activity android:name=".SecondActivity" android:exported="true" android:permission="com.example.myapp.permission.SECOND_ACTIVITY">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
......
</manifest>
复制代码
进程2:
AndroidManifest.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.chenyouyu.permissiondemo2">
//在AndroidManifest中声明权限
<uses-permission android:name="com.example.myapp.permission.SECOND_ACTIVITY"/>
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
复制代码
MainActivity.java
Intent intent = new Intent();
intent.setAction("com.cyy.jump");
intent.addCategory(Intent.CATEGORY_DEFAULT);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
复制代码
首先运行进程1,而后运行进程2。
Android 不容许两个不一样的应用定义一个相同名字的权限(除非这两个应用拥有相同的签名),因此在命名的时候,须要特别注意。拥有相同自定义权限的软件必须使用一样的签名,不然后一个程序没法安装。
场景:App A中声明了权限PermissionA,App B中使用了权限PermissionA。
状况一:PermissionA的保护级别是normal或者dangerous App B先安装,App A后安装,此时App B没法获取PermissionA的权限,从App B打开App A会报权限错误。 App A先安装,App B后安装,从App B打开App A一切正常。
状况二:PermissionA的保护级别是signature或者signatureOrSystem App B先安装,App A后安装,若是App A和App B是相同的签名,那么App B能够获取到PermissionA的权限。若是App A和App B的签名不一样,则App B获取不到PermissionA权限。即,对于相同签名的app来讲,不论安装前后,只要是声明了权限,请求该权限的app就会得到该权限。
这也说明了对于具备相同签名的系统app来讲,安装过程不会考虑权限依赖的状况。安装系统app时,按照某个顺序(例如名字排序,目录位置排序等)安装便可,等全部app安装完了,全部使用权限的app都会得到权限。
Android6.0引入了动态权限,这个你们都知道了。前面说到的自定义的权限的安全级别android:protectionLevel会影响权限在Android6.0+系统的使用
android:protectionLevel="normal",不须要动态申请 android:protectionLevel="dangerous",须要动态申请
参考文章: