Android 6.0运行时请求权限
aikf0112
8年前
<h3><strong>Android权限简介</strong></h3> <p>从Android 6.0开始,部分 <strong>危险权限</strong> 需要在运行时用户动态授权,因为一个Android应用默认情况下是不拥有任何权限的。在开发的时候,我们会在AndroidManifest.xml中静态地声明相应的权限,如果没有声明该权限却使用了相应的权限,程序会崩溃,抛出异常,例如,如果没有在程序中声明网络权限当我们使用网络的时候,就会抛出如下异常,而且一般不会try catch该异常:</p> <p>Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/a4b070568e0f971ae5b1b3614ebcd1a3.png"></p> <p>但是在Android6.0上面部分权限不但要求开发者在使用之前在AndroidManifest.xml声明,还需要用户在使用的时候进行授权才可以使用,例如用户想使用拍照上传图片,虽然已经声明了使用相机的权限,如果用户拒绝使用相机权限,还是会抛出异常:</p> <p>Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE cmp=com.android.camera/.Camera }…</p> <p>这时候如果程序员没有对Android6.0手机进行额外权限的处理,程序就会直接崩掉了,给用户带来很不好的体验。</p> <p>刚刚看到一篇新闻,《Android 7.1.1将于12月5日正式登陆Nexus设备》,Android7.x的手机都要问世了,针对Android M以上的手机动态权限确实需要开发者认真研究一下了。</p> <p>正常权限和危险权限</p> <p>事实上Android系统对权限声明有四个等级,主要通过protectionLevel来设置。</p> <pre> <code class="language-java"><permissionandroid:description="string resource" android:icon="drawable resource" android:label="string resource" android:name="string" android:permissionGroup="string" android:protectionLevel=["normal" | "dangerous" | "signature" | "signatureOrSystem"]/> </code></pre> <p>签名相关的两个权限并不常用,在开发中我们真正需要针对Android6.0以上系统进行特殊处理的都是危险权限。</p> <p>系统权限分为两类: 正常权限 和 <em>危险权限</em> :</p> <ul> <li>正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。</li> <li>危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。</li> </ul> <p>之所以在这里列出来 <em>正</em>常<em>权限</em> 和 危险权限 ,主要就是因为Android6.0在这里做了一个分割线:</p> <ul> <li>如果设备运行的是 Android 5.1 或更低版本, <strong>或者</strong> 应用的目标 SDK 为 22 或更低:如果您在清单中列出了危险权限,则用户必须在安装应用时授予此权限;如果他们不授予此权限,系统根本不会安装应用。</li> <li>如果设备运行的是 Android 6.0 或更高版本, <strong>或者</strong> 应用的目标 SDK 为 23 或更高:应用必须在清单中列出权限, <em>并且</em> 它必须在运行时请求其需要的每项危险权限。用户可以授予或拒绝每项权限,且即使用户拒绝权限请求,应用仍可以继续运行有限的功能。</li> </ul> <p>权限组</p> <p>所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:</p> <ul> <li>如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。</li> <li>如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS ,系统将立即授予该权限。</li> </ul> <p>任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只告诉用户应用需要的权限组,而不告知具体权限。</p> <table> <tbody> <tr> <th scope="col">权限组</th> <th scope="col">权限</th> </tr> <tr> <td><a href="/misc/goto?guid=4959726756695840118">CALENDAR</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726756777036233">READ_CALENDAR</a></li> </ul> <ul> <li><a href="/misc/goto?guid=4959726756858343472">WRITE_CALENDAR</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726756944429674">CAMERA</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726757021551370">CAMERA</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726757101667604">CONTACTS</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726757192609109">READ_CONTACTS</a></li> <li><a href="/misc/goto?guid=4959726757269342054">WRITE_CONTACTS</a></li> <li><a href="/misc/goto?guid=4959726757348598727">GET_ACCOUNTS</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726757434533587">LOCATION</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726757514635518">ACCESS_FINE_LOCATION</a></li> <li><a href="/misc/goto?guid=4959726757603590211">ACCESS_COARSE_LOCATION</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726757676063269">MICROPHONE</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726757755873769">RECORD_AUDIO</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726757840841995">PHONE</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726757925952699">READ_PHONE_STATE</a></li> <li><a href="/misc/goto?guid=4959726757998615245">CALL_PHONE</a></li> <li><a href="/misc/goto?guid=4959726758090683138">READ_CALL_LOG</a></li> <li><a href="/misc/goto?guid=4959726758171195488">WRITE_CALL_LOG</a></li> <li><a href="/misc/goto?guid=4959726758256182929">ADD_VOICEMAIL</a></li> <li><a href="/misc/goto?guid=4959726758336453544">USE_SIP</a></li> <li><a href="/misc/goto?guid=4959726758420577878">PROCESS_OUTGOING_CALLS</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726758509534707">SENSORS</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726758592962928">BODY_SENSORS</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726758678443457">SMS</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726758766485257">SEND_SMS</a></li> <li><a href="/misc/goto?guid=4959726758860582355">RECEIVE_SMS</a></li> <li><a href="/misc/goto?guid=4959726758946280797">READ_SMS</a></li> <li><a href="/misc/goto?guid=4959726759028788896">RECEIVE_WAP_PUSH</a></li> <li><a href="/misc/goto?guid=4959726759112439332">RECEIVE_MMS</a></li> </ul> </td> </tr> <tr> <td><a href="/misc/goto?guid=4959726759195236151">STORAGE</a></td> <td> <ul> <li><a href="/misc/goto?guid=4959726759287730661">READ_EXTERNAL_STORAGE</a></li> <li><a href="/misc/goto?guid=4959726759370820076">WRITE_EXTERNAL_STORAGE</a></li> </ul> </td> </tr> </tbody> </table> <h3><strong>动态权限处理逻辑</strong></h3> <p>Android6.0动态权限处理一般有三个步骤:</p> <ol> <li>检查权限;</li> <li>请求权限;</li> <li>处理请求相应回调。</li> </ol> <p>虽然Android6.0已经新增了针对动态权限的相关实现方法,但是为了使用方便我们使用support支持看,这样免去了判断Android版本的繁琐,针对Activity和Fragment支持库都提供了相应的方法。</p> <pre> <code class="language-java">int ContextCompat.checkSelfPermission(Contextcontext, String permission) void ActivityCompat.requestPermissions(Activityactivity, String[] permissions, int requestCode) boolean ActivityCompat.shouldShowRequestPermissionRationale(Activityactivity, String permission) void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) //fragment void requestPermissions(String[] permissions, int requestCode) </code></pre> <p>检查权限</p> <p>当我们开发的应用在操作是需要一个危险的权限,在执行操作时应该检查一下是否具有该权限。检查是否具有某项权限,可以调用 ContextCompat.checkSelfPermission() 方法。如果应用具有该权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。</p> <p>下面是一个调用相机的权限:</p> <pre> <code class="language-java">int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); if (permission == PackageManager.PERMISSION_GRANTED) { //执行拍照 } else {//请求权限 ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA }, PHOTO); } </code></pre> <p>请求权限</p> <p>在第一步中我们检查权限,如果没有相应的权限即去请求权限,请求权限时,我们可以同时请求多个权限,也可以单个权限执行请求。调用请求权限时系统会弹出来一个标准的Android对话框,开发者不能进行自定义。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3beecd7a725cd949afd5859c991f3323.png"></p> <p>当我们同时请求多个权限时,会在系统单个对话框中顺序显示多个权限授权操作,下面代码是一次请求多个权限:</p> <pre> <code class="language-java">String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE }; ActivityCompat.requestPermissions(this, permissions, PHOTO); </code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/5c4e960fa8aae38afa9ebc9fe37b37dd.gif"></p> <p>处理请求相应回调</p> <p>当我们响应系统请求权限对话框时,系统将调用应用的 onRequestPermissionsResult() 方法,在回调中继续处理我们的逻辑操作,如果用户拒绝了我们要求的权限,我们可以在这里进行必要的解释,告诉用户为什么需要此权限。当然了不是每条权限都需要解释,太多的解释反而会降低用户体验。</p> <p>为了帮助开发者提供需要解释的情形,Android提供了 shouldShowRequestPermissionRationale()方法,部分手机上面该方法不起作用,主要是由于Android手机深度定制导致的。</p> <ul> <li>如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。</li> <li>如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。</li> <li>如果设备规范禁止应用具有该权限,此方法也会返回 false。</li> </ul> <p style="text-align:center"><img src="https://simg.open-open.com/show/f4f80802231f831b8bc6f7566caa1181.png"></p> <p>下面是一个对执行拍照请求权限后的回调:</p> <pre> <code class="language-java">public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case PHOTO: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //拍照 } else { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.CAMERA)) { //提供必要的解释,为什么需要该权限 }else{ Toast.makeText(MainActivity.this, "授权失败!", 0).show(); } } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); break; } } </code></pre> <h3>权限拒绝后跳转至应用的设置界面</h3> <p>在动态请求权限处理时,有一种简单粗暴的方式,我们一下将应用中涉及到的危险权限一次性全部请求,如果用户不同意,直接提示用户需要相应的权限才可以使用该应用,然后跳转到应用的设置界面,当然了由于Android手机种类多样,下下面这种方法不一定总是可行,不过在小米、魅族、华为和三星手机上验证了部分手机都是可行的。</p> <pre> <code class="language-java">IntentlocalIntent = new Intent(); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); localIntent.setData(Uri.fromParts("package", getPackageName(), null)); startActivity(localIntent); </code></pre> <p>在Android6.0之前手机设置界面基本是这样的,所有申请的权限如下排列:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/aec629a48204ec94ec7b35079173cb2b.gif"></p> <p>在Android6.0之后新增了权限相关的设置信息:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1f92010848051bfdd642f6c436ad139b.gif"></p> <h3><strong>Fragment处理权限请求回调</strong></h3> <p>在使用Fragment处理回调是不要使用ActivityCompat.requestPermissions方法,虽然使用该方法不会报错,直接使用Fragment中的requestPermissions方法即可,否则就会将回调返回到相应的Activity。</p> <p>如果Fragment嵌套了子Fragment,在子Fragment中使用requestPermissions方 法,onRequestPermissionsResult不会回调回来,建议使用 getParentFragment().requestPermissions方法,这个方法会回调到父Fragment中的onRequestPermissionsResult,加入以下代码可以把回调透传到子Fragment。</p> <pre> <code class="language-java">public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); List<Fragment> fragments = getChildFragmentManager().getFragments(); if (fragments != null) { for (Fragmentfragment : fragments) { if (fragment != null) { fragment.onRequestPermissionsResult(requestCode,permissions,grantResults); } } } } </code></pre> <h3><strong>权限的最佳做法</strong></h3> <p>这里所说的最佳做法来自 <a href="/misc/goto?guid=4959726759470250030" rel="nofollow,noindex">https://developer.android.com/training/permissions/best-practices.html</a></p> <ul> <li>考虑使用 intent</li> <li>仅要求您需要的权限</li> <li>不要让用户感到无所适从</li> <li>测试两种权限模式</li> <li> </li> </ul> <p>另外可以参考google在github上的动态请求权限的示例或者使用第三方的库来处理权限:</p> <p>android-RuntimePermissions: <a href="/misc/goto?guid=4959646195797946867" rel="nofollow,noindex">https://github.com/googlesamples/android-RuntimePermissions</a></p> <p>easypermissions: <a href="/misc/goto?guid=4958975973197956012" rel="nofollow,noindex">https://github.com/googlesamples/easypermissions</a></p> <p>PermissionsDispatcher: <a href="/misc/goto?guid=4959643849338359243" rel="nofollow,noindex">https://github.com/hotchemi/PermissionsDispatcher</a></p> <p>RxPermissions: <a href="/misc/goto?guid=4958969142888824489" rel="nofollow,noindex">https://github.com/tbruyelle/RxPermissions</a></p> <p>Grant: <a href="/misc/goto?guid=4959646195708109067" rel="nofollow,noindex">https://github.com/anthonycr/Grant</a></p> <h3><strong>参考资料</strong></h3> <p><a href="/misc/goto?guid=4959726759706517073" rel="nofollow,noindex">Android M Permission 运行时权限 学习笔记</a></p> <p><a href="/misc/goto?guid=4959726759789215929" rel="nofollow,noindex">聊一聊Android 6.0的运行时权限</a></p> <p><a href="/misc/goto?guid=4959726759874606086" rel="nofollow,noindex">权限最佳做法</a></p> <p><a href="/misc/goto?guid=4959726759963970184" rel="nofollow,noindex">系统权限</a></p> <p><a href="/misc/goto?guid=4959676719568383091" rel="nofollow,noindex">在运行时请求权限</a></p> <p><a href="/misc/goto?guid=4959726760080862194" rel="nofollow,noindex">安卓6.0新特性在Fragment申请运行时权限</a></p> <p><a href="/misc/goto?guid=4959726760162062738" rel="nofollow,noindex">android 6.0权限全面详细分析和解决方案</a></p> <p><a href="/misc/goto?guid=4959667198607137103" rel="nofollow,noindex">Android 6.0 运行时权限处理</a></p> <p> </p> <p>来自:http://www.sunnyang.com/598.html</p> <p> </p>