Android动态权限申请详解

前言

注:只想看实现的朋友们可以直接跳到最后面的最终实现

大家是否还在为动态权限申请感到苦恼呢?传统的动态权限申请需要在Activity中重写onRequestPermissionsResult方法来接收用户权限授予的结果。试想一下,你需要在一个子模块中申请权限,那得从这个模块所在的ActivityonRequestPermissionsResult中将结果一层层再传回到这个模块中,相当的麻烦,代码也相当冗余和不干净,逼死强迫症。

使用

为了解决这个痛点,我封装出了两个方法,用于随时随地快速的动态申请权限,我们先来看看我们的封装方法是如何调用的:

  1. activity.requestPermission(Manifest.permission.CAMERA, onPermit = {
  2.      //申请权限成功 Do something
  3. }, onDeny = { shouldShowCustomRequest ->
  4.      //申请权限失败 Do something
  5.      if (shouldShowCustomRequest) {
  6.          //用户选择了拒绝并且不在询问,此时应该使用自定义弹窗提醒用户授权(可选)
  7.      }
  8. })

这样是不是非常的简单便捷?申请和结果回调都在一个方法内处理,并且支持随用随调。

方案

那么,这么方便好用的方法是怎么实现的呢?不知道小伙伴们在平时开发中有没有注意到过,当你调用startActivityForResult时,AS会提示你该方法已被弃用,点进去看会告诉你应该使用registerForActivityResult方法替代。没错,这就是androidx给我们提供的ActivityResult功能,并且这个功能不仅支持ActivityResult回调,还支持打开文档,拍摄照片,选择文件等各种各样的回调,同样也包括我们今天要说的权限申请

其实Android在官方文档 请求运行时权限 中就已经将其作为动态权限申请的推荐方法了,如下示例代码所示:

  1. val requestPermissionLauncher =
  2.      registerForActivityResult(RequestPermission()
  3.      ) { isGranted: Boolean ->
  4.          if (isGranted) {
  5.              // Permission is granted. Continue the action or workflow in your
  6.              // app.
  7.          } else {
  8.              // Explain to the user that the feature is unavailable because the
  9.              // feature requires a permission that the user has denied. At the
  10.              // same time, respect the user’s decision. Don’t link to system
  11.              // settings in an effort to convince the user to change their
  12.              // decision.
  13.          }
  14.      }
  15.  
  16. when {
  17.      ContextCompat.checkSelfPermission(
  18.              CONTEXT,
  19.              Manifest.permission.REQUESTED_PERMISSION
  20.              ) == PackageManager.PERMISSION_GRANTED -> {
  21.          // You can use the API that requires the permission.
  22.      }
  23.      shouldShowRequestPermissionRationale(…) -> {
  24.          // In an educational UI, explain to the user why your app requires this
  25.          // permission for a specific feature to behave as expected, and what
  26.          // features are disabled if it’s declined. In this UI, include a
  27.          // “cancel” or “no thanks” button that lets the user continue
  28.          // using your app without granting the permission.
  29.          showInContextUI(…)
  30.      }
  31.      else -> {
  32.          // You can directly ask for the permission.
  33.          // The registered ActivityResultCallback gets the result of this request.
  34.          requestPermissionLauncher.launch(
  35.                  Manifest.permission.REQUESTED_PERMISSION)
  36.      }
  37. }

说到这里,可能有小伙伴要质疑我了:“官方文档里都写明了的东西,你还特地写一遍,还起了这么个标题,是不是在水文章?!”

莫急,如果你遵照以上方法这么写的话,在实际调用的时候会直接发生崩溃:

  1. Java.lang.IllegalStateException:
  2. LifecycleOwner Activity is attempting to register while current state is RESUMED.
  3. LifecycleOwners must call register before they are STARTED.

这段报错很明显的告诉我们,我们的注册工作必须要在Activity声明周期STARTED之前进行(也就是onCreate时和onStart完成前),但这样我们就必须要事先注册好所有可能会用到的权限,没办法做到随时随地有需要时再申请权限了,有办法解决这个问题吗?答案是肯定的。

绕过生命周期检测

想解决这个问题,我们必须要知道问题的成因,让我们带着问题进到源码中一探究竟:

  1. public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
  2.          @NonNull ActivityResultContract<I, O> contract,
  3.          @NonNull ActivityResultCallback<O> callback) {
  4.      return registerForActivityResult(contract, MACtivityResultRegistry, callback);
  5. }
  6.  
  7. public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
  8.          @NonNull final ActivityResultContract<I, O> contract,
  9.          @NonNull final ActivityResultRegistry registry,
  10.          @NonNull final ActivityResultCallback<O> callback) {
  11.      return registry.register(
  12.              “activity_rq#” + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
  13. }
  14.  
  15. public final <I, O> ActivityResultLauncher<I> register(
  16.          @NonNull final String key,
  17.          @NonNull final LifecycleOwner lifecycleOwner,
  18.          @NonNull final ActivityResultContract<I, O> contract,
  19.          @NonNull final ActivityResultCallback<O> callback) {
  20.  
  21.      Lifecycle lifecycle = lifecycleOwner.getLifecycle();
  22.  
  23.      if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
  24.          throw new IllegalStateException(“LifecycleOwner “ + lifecycleOwner + ” is “
  25.                  + “attempting to register while current state is “
  26.                  + lifecycle.getCurrentState() + “. LifecycleOwners must call register before “
  27.                  + “they are STARTED.”);
  28.      }
  29.  
  30.      registerKey(key);
  31.      LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
  32.      if (lifecycleContainer == null) {
  33.          lifecycleContainer = new LifecycleContainer(lifecycle);
  34.      }
  35.      LifecycleEventObserver observer = new LifecycleEventObserver() {  };
  36.      lifecycleContainer.addObserver(observer);
  37.      mKeyToLifecycleContainers.put(key, lifecycleContainer);
  38.  
  39.      return new ActivityResultLauncher<I>() {  };
  40. }

