Android 6.0运行时权限简析及最佳实践

wubo_1989 8年前
   <h3>1、前言</h3>    <p>从Android 6.0(API 23)开始,对系统权限做了很大的改变。在之前用户安装APP前,只是把APP需要使用的权限列出来给用户告知一下,APP安装后都可以访问这些权限。从6.0开始,一些敏感权限,需要在使用时动态申请,并且用户可以选择拒绝授权访问这些权限,已授予过的权限,用户也可以去APP设置页面去关闭授权。这对用户来说提高了安全性,可以防止一些应用恶意访问用户数据,但是对于开发来说,也增加了不少工作量,这块不做适配处理的话,APP在访问权限的时候会容易crash。</p>    <h3>2、权限等级和权限组</h3>    <p>权限主要分为normal、dangerous、signature和signatureOrSystem四个等级,常规情况下我们只需要了解前两种,即正常权限和危险权限。</p>    <p>2.1、正常权限</p>    <p>正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。应用声明其需要正常权限,系统会自动授予该权限。例如设置时区,只要应用声明过权限,系统就直接授予应用此权限。下面是截止到API 23的 <a href="/misc/goto?guid=4959737316722881936" rel="nofollow,noindex">普通权限</a> (国内或许不能访问访问)</p>    <table>     <thead>      <tr>       <th> </th>       <th> </th>      </tr>     </thead>     <tbody>      <tr>       <td>ACCESS_LOCATION_EXTRA_COMMANDS</td>       <td>ACCESS_NETWORK_STATE</td>      </tr>      <tr>       <td>ACCESS_NOTIFICATION_POLICY</td>       <td>ACCESS_WIFI_STATE</td>      </tr>      <tr>       <td>BLUETOOTH</td>       <td>BLUETOOTH_ADMIN</td>      </tr>      <tr>       <td>BROADCAST_STICKY</td>       <td>CHANGE_NETWORK_STATE</td>      </tr>      <tr>       <td>CHANGE_WIFI_MULTICAST_STATE</td>       <td>CHANGE_WIFI_STATE</td>      </tr>      <tr>       <td>DISABLE_KEYGUARD</td>       <td>EXPAND_STATUS_BAR</td>      </tr>      <tr>       <td>GET_PACKAGE_SIZE</td>       <td>INSTALL_SHORTCUT</td>      </tr>      <tr>       <td>INTERNET</td>       <td>KILL_BACKGROUND_PROCESSES</td>      </tr>      <tr>       <td>MODIFY_AUDIO_SETTINGS</td>       <td>NFC</td>      </tr>      <tr>       <td>READ_SYNC_SETTINGS</td>       <td>READ_SYNC_STATS</td>      </tr>      <tr>       <td>RECEIVE_BOOT_COMPLETED</td>       <td>REORDER_TASKS</td>      </tr>      <tr>       <td>REQUEST_IGNORE_BATTERY_OPTIMIZATIONS</td>       <td>REQUEST_INSTALL_PACKAGES</td>      </tr>      <tr>       <td>SET_ALARM</td>       <td>SET_TIME_ZONE</td>      </tr>      <tr>       <td>SET_WALLPAPER</td>       <td>SET_WALLPAPER_HINTS</td>      </tr>      <tr>       <td>TRANSMIT_IR</td>       <td>UNINSTALL_SHORTCUT</td>      </tr>      <tr>       <td>USE_FINGERPRINT</td>       <td>VIBRATE</td>      </tr>      <tr>       <td>WAKE_LOCK</td>       <td>WRITE_SYNC_SETTINGS</td>      </tr>     </tbody>    </table>    <p>2.2、危险权限</p>    <p>危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如读取用户联系人,在6.0以上系统中,需要在运行时明确向用户申请权限。</p>    <p>2.3、权限组</p>    <p>系统根据权限用途又定义了权限组,每个权限都可属于一个权限组,每个权限组可以包含多个权限。例如联系人权限组,包含读取联系人、修改联系人和获取账户三个权限。</p>    <ul>     <li>如果应用申请访问一个危险权限,而此应用目前没有对应的权限组内的任何权限,系统会弹窗提示用户要访问的权限组(注意不是权限)。例如无论你申请READ_CONTACTS还是WRITE_CONTACTS,都是提示应用需要访问联系人信息。</li>     <li>如果用户申请访问一个危险权限,而应用已经授权同权限组的其他权限,则系统会直接授权,不会再与用户有交互。例如应用已经请求并授予了READ_CONTACTS权限,那么当应用申请WRITE_CONTACTS时,系统会立即授予该权限。下面为危险权限和权限组:</li>    </ul>    <table>     <thead>      <tr>       <th>权限组</th>       <th>权限</th>      </tr>     </thead>     <tbody>      <tr>       <td>CALENDAR</td>       <td>READ_CALENDAR<br> WRITE_CALENDAR</td>      </tr>      <tr>       <td>CAMERA</td>       <td>CAMERA</td>      </tr>      <tr>       <td>CONTACTS</td>       <td>READ_CONTACTS<br> WRITE_CONTACTS<br> GET_ACCOUNTS</td>      </tr>      <tr>       <td>LOCATION</td>       <td>ACCESS_FINE_LOCATION<br> ACCESS_COARSE_LOCATION</td>      </tr>      <tr>       <td>MICROPHONE</td>       <td>RECORD_AUDIO</td>      </tr>      <tr>       <td>PHONE</td>       <td>READ_PHONE_STATE<br> CALL_PHONE<br> READ_CALL_LOG<br> WRITE_CALL_LOG<br> ADD_VOICEMAIL<br> USE_SIP<br> PROCESS_OUTGOING_CALLS</td>      </tr>      <tr>       <td>SENSORS</td>       <td>BODY_SENSORS</td>      </tr>      <tr>       <td>SMS</td>       <td>SEND_SMS<br> RECEIVE_SMS<br> READ_SMS<br> RECEIVE_WAP_PUSH<br> RECEIVE_MMS</td>      </tr>      <tr>       <td>STORAGE</td>       <td>READ_EXTERNAL_STORAGE<br> WRITE_EXTERNAL_STORAGE</td>      </tr>     </tbody>    </table>    <h3>3、运行时请求权限</h3>    <p>关于运行时请求权限,官网介绍的很清楚,也有很多其他文章介绍,这里只是简单罗列一下。</p>    <p>3.1、检查权限</p>    <p>应用每次需要危险权限时,都要判断应用目前是否有该权限。兼容库中已经做了封装,只需要通过下面代码即可:</p>    <pre>  <code class="language-java">int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,          Manifest.permission.WRITE_CALENDAR);</code></pre>    <p>如果有权限则返回PackageManager.PERMISSION_GRANTED,否则返回PackageManager。PERMISSION_DENIED。</p>    <p>3.2、请求权限</p>    <p>当应用需要某个权限时,可以申请获取权限,这时会有弹出一个系统标准Dialog提示申请权限,此Diolog不能定制,用户同意或者拒绝后会通过方法onRequestPermissionsResult()返回结果。当用户拒绝过此权限申请时,再次申请Dialog上可以勾选不再提示,这种情况下,以后再申请权限不会弹Dialog直接返回拒绝。所以一些依赖某些敏感权限的应用,需要自己去处理,向用户解释 为什么需要此权限,说服用户授予权限。请求权限代码如下:</p>    <pre>  <code class="language-java">ActivityCompat.requestPermissions(thisActivity,                  new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);</code></pre>    <p>3.3、处理权限请求响应</p>    <p>当用户处理权限请求后,系统会回调申请权限的Activity的onRequestPermissionsResult()方法,只需要覆盖此方法,就能获得返回结果</p>    <pre>  <code class="language-java">@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;          }      }  }</code></pre>    <p>3.4、考虑使用intent</p>    <p>有很多权限操作可以考虑调用其他应用,这样的话当前应用就不需要申请权限。例如想要获取相机照相,可以通过ACTION_IMAGE_CAPTURE唤起相机应用去完成,相机应用会把照片返回。同样拨打电话、访问联系人,都可以考虑使用类似方法。相比较其他应用,这类专门的应用做一些操作更容易让用户接受。</p>    <h3>4、 关于国产机6.0以下系统</h3>    <p>部分国产厂商定制了系统(例如小米、华为),在6.0以下系统就可以单独控制权限。但是通常它们处理的不够彻底,代码中判断是否授权的时候,返回的是已经授权。而真正去做操作的时候,却会因为没有权限导致应用crash。例如我曾遇到过的场景:</p>    <ul>     <li>访问联系人,可以拿到Cursor对象,但是cursor.moveToFirst()会返回false。</li>     <li>访问摄像头时,获取到的Camera对象为空</li>    </ul>    <p>所以在低版本手机上,不要以为拥有系统授权就万事大吉了,一定要多加条件判读和测试。</p>    <h3>5、PermissionGrantor,一行代码搞定动态权限申请。</h3>    <p>上面运行时请求权限中,我们看到了权限申请依赖于Activity和Fragment,和startActivityForResult()方法的使用类似,必须依赖于Activity和Fragment的回调方法。正常情况下还能满足需求,可是当申请的权限的代码在一个独立的模块中时,例如我封装了一个UI控件,控件中某个操作需要申请权限,或者项目采用了MVVM框架,需要在一些view类或者model类中申请权限,这是处理起来就会很麻烦。</p>    <p>我在自己代码中就遇到类似情况,后面我采用了使用一个单独的Activity来申请权限,通过回调的方式通知业务层授权结果。感觉使用起来挺方便,就把它放到maven仓库中了,项目中通过加入如下依赖即可。</p>    <pre>  <code class="language-java">compile 'com.github.dfqin:grantor:1.0.1'</code></pre>    <p>5.1 PermissionGrantor使用</p>    <p>申请权限很简单,只需要下面一句话即可。</p>    <pre>  <code class="language-java">PermissionsUtil.requestPermission(Activity activity, PermissionListener listener,       String[] permissions);</code></pre>    <p>下面是一个申请摄像头的例子:</p>    <pre>  <code class="language-java">private void requestCemera() {          if (PermissionsUtil.hasPermission(this, Manifest.permission.CAMERA)) {              //有访问摄像头的权限          } else {              PermissionsUtil.requestPermission(this, new PermissionListener() {                  @Override                  public void permissionGranted(@NonNull String[] permissions) {                     //用户授予了访问摄像头的权限                  }                    @Override                  public void permissionDenied(@NonNull String[] permissions) {                      //用户拒绝了访问摄像头的申请                  }              }, new String[]{Manifest.permission.CAMERA});          }      }</code></pre>    <p>PermissionsUtil.requestPermission还有两个重载的实现。用户拒绝授权时,会有一个默认Dialog提示用户开通权限。这个Dialog的内容可以定制,也可以不显示此Dialog,详情见 <a href="/misc/goto?guid=4959737316828823383" rel="nofollow,noindex">Github</a> 。有什么问题欢迎讨论交流。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/289dd11b68cc5e7a6c2ecea4121e1fe2.gif"></p>    <p style="text-align:center">grant1.gif</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6988987221cb1206ed4f82961c638443.gif"></p>    <p style="text-align:center">grant2.gif</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/602ca839f5eff0e2ced43de7d9b1eca0.gif"></p>    <p style="text-align:center">grant3.gif</p>    <h3>6、参考</h3>    <ul>     <li><a href="/misc/goto?guid=4959737316912006742" rel="nofollow,noindex">http://blog.csdn.net/qiujuer/article/details/44195131</a></li>     <li><a href="http://2dxgujun.com/post/2015/02/11/Publish-AAR-to-Maven-Central-with-Gradle.html?utm_source=tuicool&utm_medium=referral" rel="nofollow,noindex">http://2dxgujun.com/post/2015/02/11/Publish-AAR-to-Maven-Central-with-Gradle.html?utm_source=tuicool&utm_medium=referral</a></li>     <li><a href="/misc/goto?guid=4959667390948710569" rel="nofollow,noindex">http://www.jianshu.com/p/dbe4d37731e6</a></li>    </ul>    <p> </p>    <p>来自:http://www.jianshu.com/p/cdcbd3038902</p>    <p> </p>