Android6.0 权限适配

yaokaizi 8年前
   <h2>前言</h2>    <p>现在谈论Android权限适配可能有点没必要,因为网上关于权限适配的文章很多,搜一下Android6.0权限适配关键词能搜到一堆文章,而且很多写的还很不错。不过自己想了想还是总结一下,因为那些文章都是别人的,不是自己的,之前一直想总结一下,但是一直没做,今天就简单记录一下,方便以后查阅,也对Android6.0的权限机制再次进行一次全面的认识。</p>    <p>从Android M开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。这样更友好的让用户选择,当真正需要权限的时候再去申请权限,而不是Android M之前在安装时一下子去申请。</p>    <h2>正常权限</h2>    <p>正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。而不需要我们去请求权限。</p>    <pre>  <code class="language-java">ACCESS_LOCATION_EXTRA_COMMANDS    ACCESS_NETWORK_STATE  ACCESS_NOTIFICATION_POLICY  ACCESS_WIFI_STATE  BLUETOOTH  BLUETOOTH_ADMIN  BROADCAST_STICKY  CHANGE_NETWORK_STATE  CHANGE_WIFI_MULTICAST_STATE  CHANGE_WIFI_STATE  DISABLE_KEYGUARD  EXPAND_STATUS_BAR  GET_PACKAGE_SIZE  INSTALL_SHORTCUT  INTERNET  KILL_BACKGROUND_PROCESSES  MODIFY_AUDIO_SETTINGS  NFC  READ_SYNC_SETTINGS  READ_SYNC_STATS  RECEIVE_BOOT_COMPLETED  REORDER_TASKS  REQUEST_IGNORE_BATTERY_OPTIMIZATIONS  REQUEST_INSTALL_PACKAGES  SET_ALARM  SET_TIME_ZONE  SET_WALLPAPER  SET_WALLPAPER_HINTS  TRANSMIT_IR  UNINSTALL_SHORTCUT  USE_FINGERPRINT  VIBRATE  WAKE_LOCK  WRITE_SYNC_SETTINGS</code></pre>    <h2>危险权限</h2>    <p>危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。</p>    <p>在危险权限中,我们需要了解一个权限组的概念,所有危险的 Android 系统权限都属于权限组,如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有拥有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。</p>    <p>例如我们需要读取获取手机卡imsi,此时需要请求权限READ_PHONE_STATE,发现此时提示框也展示了请求打电话权限。( <strong>系统只告诉用户应用需要的权限组,而不告知具体权限</strong> )其实READ_PHONE_STATE和打电话权限CALL_PHONE都属于一个权限组PHONE,如果我们此时允许了权限,那么下次再其他地方使用了打电话权限时系统将立即授予该权限。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e9f103d4cef7f4a171ce8957b17a5cb8.png"></p>    <p>这里写图片描述</p>    <p><em>注:任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。</em></p>    <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>     </tbody>    </table>    <h2>权限请求API</h2>    <pre>  <code class="language-java">/**       * 确定权限是否已经被授予       * @param permission 被检测权限的名字.       * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 如果权限被授予,       * {@link android.content.pm.PackageManager#PERMISSION_DENIED} 如果权限被拒绝返回值.       */      public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)             /**       * 是否显示自定义UI提示用户       * 华为手机测试 第一次使用时返回false       * 如果拒绝返回true       * 如果拒绝并点击不在提醒返回false       * 已经同意过权限,但在设置拒绝此时返回true       * 没有同意过权限,在设置中开启并拒绝权限返回false       * @param activity   请求权限Activity.       * @param permission 需要请求的权限.       * @return 是否显示自定义对话框提示用户.       */      public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,                                                                 @NonNull String permission)              /**       * 给应用申请权限,申请的权限必须在manifest文件注册,正常权限在安装时自动被授权,不需要使用此方法请求权限       * 请求之后会弹出系统提示框,供我们选择是拒绝还是允许,点击后       * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(       * int, String[], int[])} 方法将会被回调,       * @param activity 请求权限的Activity.       * @param permissions 需要请求的权限.       * @param requestCode 指定一个请求码,用于区别返回结果       *       */      public static void requestPermissions(final @NonNull Activity activity,                                            final @NonNull String[] permissions, final int requestCode)            /**       * 调用requestPermissions方法请求权限的回调       *需要注意的是可能请求的权限与用户互动中断;正在这种情况下回调将接收一个空的permissions和grantResults数组       * @param permissions 请求的权限. 不为null,长度可能为0.       * @param grantResults 请求权限的结果PERMISSION_GRANTED表示权限被允许,PERMISSION_DENIED表示权限被拒绝       */      void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,                                      @NonNull int[] grantResults)</code></pre>    <p>需要注意的是,对于如果在Activity中请求权限则可使用上面API ActivityCompat类,如果在Frament请求权限则,需要使用Fragment类中的对应方法,否则回调会有问题。</p>    <h2>简单封装</h2>    <pre>  <code class="language-java">/**       * 判断是否具备所有权限       *       * @param permissions 所有权限       * @return true 具有所有权限  false没有具有所有权限,此时包含未授予的权限       */      public static boolean isHasPermissions(String... permissions) {          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)              return true;          for (String permission : permissions) {              if (!isHasPermission(permission))                  return false;          }          return true;      }        /**       * 判断该权限是否已经被授予       *       * @param permission       * @return true 已经授予该权限 ,false未授予该权限       */      private static boolean isHasPermission(String permission) {          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)              return true;          return ContextCompat.checkSelfPermission(MyApplication.getAppContext(), permission) == PackageManager.PERMISSION_GRANTED;      }        /**       * 请求权限,经测试发现TabActivity管理Activity时,在Activity中请求权限时需要传入父Activity对象,即TabActivity对象       * 并在TabActivity管理Activity中重写onRequestPermissionsResult并分发到子Activity,否则回调不执行  。TabActivity回调中  调用getLocalActivityManager().getCurrentActivity().onRequestPermissionsResult(requestCode, permissions, grantResults);分发到子Activity         *        *       * @param object      Activity or Fragment       * @param requestCode 请求码       * @param permissions 请求权限       */      public static void requestPermissions(Object object, int requestCode, String... permissions) {          ArrayList<String> arrayList = new ArrayList<>();          for (String permission : permissions) {              if (!isHasPermissions(permission)) {                  arrayList.add(permission);              }          }          if (arrayList.size() > 0) {              if (object instanceof Activity) {                  Activity activity = (Activity) object;                  Activity activity1 = activity.getParent() != null && activity.getParent() instanceof TabActivity ? activity.getParent() : activity;                  ActivityCompat.requestPermissions(activity1, arrayList.toArray(new String[]{}), requestCode);              } else if (object instanceof Fragment) {                  Fragment fragment = (Fragment) object;                  //当Fragment嵌套Fragment时使用getParentFragment(),然后在父Fragment进行分发,否则回调不执行                  Fragment fragment1 = fragment.getParentFragment() != null ? fragment.getParentFragment() : fragment;                  fragment1.requestPermissions(arrayList.toArray(new String[]{}), requestCode);              } else {                  throw new RuntimeException("the object must be Activity or Fragment");              }          }      }</code></pre>    <p>如果想展示自定义UI友好的提示用户申请该权限的原因,则需要使用shouldShowRequestPermissionRationale方法,简要封装如下</p>    <pre>  <code class="language-java">public static boolean shouldShowRequestPermissionRationale(@NonNull Object object, String... permissions) {          for (String permission : permissions) {              if (object instanceof Activity) {                  if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, permission)) {                      return true;                  }              } else if (object instanceof Fragment) {                  ((Fragment) object).shouldShowRequestPermissionRationale(permission);              } else {                  throw new RuntimeException("the object must be Activity or Fragment");              }              }          return false;      }     /**       * 二次申请权限时,弹出自定义提示对话框       *       * @param activity       * @param message       * @param iPermissionRequest       */      public static void showDialog(Activity activity, String message, final IPermissionRequest iPermissionRequest) {          new AlertDialog.Builder(activity)                  .setPositiveButton("允许", new DialogInterface.OnClickListener() {                      @Override                      public void onClick(@NonNull DialogInterface dialog, int which) {                          iPermissionRequest.agree();                      }                  })                  .setNegativeButton("不允许", new DialogInterface.OnClickListener() {                      @Override                      public void onClick(@NonNull DialogInterface dialog, int which) {                          iPermissionRequest.refuse();                      }                  })                  .setCancelable(false)                  .setMessage(message)                  .show();      }</code></pre>    <p>弹出对话框后,点击了拒绝或者允许后,给一个回调,方便进行不同的处理,当然如果统一处理的话,就不需要写接口,直接在上述点击允许的时候请求权限,点击不允许的时候,显示一个Toast再次做下权限拒绝提示。</p>    <p>接口方法</p>    <pre>  <code class="language-java">public interface IPermissionRequest {      void agree();      void refuse();  }</code></pre>    <h2>特殊权限</h2>    <p>有许多权限其行为方式与正常权限及危险权限都不同。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并且发送请求用户授权的 intent( <strong>注意特殊权限和危险权限请求方式不一样</strong> )。系统将向用户显示详细管理屏幕,以响应该 intent。</p>    <h2>请求WRITE_SETTINGS权限</h2>    <pre>  <code class="language-java">/**       * 测试请求WRITE_SETTINGS权限       */      @OnClick(R.id.request_write_setting)      @TargetApi(android.os.Build.VERSION_CODES.M)      public void requestWriteSetting() {          if (!Settings.System.canWrite(this)) {              Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,                      Uri.parse("package:" + getPackageName()));              startActivityForResult(intent, requestCodeWriteSetting);          } else {              Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 已经被授权", Toast.LENGTH_SHORT).show();          }      }          @TargetApi(Build.VERSION_CODES.M)      private void showToast() {          if (Settings.System.canWrite(this)) {              //检查返回结果              Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 被授权", Toast.LENGTH_SHORT).show();          } else {              Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 没有被授权", Toast.LENGTH_SHORT).show();          }      }     @Override      protected void onActivityResult(int requestCode, int resultCode, Intent data) {          super.onActivityResult(requestCode, resultCode, data);          if (requestCode == requestCodeWriteSetting) {              showToast();          }else if(requestCode==requestCodeAlertWindow){              showToastAlerterWindow();          }      }</code></pre>    <h2>请求SYSTEM_ALERT_WINDOW权限</h2>    <pre>  <code class="language-java">/**       * 测试请求SYSTEM_ALERT_WINDOW权限       */      @OnClick(R.id.request_alert_window)      @TargetApi(android.os.Build.VERSION_CODES.M)      public void requestAlertWindow() {          if (!Settings.canDrawOverlays(this)) {              Intent intent = new Intent(Settings. ACTION_MANAGE_OVERLAY_PERMISSION,                      Uri.parse("package:" + getPackageName()));              startActivityForResult(intent, requestCodeAlertWindow);          } else {              Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 已经被授权", Toast.LENGTH_SHORT).show();          }      }   @TargetApi(Build.VERSION_CODES.M)      private void showToastAlerterWindow() {          if (Settings.System.canWrite(this)) {              //检查返回结果              Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 被授权", Toast.LENGTH_SHORT).show();          } else {              Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 没有被授权", Toast.LENGTH_SHORT).show();          }      }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/879fa01b653dcbbda91a7f8d511f8e86.png"></p>    <p>WRITE_SETTINGS权限设置界面</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a5073eaa4a54dd2f30594f3a98835de7.png"></p>    <p>SYSTEM_ALERT_WINDOW权限设置界面</p>    <p>注意:权限必须在清单文件中声明,否则进入上面界面时开关是不可点击的灰色。</p>    <h2>打开权限设置界面</h2>    <p>在上面危险权限申请中,如果用户拒绝了权限,并且选中永不提醒,那么下次请求权限时直接执行onRequestPermissionsResult回调,并且返回状态是权限被拒绝状态,那么若想授予权限,必须去手机的权限管理中设置,如果用户去手机里找是不是很麻烦,况且一步人不知道设置权限的地方在哪,那么为了程序的体验更好,我们可以在我们的应用中引导用户跳转到设置权限的界面。实现代码如下</p>    <pre>  <code class="language-java">/**       * 打开应用权限设置界面       */      @OnClick(R.id.open_permission_setting)      public void requestOpenPermissionSetting() {          Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);          // Uri uri = Uri.fromParts("package", getPackageName(), null);          Uri uri1=Uri.parse("package:" + getPackageName());          intent.setData(uri1);          startActivity(intent);      }</code></pre>    <p>介绍到此就结束了,水平有限若有问题请指出,Hava a wonderful day.</p>    <p> </p>    <p> </p>    <p>来自:https://juejin.im/post/58f5e36cb123db2fa2b3dfb3</p>    <p> </p>