Permission 权限

Permission 权限

Android引入权限的目的是保护用户隐私。APP一定要在访问用户敏感数据(如短信服务)和系统功能(如相机、网络)时申请权限。对于不同的功能,系统可能会自动通过权限申请,也可能提示用户批准权限。

安卓系统安全架构的中心点是禁止任何APP默认拥有对其他APP、系统或是用户拥有不利影响操作的权限。

申请权限

APP功能需要的权限一定要在manifest中声明。如下APP需要发送短信功能的权限时:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.snazzyapp">

    <uses-permission android:name="android.permission.SEND_SMS"/>

    <application ...>
        ...
    </application>
</manifest>
  1. 如果在manifest中申请的权限是普通权限,那么系统将自动同意
  2. 如果在manifest中申请的权限是危险权限,系统将提示用户同意APP的权限申请

关于普通权限危险权限的区别,查看后面的保护等级

危险权限的申请提示

根据Android版本的不同和APP的目标版本的不同,Android提示用户批准危险权限的方式也不同。

运行时申请 Android 6.0及以上

Android版本在6.0及以上,并且APP目标版本在23及以上时,在安装时,系统不会提醒用户批准权限(荣耀手机会在安装完成时,显示一个用户可以操作权限的界面),用户需要在运行时申请权限。如下图左,系统将弹出Dialog供用户操作。若用户已经拒绝过一次,APP再次申请该权限是,Dialog则会显示“不再提醒”的勾选框,以后APP申请权限时,将不在提示用户申请,自动拒绝,如下图右(现在很多系统会在第一次申请时就显示“不再提醒”的勾选框):

申请运行时权限

另外,及时用户通过了权限,但是也可以在之后在系统设置中关闭权限,所以一定要在运行时每次需要就申请。

安装时申请

Android版本在5.1.1及以下或是APP目标版本在22及以下时,系统将在安装时询问用户批准所有的危险权限。若用户同意,那么系统会批准APP声明的所有权限,若用户不同意,APP将无法安装。(某些定制系统会允许安装,但是需要特殊的方法在运行时申请权限)。

获取用户敏感信息的申请提示

一些APP可能需要获取依赖于短信的用户敏感信息。如果想申请这些权限,一定要在运行时申请权限之前,提示用户在系统设置中,修改“默认应用”,如默认的短信服务、电话服务。

可选的硬件功能的权限

访问硬件功能时需要申请应用权限。然而,不是所有的安卓设备有特定的硬件功能呢。所以,加入你申请相机权限,那么在manifest中一定要使用<uses-feature>标记声明需要的硬件功能。如下:

<uses-feature android:name="android.hardware.camera" android:required="false" />

如果硬件功能的需求设置为true,那么如果硬件上没有该功能,那么系统将不允许安装该APP,如果为false,那么最好在运行时需要改功能前,使用PackageManager.hasSystemFeature()判断设备是否有改功能。

权限的强制执行

权限不仅仅用于申请系统的功能,APPService也可以强制执行想使用它的自定义权限。关于如果自定义权限,可查看自定义的APP权限

Activity权限的强制执行

可以在<activity>标记中添加android:permission属性,限制谁可以启动该Activity。在Context.startActivity()Activity.startActivityForResult()会检查权限,如果调用者没有该权限,调用时将抛出SecurityException

Service权限的强制执行

Activity一样,可以在<service>设置需要的权限。将在Context.startService()Context.stopService()Context.bindService()中检查权限。

Broadcast权限的强制执行

Activity一样,可以在<receiver>设置需要的权限。系统将在Context.sendBroadcast()返回后检查权限,若通过,系统将发送,若未通过,系统不会抛出异常,但也不会发送该Broadcast

动态注册的BroadcastReceiver也可以在注册时的代码中设置权限。

重点:发送者和接收者都需要申请权限。

发送时的权限

当发送广播Broadcast时,可以指定权限参数。只有拥有该权限的接收者能够收到该广播。如下

sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)

接收者一定要获取该权限:

<uses-permission android:name="android.permission.SEND_SMS"/>

不仅仅可以使用Android自带的权限,也可以使用自定义的权限。

接收时的权限

当在<receiver>(接收者)声明时指定权限(危险的权限),那么想要接收者收到该通知,那么一定要在manifest中使用<uses-permission>声明接收者所需要的权限。如下:

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
    </intent-filter>
</receiver>

动态注册类似

BroadcastReceiver权限总结

  1. 在发送时指定权限,指的是接收者必须拥有该权限,并且需要在发送者的manifest中定义该权限。
  2. 接收者指定权限,指的是发送者必须拥有并携带权限,并且需要在接收者的manifest中定义该权限。

Android 8.0 权限限制

