Android6.0 权限适配,比你想的还要简单(实践篇)

VeldaCavana 8年前
   <h3>前言</h3>    <blockquote>     <p>自从升级到Android M以来,最大的改变就是增加了运行时权限RuntimePermission,6.0以上的系统如果没有做适配,运行了targetSDK=23的App时就会报权限错误;当然如果你还没准备好适配权限,把targetSDK设置成小于23就ok了,不过适配是迟早的。</p>    </blockquote>    <blockquote>        </blockquote>    <h3>运行时权限</h3>    <blockquote>     <p>谷歌官方将权限分为了两类,一个是正常权限(Normal Permissions),这类权限不涉及用户隐私,是不需要用户进行授权的,比如访问网络,手机震动等。还有一类是危险权限(Dangerous Permissions),一般是涉及到用户隐私的,需要用户进行授权,比如操作SD卡的写入,相机,录音等。</p>    </blockquote>    <blockquote>     <p>我们所要关注的就是危险权限,由上图可以看到这些权限被分为不同的权限组(PermissionGroup),这里需要说明一下,当一个权限组里的任一权限被授权,这个组里的其他权限也都会被授权,比如:READ_EXTERNAL_STORAGE这个读SD卡的权限被授权了,这时候WRITE_EXTERNAL_STORAGE也同时被授权。</p>    </blockquote>    <h3>预览</h3>    <p>我们要在保证权限适配的同时,保证代码的整洁和可读,最终我们实现的效果是如下</p>    <pre>  <code class="language-java">requestCameraPermission(new PermissionHandler() {              @Override              public void onGranted() {                  Intent intent = new Intent(); //调用照相机                  intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);                  startActivity(intent);              }                @Override              public void onDenied() {                }          });</code></pre>    <h3>实践</h3>    <pre>  <code class="language-java">关于运行时权限的理论知识就不多说了,网上一搜也是一大把,我们这里着重讲如何实践。当你准备做6.0权限适配的时候,你的第一反应会是:"卧槽,项目中要修改的地方太多了,心中无数个草泥马。"这个时候你要淡定,其实一切没有那么复杂</code></pre>    <p>1.打开应用程序设置-权限,比如微信,这里看到的权限就是你将要进行适配的权限,也不会太多</p>    <p><img src="https://simg.open-open.com/show/2da8af3ac7ad0571a14cd8132a18f40b.png"></p>    <p>2.分析哪些权限是基础权限</p>    <pre>  <code class="language-java">所谓基础权限就是你的App普遍都需要用的,比如位置、存储权限,如果要在项目中适配这两个权限的话,代码肯定会被改得面目全非,所以我们把这两个权限的获取放在启动页去判断。如果基础权限没有授权通过,我们就不让进入App,基础权限都不给还用个毛,这么一来适配的工作就简单多了。</code></pre>    <p>3.上一个使用原生API获取权限的小栗子</p>    <p><img src="https://simg.open-open.com/show/6be95ea6f65ba0e2f8d5f84b9676a82a.png"></p>    <pre>  <code class="language-java">这是最原生的使用方法,可以看到权限的获取操作和获取结果不是在同一个地方的,这样的话对原有代码的改动还是比较大的,而且操作过程繁琐,标题不是说"比你想的还要简单"吗?</code></pre>    <p>4.这个时候PermissionsDispatcher就要登场了 <a href="/misc/goto?guid=4959643849338359243" rel="nofollow,noindex">github</a></p>    <p><img src="https://simg.open-open.com/show/3434877424099e1b693f643d4660c27b.png"></p>    <pre>  <code class="language-java">PermissionsDispatcher是一个通过注解在编译期间生成权限检查代码的工具,以最少的改动来让你的App对权限进行适配。</code></pre>    <p>主要有下面5个注解</p>    <p>@RuntimePermissions 标记需要运行时判断的类</p>    <p>@NeedsPermission 标记需要检查权限的方法</p>    <p>@OnShowRationale 授权提示回调</p>    <p>@OnPermissionDenied 授权被拒绝回调</p>    <p>@OnNeverAskAgain 授权不再拒绝不再显示回调</p>    <p>配置</p>    <p>跟ButterKnife和Dagger2一样,配置方法很简单</p>    <p>在项目的build.gradle文件中加上:</p>    <pre>  <code class="language-java">buildscript {      dependencies {      classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'    }  }</code></pre>    <p>在module中的build.gradle加上:</p>    <pre>  <code class="language-java">apply plugin: 'android-apt'    dependencies {      compile 'com.github.hotchemi:permissionsdispatcher:2.1.3'    apt 'com.github.hotchemi:permissionsdispatcher-processor:2.1.3'  }</code></pre>    <p>使用方法</p>    <pre>  <code class="language-java">@RuntimePermissions  public class PermissionsDispatcherActivity extends AppCompatActivity    implements View.OnClickListener {            @Override          protected void onCreate(@Nullable Bundle savedInstanceState) {              super.onCreate(savedInstanceState);              setContentView(R.layout.activity_normal);                setTitle("PermissionsDispatcher");                findViewById(R.id.btn_camera).setOnClickListener(this);              findViewById(R.id.btn_call).setOnClickListener(this);          }            @Override          public void onClick(View v) {              switch (v.getId()) {                  case R.id.btn_call:                      PermissionsDispatcherActivityPermissionsDispatcher.startCallWithCheck(this);                      break;                  case R.id.btn_camera:                      PermissionsDispatcherActivityPermissionsDispatcher.startCameraWithCheck(this);                      break;              }          }            @NeedsPermission(Manifest.permission.CAMERA)          void startCamera() {              Intent intent = new Intent(); //调用照相机              intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);              startActivity(intent);          }            @NeedsPermission(Manifest.permission.CALL_PHONE)          void startCall(){              Intent intent = new Intent(Intent.ACTION_CALL);              Uri data = Uri.parse("tel:10086");              intent.setData(data);              startActivity(intent);          }      }</code></pre>    <p>1.首先@RuntimePermissions注解需要进行权限判断的类</p>    <p>2.将需要权限的操作定义在一个方法里,并用 @NeedsPermission(Manifest.permission.CAMERA)表明需要的权限(可以是多个)</p>    <p>3.Make编译一下,就会生成【当前类名+PermissionsDispatcher】的类,在原本调用的地方调用@NeedsPermission标记的方法,这时候你会发现会对应生成【方法名+WithCheck】的方法</p>    <p>4.如果你需要监听拒绝后的操作,则使用@OnPermissionDenied,使用方法一样。</p>    <p>原理</p>    <pre>  <code class="language-java">PermissionsDispatcher在编译期间,对需要权限判断的方法前后进行修饰,增加权限检查、获取逻辑,我们打开生成的代码看看便知道了,这里边并没有什么高深的东西。</code></pre>    <p><img src="https://simg.open-open.com/show/d9edba9e5dfd48e342fdac9196f49bd9.png"></p>    <p>封装</p>    <p>从上面的使用方法来看,增加一个权限判断需要定义一个方法,如果需要监听拒绝,则还要定义对应的方法,当需要获取不同权限的时候代码就多了。这时,我们就可以把权限代码抽取到Activity父类中,这里叫BasePermissionActivity, 代码如下.</p>    <pre>  <code class="language-java">/**   * 权限管理   * Created by Laiyimin on 2016/8/16.   */  @RuntimePermissions  public abstract class BasePermissionActivity extends AppCompatActivity {        /**       * 权限回调接口       */      public abstract class PermissionHandler {          /**           * 权限通过           */          public abstract void onGranted();            /**           * 权限拒绝           */          public void onDenied() {          }      }        private PermissionHandler mHandler;          @Override      public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {          super.onRequestPermissionsResult(requestCode, permissions, grantResults);          BasePermissionActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);      }        //-----------------------------------------------------------      /**       * 请求相机权限       *       * @param permissionHandler       */      protected void requestCameraPermission(PermissionHandler permissionHandler) {          this.mHandler = permissionHandler;          BasePermissionActivityPermissionsDispatcher.handleCameraPermissionWithCheck(this);      }          @NeedsPermission(Manifest.permission.CAMERA)      void handleCameraPermission() {          if (mHandler != null)              mHandler.onGranted();      }        @OnPermissionDenied(Manifest.permission.CAMERA)      void deniedCameraPermission() {          if (mHandler != null)              mHandler.onDenied();      }        @OnNeverAskAgain(Manifest.permission.CAMERA)      void OnCameraNeverAskAgain() {          showDialog("[相机]");      }        //-----------------------------------------------------------      /**       * 请求电话权限       *       * @param permissionHandler       */      protected void requestCallPermission(PermissionHandler permissionHandler) {          this.mHandler = permissionHandler;          BasePermissionActivityPermissionsDispatcher.handleCallPermissionWithCheck(this);      }          @NeedsPermission(Manifest.permission.CALL_PHONE)      void handleCallPermission() {          if (mHandler != null)              mHandler.onGranted();      }        @OnPermissionDenied(Manifest.permission.CALL_PHONE)      void deniedCallPermission() {          if (mHandler != null)              mHandler.onDenied();      }        @OnNeverAskAgain(Manifest.permission.CALL_PHONE)      void OnCallNeverAskAgain() {          showDialog("[电话]");      }          public void showDialog(String permission) {          new AlertDialog.Builder(this)                  .setTitle("权限申请")                  .setMessage("在设置-应用-荟医医生-权限中开启" + permission + "权限,以正常使用荟医功能")                  .setPositiveButton("去开启", new DialogInterface.OnClickListener() {                      @Override                      public void onClick(DialogInterface dialog, int which) {                          Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);                          Uri uri = Uri.fromParts("package", getPackageName(), null);                          intent.setData(uri);                          startActivity(intent);                            dialog.dismiss();                      }                  })                  .setNegativeButton("取消", new DialogInterface.OnClickListener() {                      @Override                      public void onClick(DialogInterface dialog, int which) {                          if (mHandler != null) mHandler.onDenied();                          dialog.dismiss();                      }                  })                  .setCancelable(false)                  .show();      }    }</code></pre>    <p>这里定义了一个PermissionHandler回调接口,同时mHandler保存了当前权限的回调操作(因为同一时间只能有一次权限请求),</p>    <pre>  <code class="language-java">protected void requestCameraPermission(PermissionHandler permissionHandler) {      this.mHandler = permissionHandler;      BasePermissionActivityPermissionsDispatcher.handleCameraPermissionWithCheck(this);  }      @NeedsPermission(Manifest.permission.CAMERA)  void handleCameraPermission() {      if (mHandler != null)          mHandler.onGranted();  }</code></pre>    <p>这里定义好各种权限请求的方法供子类调用,例如requestCameraPermission,在它里边调用了权限判断方法,逻辑很简单,大家看看代码就明白了。最后在子类中只要调用这个方法就行了。</p>    <pre>  <code class="language-java">requestCameraPermission(new PermissionHandler() {          @Override          public void onGranted() {              Intent intent = new Intent(); //调用照相机              intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);              startActivity(intent);          }            @Override          public void onDenied() {            }      });</code></pre>    <h3>后续</h3>    <pre>  <code class="language-java">在App运行过程中,用户可能手动去关闭权限,如果这个时候正在使用着权限,应用就会挂掉,我们的处理就是让App自动重启,让其在引导页重新获取权限,保证软件后续正常运行。附带一个CrashHandler类实现了异常重启,在项目代码中可以找到。</code></pre>    <p>GitHub地址 <a href="/misc/goto?guid=4959676972392232066" rel="nofollow,noindex">https://github.com/a5533348/XPermission</a></p>    <p> </p>    <p>来自:http://xdeveloper.cn/android6-0quan-xian-gua-pei-bi-ni-xiang-de-huan-yao-jian-dan-2/</p>    <p> </p>    <p><span style="background:rgb(189, 8, 28) url("data:image/svg+xml; border-radius:2px; border:medium none; color:rgb(255, 255, 255); cursor:pointer; display:none; font:bold 11px/20px "Helvetica Neue",Helvetica,sans-serif; opacity:1; padding:0px 4px 0px 0px; position:absolute; text-align:center; text-indent:20px; width:auto; z-index:8675309">Save</span></p>