我们可以发现,registerForActivityResult实际上就是调用了ComponentActivity内部成员变量的mActivityResultRegistry.register方法,而在这个方法的一开头就检查了当前Activity的生命周期,如果生命周期位于STARTED后则直接抛出异常,那我们该如何绕过这个限制呢?

其实在register方法的下面就有一个同名重载方法,这个方法并没有做生命周期的检测:

  1. public final <I, O> ActivityResultLauncher<I> register(
  2.          @NonNull final String key,
  3.          @NonNull final ActivityResultContract<I, O> contract,
  4.          @NonNull final ActivityResultCallback<O> callback) {
  5.      registerKey(key);
  6.      mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
  7.  
  8.      if (mParsedPendingResults.containsKey(key)) {
  9.          @SuppressWarnings(“unchecked”)
  10.          final O parsedPendingResult = (O) mParsedPendingResults.get(key);
  11.          mParsedPendingResults.remove(key);
  12.          callback.onActivityResult(parsedPendingResult);
  13.      }
  14.      final ActivityResult pendingResult = mPendingResults.getParcelable(key);
  15.      if (pendingResult != null) {
  16.          mPendingResults.remove(key);
  17.          callback.onActivityResult(contract.parseResult(
  18.                  pendingResult.getResultCode(),
  19.                  pendingResult.getData()));
  20.      }
  21.  
  22.      return new ActivityResultLauncher<I>() {  };
  23. }

找到这个方法就简单了,我们将registerForActivityResult方法调用替换成activityResultRegistry.register调用就可以了

当然,我们还需要注意一些小细节,检查生命周期的register方法同时也会注册生命周期回调,当Activity被销毁时会将我们注册的ActivityResult回调移除,我们也需要给我们封装的方法加上这个逻辑,最终实现就如下所示。

最终实现

  1. private val nextLocalRequestCode = AtomicInteger()
  2.  
  3. private val nextKey: String
  4.      get() = “activity_rq#${nextLocalRequestCode.getAndIncrement()}”
  5.  
  6. fun ComponentActivity.requestPermission(
  7.      permission: String,
  8.      onPermit: () -> Unit,
  9.      onDeny: (shouldShowCustomRequest: Boolean) -> Unit
  10. ) {
  11.      if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
  12.          onPermit()
  13.          return
  14.      }
  15.      var launcher by Delegates.notNull<ActivityResultLauncher<String>>()
  16.      launcher = activityResultRegistry.register(
  17.          nextKey,
  18.          ActivityResultContracts.RequestPermission()
  19.      ) { result ->
  20.          if (result) {
  21.              onPermit()
  22.          } else {
  23.              onDeny(!ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
  24.          }
  25.          launcher.unregister()
  26.      }
  27.      lifecycle.addObserver(object : LifecycleEventObserver {
  28.          override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
  29.              if (event == Lifecycle.Event.ON_DESTROY) {
  30.                  launcher.unregister()
  31.                  lifecycle.removeObserver(this)
  32.              }
  33.          }
  34.      })
  35.      launcher.launch(permission)
  36. }
  37.  
  38. fun ComponentActivity.requestPermissions(
  39.      permissions: Array<String>,
  40.      onPermit: () -> Unit,
  41.      onDeny: (shouldShowCustomRequest: Boolean) -> Unit
  42. ) {
  43.      var hASPermissions = true
  44.      for (permission in permissions) {
  45.          if (ContextCompat.checkSelfPermission(
  46.                  this,
  47.                  permission
  48.              ) != PackageManager.PERMISSION_GRANTED
  49.          ) {
  50.              hasPermissions = false
  51.              break
  52.          }
  53.      }
  54.      if (hasPermissions) {
  55.          onPermit()
  56.          return
  57.      }
  58.      var launcher by Delegates.notNull<ActivityResultLauncher<Array<String>>>()
  59.      launcher = activityResultRegistry.register(
  60.          nextKey,
  61.          ActivityResultContracts.RequestMultiplePermissions()
  62.      ) { result ->
  63.          var allAllow = true
  64.          for (allow in result.values) {
  65.              if (!allow) {
  66.                  allAllow = false
  67.                  break
  68.              }
  69.          }
  70.          if (allAllow) {
  71.              onPermit()
  72.          } else {
  73.              var shouldShowCustomRequest = false
  74.              for (permission in permissions) {
  75.                  if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
  76.                      shouldShowCustomRequest = true
  77.                      break
  78.                  }
  79.              }
  80.              onDeny(shouldShowCustomRequest)
  81.          }
  82.          launcher.unregister()
  83.      }
  84.      lifecycle.addObserver(object : LifecycleEventObserver {
  85.          override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
  86.              if (event == Lifecycle.Event.ON_DESTROY) {
  87.                  launcher.unregister()
  88.                  lifecycle.removeObserver(this)
  89.              }
  90.          }
  91.      })
  92.      launcher.launch(permissions)
  93. }

总结

其实很多实用技巧本质上都是很简单的,但没有接触过就很难想到,我将我的开发经验分享给大家,希望能帮助到大家。

到此这篇关于Android动态权限申请详解的文章就介绍到这了,更多相关Android动态权限申请内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

标签

发表评论