为了避免像IM SDK这种进行相互启动的机制。从Android 8.0开始,对广播进行了极大限制,自定义的ACTION不再能够使用。所以,当我在荣耀手机(Android 9.0)上测试时,使用了自定义的ACTION发送广播,接收者始终没有收到。解决方案是使用动态注册。不过这样就实现不了很多功能了。

ContentProvider权限的强制执行

<provider>上添加android:permission属性声明想要操作ContentProvider数据的权限。ContentProvder拥有额外的一个重要的安全特性,被称为URI权限,后面将说明。不同于其他的组件,ContentProvider有两个单独的权限属性可以设置android:readPermissionandroid:writePermission。注意,拥有写权限,并不意味着拥有读权限,这两者是独立的。

当首次检索一个ContentProvider,进行对应操作时将进行权限检查,若未通过,将抛出SecurityException异常。使用ContentResolver.query()时,需要读权限,使用ContentResolver.insert()ContentResolver.update()ContentResolver.delete()时,需要写权限。

URI 权限

标准的权限系统对ContentProvider的功能有限。ContentProvider获取想保护它自己的读写权限,并且使其提供的URI只被单独需要的APP使用。

典型的例子是邮箱应用。应该通过权限机制保护用户的隐私数据。如想要展示邮箱应用中的图片时,提供一个URI给图片预览APP,那么当不再预览时,应该收回该权限,因为没有任何理由再让图片预览APP获取其操作了。

解决方法是为每个URI设置单独的权限,如当启动一个Activity时,设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这样保证接收的Activity能够获取Intent中指定的URI数据,而不管是否操作ContentProvider的权限。

关于URI权限的使用,可以参考FileProvider

其他的权限强制执行

参考:权限概览

权限级别

权限被分为3个级别:

  1. 普通权限:对于用户隐私或是设备操作没有危险的权限,系统将在安装时自动通过该权限
  2. 签名权限:系统将在安装时通过的权限,但是只有相同签名的APP才能够获取。注意,一些签名权限并不用于第三方APP
  3. 危险权限:可能会影响到用户隐私或是设备的普通操作

特殊权限

某些权限并不是与普通权限和危险权限表现相同。SYSTEM_ALERT_WINDOWWRITE_SETTINGS两个权限相当的敏感,大部分APP不应该使用它们。如果APP需要使用,那么需要在manifest中声明,并且发送Intent请求用户验证。如下:

if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
        this, Manifest.permission.WRITE_SETTINGS
    )
) &#123;
    startActivityForResult(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply &#123;
        data = Uri.parse("package:$packageName")
    &#125;, 2)
&#125;

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) &#123;
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) &#123;
        2 -> if (Settings.System.canWrite(this)) &#123;
            Toast.makeText(this, "获取到了WRITE_SETTINGS权限", Toast.LENGTH_LONG).show()
        &#125; else &#123;
            Toast.makeText(this, "没有获取到了WRITE_SETTINGS权限", Toast.LENGTH_LONG).show()
        &#125;
    &#125;
&#125;

权限组

权限根据设备特性或功能被分为不同的组。在这种系统下,权限请求被组织为组级别处理。并且单个清单组对应多个权限声明。例如,SMS权限组包含READ_SMSRECEIVE_SMS权限。这样对权限进行分组,让用户做出更有意义的选择,而不是被复杂的权限请求弄得不知所措。

所有危险的权限都属于权限组。无论保护级别如何,任何权限都可以属于权限组。 但是,如果权限很危险,则权限的组只会影响用户体验。

当设备运行的是Android 6.0及以上,并且APP目标版本是23及以上时,当请求一个危险权限时,系统的表现:当我们请求权限时,系统检查APP是否已经获得同组下的其他权限,若没有,将使用系统Dialog提示用户。若APP已经又有了同组下的危险权限,那么系统将直接通过新申请的权限,不必再提示用户。

请求应用权限

非常简单,直接可以参考请求应用权限

定义自定义权限

自定义权限非常简单,重要的是如何使用权限。如下所示:

<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" />

总结

理解设计权限的目的,就非常容易理解权限的使用。自定义权限较少,大多数时候我们是在使用权限。当然自定义权限也可以使用到,不过这通常是在Service中。另外注意Android 8.0开始,ContentProvider的静态声明受到了极大限制,对于推送功能的实现,目前都需要根据厂商的渠道。


   转载规则


《Permission 权限》 Mycroft Wong 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Intent的一些知识点 Intent的一些知识点
Intent的一些知识点Intent的用途Intent是一个消息传递对象。使用Intent向系统请求操作,主要包括: 启动Activity 启动Service 发送Broadcast 下图展示了启动Activity时,Intent在两个
2019-12-03
下一篇 
okhttp实现token验证 okhttp实现token验证
okhttp实现token验证前言公司目前的项目使用了token来验证用户。登陆之后会返回最新的access token,后续在每次请求API时,服务端会返回最新的access token,客户端进行保存。若一段时间内(假定是7天)没有进行
  目录