SoulPermission-Android一句话权限适配的更优解决方案

2023-11-22 17:38

本文主要是介绍SoulPermission-Android一句话权限适配的更优解决方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

关于运行时的权限不用多说,这个概念已经很久,近期工信部在强推TargetSDK26,我这边做了一些适配工作,其中有一项就是运行时权限,今天将对运行时权限提供一个更优雅的解决方案,如果你还不了解运行时权限,请移步:Android运行时权限浅谈

现状:

以直接调用打电话功能为例

首先我们项目中可能会有这么一个方法:

    /*** 拨打指定电话*/public static void makeCall(Context context, String phoneNumber) {Intent intent = new Intent(Intent.ACTION_CALL);Uri data = Uri.parse("tel:" + phoneNumber);intent.setData(data);if (!(context instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}context.startActivity(intent);}
复制代码

那么在适配动态权限以前,在我们任意用到打电话的业务页面我们可能就是这么用:

 public void makeCall() {Utils.makeCall(BeforeActivity.this, "10086");}
复制代码

于是乎,某一天,我们应用要适配targetSdk 26,首先我们要适配的就是动态权限,所以下面的代码就会变成这样:

  public void makeCall() {//6.0以下 直接即可拨打if (android.os.Build.VERSION.SDK_INT < M) {Utils.makeCall(BeforeActivity.this, "10086");} else {//6.0以上if (ContextCompat.checkSelfPermission(BeforeActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(BeforeActivity.this, new String[]{Manifest.permission.CALL_PHONE},REQUEST_CODE_CALL);} else {Utils.makeCall(BeforeActivity.this, "10086");}}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUEST_CODE_CALL) {if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {Toast.makeText(BeforeActivity.this, "本次拨打电话授权失败,请手动去设置页打开权限,或者重试授权权限", Toast.LENGTH_SHORT).show();} else {Utils.makeCall(BeforeActivity.this, "10086");}}}
复制代码

以上就是拨打电话功能新老权限版本的基本实现(还不包括shouldShowRequestPermissionRationale的部分)。 目前也有一些知名的开源库,如PermissionsDispatcher,RXPermission等。虽然也能实现我们的功能,但无论自己适配还是现有开源库方案大体上都会或多或少有以下几个问题:

现有权限库存在的问题:

  • 每个页面都要重写onPermissionResult方法、维护requestCode、或者第三方库封装的onPermissionResult方法,如果项目庞大,适配到每个业务点会非常繁琐。
  • 权限申请还区分Activity和Fragment,又要分别处理
  • 每个权限都要写大量的if else代码去做版本判断,判断新老机型分别处理

基于第一个业务繁琐的问题,很多应用选择适配权限的时候,把所用到的敏感权限放在一个特定的页面去申请,比如欢迎页(某知名音乐播放器等),如果授权不成功,则会直接无法进入应用,这样虽然省事,但是用户体验不好,我在应用一打开,提示需要电话权限,用户会很疑惑。这样其实就违背了“运行时授权”的初衷,谷歌希望我们在真正调用的该功能的时候去请求,这样权限请求和用户的目的是一致的,也更容易授予权限成功。

那么能不能做到如下几个点呢?

对权限适配的期望:

  • 基于用户体验考虑,我不希望在应用一打开就向用户索取一堆授权,异或是跳一个页面专门去授权、困扰我们宝贵的用户
  • 不需要去重写onPermissionResult、甚至不需要Activity和Fragment。
  • 去除版本判断。无论什么系统版本的新老手机,都是走一个方法
  • 一行代码完成从权限检查、请求、到最终完我要做的事情
  • 我不需要在原有项目中改太多代码

带着上述几个问题,我们今天的主角:SoulPermission应运而生。

当使用了SoulPermission以后,最直观上看,我们上面的代码就变成了这样:

 public void makeCall() {SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {@Overridepublic void onPermissionOk(Permission permission) {Utils.makeCall(AfterActivity.this, "10086");}@Overridepublic void onPermissionDenied(Permission permission) {Toast.makeText(AfterActivity.this, "本次拨打电话授权失败,请手动去设置页打开权限,或者重试授权权限", Toast.LENGTH_SHORT).show();}});}
复制代码

SoulPermission:

优势:

  • 解耦Activity和Fragment、不再需要Context、不再需要onPermissionResult
  • 内部涵盖版本判断,一行代码解决权限相关操作,无需在调用业务方写权限适配代码,继而实现真正调用时请求的“真运行时权限”
  • 接入成本低,零入侵,仅需要在gradle配置一行代码

工作流程:

如果我以在Android手机上要做一件事(doSomeThing),那么我最终可以有两个结果:

  • A:可以做
  • B:不能做

基于上述两种结果,那么SoulPermission的大致工作流程如下:

 

在这里插入图片描述

 

 

从开始到结束展示了我们上述打电话的流程,A即直接拨打,B即toast提示用户,无法继续后续操作,绿色部分流程即可选部分,即对shouldShowRequestPermissionRationale的处理,那么完整权限流程下来,我们拨打电话的代码就是这么写:

   public void makeCall() {SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {@Overridepublic void onPermissionOk(Permission permission) {Utils.makeCall(AfterActivity.this, "10086");}@Overridepublic void onPermissionDenied(Permission permission) {//绿色框中的流程//用户第一次拒绝了权限且没有勾选"不再提示"的情况下这个值为true,此时告诉用户为什么需要这个权限。if (permission.shouldRationale()) {new AlertDialog.Builder(AfterActivity.this).setTitle("提示").setMessage("如果你拒绝了权限,你将无法拨打电话,请点击授予权限").setPositiveButton("授予", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {//用户确定以后,重新执行请求原始流程makeCall();}}).create().show();} else {Toast.makeText(AfterActivity.this, "本次拨打电话授权失败,请手动去设置页打开权限,或者重试授权权限", Toast.LENGTH_SHORT).show();}}});}
复制代码

 

在这里插入图片描述

 

 

上述便是其在满足运行时权限下的完整工作流程。那么关于版本兼容呢? 针对部分手机6.0以下手机,SoulPermission也做了兼容,可以通过AppOps 检查权限,内部将权限名称做了相应的映射,它的大体流程就是下图: (这个检查结果不一定准确,但是即使不准确,也默认成功(A),保证我们回调能往下走,不会阻塞流程,有些在6.0以下自己实现了权限系统的手机(如vivo,魅族)等也是走此A的回调,最终会走到它们自己的权限申请流程)

在这里插入图片描述

 

 

最佳实践:

基于对于代码中对新老系统版本做了控制,而在权限拒绝里面很多处理也是又可以提取的部分,我们可以把回调再次封装一下,进一步减少重复代码:

public abstract class CheckPermissionWithRationaleAdapter implements CheckRequestPermissionListener {private String rationaleMessage;private Runnable retryRunnable;/*** @param rationaleMessage 当用户首次拒绝弹框时候,根据权限不同给用户不同的文案解释* @param retryRunnable    用户点重新授权的runnable 即重新执行原方法*/public CheckPermissionWithRationaleAdapter(String rationaleMessage, Runnable retryRunnable) {this.rationaleMessage = rationaleMessage;this.retryRunnable = retryRunnable;}@Overridepublic void onPermissionDenied(Permission permission) {Activity activity = SoulPermission.getInstance().getTopActivity();if (null == activity) {return;}//绿色框中的流程//用户第一次拒绝了权限、并且没有勾选"不再提示"这个值为true,此时告诉用户为什么需要这个权限。if (permission.shouldRationale()) {new AlertDialog.Builder(activity).setTitle("提示").setMessage(rationaleMessage).setPositiveButton("授予", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {//用户确定以后,重新执行请求原始流程retryRunnable.run();}}).create().show();} else {//此时请求权限会直接报未授予,需要用户手动去权限设置页,所以弹框引导用户跳转去设置页String permissionDesc = permission.getPermissionNameDesc();new AlertDialog.Builder(activity).setTitle("提示").setMessage(permissionDesc + "异常,请前往设置->权限管理,打开" + permissionDesc + "。").setPositiveButton("去设置", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {//去设置页SoulPermission.getInstance().goPermissionSettings();}}).create().show();}}
}
复制代码

然后我们在App所有打电话的入口处做一次调用:

  /*** 拨打指定电话*/public static void makeCall(final Context context, final String phoneNumber) {SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.CALL_PHONE,new CheckPermissionWithRationaleAdapter("如果你拒绝了权限,你将无法拨打电话,请点击授予权限",new Runnable() {@Overridepublic void run() {//retrymakeCall(context, phoneNumber);}}) {@Overridepublic void onPermissionOk(Permission permission) {Intent intent = new Intent(Intent.ACTION_CALL);Uri data = Uri.parse("tel:" + phoneNumber);intent.setData(data);if (!(context instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}context.startActivity(intent);}});}
复制代码

那么这样下来,在Activity和任何业务页面的调用就只有一行代码了:

   findViewById(R.id.bt_call).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {UtilsWithPermission.makeCall(getActivity(), "10086");}});
复制代码

其中完全拒绝以后,SoulPermission 提供了跳转到系统权限设置页的方法,我们再来看看效果:

 

在这里插入图片描述

 

 

很多时候,其实绿色部分(shouldShowRequestPermissionRationale)其实并不一定必要,反复的弹框用户可能会厌烦,大多数情况,我们这么封装就好:

public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {@Overridepublic void onPermissionDenied(Permission permission) {//SoulPermission提供栈顶ActivityActivity activity = SoulPermission.getInstance().getTopActivity();if (null == activity) {return;}String permissionDesc = permission.getPermissionNameDesc();new AlertDialog.Builder(activity).setTitle("提示").setMessage(permissionDesc + "异常,请前往设置->权限管理,打开" + permissionDesc + "。").setPositiveButton("去设置", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialogInterface, int i) {//去设置页SoulPermission.getInstance().goPermissionSettings();}}).create().show();}
}
复制代码

我们再写一个选择联系人的方法:

   /*** 选择联系人*/public static void chooseContact(final Activity activity, final int requestCode) {SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.READ_CONTACTS,new CheckPermissionAdapter() {@Overridepublic void onPermissionOk(Permission permission) {activity.startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), requestCode);}});}
复制代码

在Activity中也是一行解决问题:

 findViewById(R.id.bt_choose_contact).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);}});
复制代码

代码细节请参考demo,我们再来看看效果:

 

在这里插入图片描述

 

 

主要功能的源码分析:

优雅的避掉onPermissionResult:

适配权限最大的痛点在于:项目业务页面繁多,如果你想实现“真运行时权限”的话就需要在业务的Activity或者Fragment中去重写权限请求回调方法,斟酌一番并且在参考了下RxPermission中对权限请求的处理,我决定用同样的方式—用一个没有界面的Fragment去完成我们权限请求的操作,下面贴上部分代码:

首先定义一个接口,用于封装权限请求的结果

public interface RequestPermissionListener {/*** 得到权限检查结果** @param permissions 封装权限的数组*/void onPermissionResult(Permission[] permissions);}
复制代码

然后是我们的Fragment:

public class PermissionSupportFragment extends Fragment implements IPermissionActions {/*** 内部维护requestCode*/private static final int REQUEST_CODE = 11;/*** 传入的回调*/private RequestPermissionListener listener;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//当状态发生改变,比如设备旋转时候,Fragment不会被销毁setRetainInstance(true);}/*** 外部请求的最终调用方法* @param permissions 权限* @param listener    回调*/@TargetApi(M)@Overridepublic void requestPermissions(String[] permissions, RequestPermissionListener listener) {requestPermissions(permissions, REQUEST_CODE);this.listener = listener;}@TargetApi(M)@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);Permission[] permissionResults = new Permission[permissions.length];//拿到授权结果以后对结果做一些封装if (requestCode == REQUEST_CODE) {for (int i = 0; i < permissions.length; ++i) {Permission permission = new Permission(permissions[i], grantResults[i], this.shouldShowRequestPermissionRationale(permissions[i]));permissionResults[i] = permission;}}if (listener != null && getActivity() != null && !getActivity().isDestroyed()) {listener.onPermissionResult(permissionResults);}}}
复制代码

其中Permission是我们的权限名称、授予结果、是否需要给用于一个解释的包装类:

public class Permission {private static final String TAG = Permission.class.getSimpleName();/*** 权限名称*/public String permissionName;/*** 授予结果*/public int grantResult;/*** 是否需要给用户一个解释*/public boolean shouldRationale;/*** 权限是否已经被授予*/public boolean isGranted() {return grantResult == PackageManager.PERMISSION_GRANTED;}
//。。。}
复制代码

至此,我们已经利用自己实现的一个没有界面的Fragment封装了运行时权限相关的请求、RequestCode的维护、以及onPermissionResult的回调、在我们真正调用的时候代码是这样的:

    /**** @param activity 栈顶 Activity* @param permissionsToRequest 待请求的权限* @param listener 回调*/private void requestRuntimePermission(final Activity activity, final Permission[] permissionsToRequest, final CheckRequestPermissionsListener listener) {new PermissionRequester(activity).withPermission(permissionsToRequest).request(new RequestPermissionListener() {@Overridepublic void onPermissionResult(Permission[] permissions) {List<Permission> refusedListAfterRequest = new LinkedList<>();for (Permission requestResult : permissions) {if (!requestResult.isGranted()) {refusedListAfterRequest.add(requestResult);}}if (refusedListAfterRequest.size() == 0) {listener.onAllPermissionOk(permissionsToRequest);} else {listener.onPermissionDenied(PermissionTools.convert(refusedListAfterRequest));}}});}
复制代码

其中PermissionRequester也就是一个简单的构建者模式,其中包含了对Activity的类型判断,根据Activity类型去确定Fragment的实现:如果是FragmentActivity的实例,则使用Support包中的Fragment,否则用默认的Fragment,这样就兼容了有些应用的项目的基类不是AppComponentActivity(FragmentActivity)的情形,当然,原则上最低支持4.0,即默认Fragment的支持版本。

class PermissionFragmentFactory {private static final String FRAGMENT_TAG = "permission_fragment_tag";static IPermissionActions create(Activity activity) {IPermissionActions action;if (activity instanceof FragmentActivity) {FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();PermissionSupportFragment permissionSupportFragment = (PermissionSupportFragment) supportFragmentManager.findFragmentByTag(FRAGMENT_TAG);if (null == permissionSupportFragment) {permissionSupportFragment = new PermissionSupportFragment();supportFragmentManager.beginTransaction().add(permissionSupportFragment, FRAGMENT_TAG).commitNowAllowingStateLoss();}action = permissionSupportFragment;} else {android.app.FragmentManager fragmentManager = activity.getFragmentManager();PermissionFragment permissionFragment = (PermissionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);if (null == permissionFragment) {permissionFragment = new PermissionFragment();activity.getFragmentManager().beginTransaction().add(permissionFragment, FRAGMENT_TAG).commitAllowingStateLoss();}action = permissionFragment;}return action;}
}
复制代码

至此,整个请求链已经很像最外层暴露的CheckAndRequestPermission方法了,就差一个Activity了,那么参数Activity怎么来呢?我们继续想办法。

再舍去Activity:

当然是使用Application中的ActivityLifecycleCallbacks,使用它的 registerActivityLifecycleCallbacks,感知Activity声明周期变化,获取到当前应用栈顶的Activity,这样我们就不需要自己手动传入了。

public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {WeakReference<Activity> topActWeakReference;@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {//原则上只需要onResume,兼容如果在onCreate的时候做权限申请保证此时有Activity对象topActWeakReference = new WeakReference<>(activity);}//.....@Overridepublic void onActivityResumed(Activity activity) {topActWeakReference = new WeakReference<>(activity);}//.....
}复制代码

注册它仅仅需要一个Application:

 /*** @param context Application*/private void registerLifecycle(Application context) {if (null != lifecycle) {context.unregisterActivityLifecycleCallbacks(lifecycle);}lifecycle = new PermissionActivityLifecycle();context.registerActivityLifecycleCallbacks(lifecycle);}
复制代码

这样一来,只要调用了初始化方法registerLifecycle,我们就能提供提供栈顶Activity了

  /*** 获取栈顶Activity** @return 当前应用栈顶Activity* @throws InitException            初始化失败* @throws ContainerStatusException Activity状态异常*/private Activity getContainer() {// may auto init failedif (null == lifecycle || null == lifecycle.topActWeakReference) {throw new InitException();}// activity status errorif (null == lifecycle.topActWeakReference.get() || lifecycle.topActWeakReference.get().isFinishing()) {throw new ContainerStatusException();}return lifecycle.topActWeakReference.get();}
复制代码

结合起来回到我们之前申请权限的方法(省略了日志打印和线程的判断,如果需要再细看源码):

    private void requestPermissions(final Permissions permissions, final CheckRequestPermissionsListener listener) {//check container statusfinal Activity activity;try {activity = getContainer();} catch (Exception e) {//activity status error do not requestreturn;}//......//finally requestrequestRuntimePermission(activity, permissions.getPermissions(), listener);}复制代码

至此,我们已经能脱离Activity和Fragment,也无需重写onPermissionResult了,只需要一个ApplicationContext初始化即可。

能否更简便一点?

最后避掉Application(免初始化):

我们可以自定义ContentProvider来完成库的初始化,我们可以参考Lifecycle组件的初始化:

//lifeCycle定义的初始化Provider
public class LifecycleRuntimeTrojanProvider extends ContentProvider {@Overridepublic boolean onCreate() {LifecycleDispatcher.init(getContext());ProcessLifecycleOwner.init(getContext());return true;}
}
复制代码

和它的Manifest文件:

 <application><providerandroid:name="android.arch.lifecycle.LifecycleRuntimeTrojanProvider"android:authorities="${applicationId}.lifecycle-trojan"android:exported="false"android:multiprocess="true" /></application>
复制代码

参照它的实现给我们提供了一个很好的思路,我们可以自定义Provider去初始化一些库或者其他的内容,现在我们写一个自己的initContentProvider:

public class InitProvider extends ContentProvider {@Overridepublic boolean onCreate() {//初始化我们的库SoulPermission.getInstance().autoInit((Application) getContext());return true;}//......
}
复制代码

在库的AndroidManifest文件中声明:

   <application><provider android:authorities="${applicationId}.permission.provider"android:name=".permission.InitProvider"android:multiprocess="true"android:exported="false"/></application>
复制代码

至于为什么这个Context就是Application,我们可以参考ActivityThread中的对ContentProvider的初始化:

    public void handleInstallProvider(ProviderInfo info) {//即我们的应用的ApplicationinstallContentProviders(mInitialApplication, Arrays.asList(info));}
复制代码

至此,我们权限申请流程就跟Activity、Fragment、乃至Context都没有关系了。

去除if&else、涵盖版本判断:

虽然我们完成了对运行时权限的申请流程,但是毕竟只针对6.0以上机型,如果上面流程还想一句话完成的话,那我们还得兼容老的机型,so,我们需要做在方法内做一个版本判断:

首先判断系统版本

    public static boolean isOldPermissionSystem(Context context) {int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;return android.os.Build.VERSION.SDK_INT < M || targetSdkVersion < M;}复制代码

然后是检查权限:

6.0以上当然是走系统Api:

class RunTimePermissionChecker implements PermissionChecker {private String permission;private Context context;RunTimePermissionChecker(Context context, String permission) {this.permission = permission;this.context = context;}@TargetApi(M)@Overridepublic boolean check() {int checkResult = ContextCompat.checkSelfPermission(context, permission);return checkResult == PackageManager.PERMISSION_GRANTED;}}
复制代码

6.0以下、4.4以上通过AppOps反射获取(为了保证一致性,把权限名称参数在check方法中做了映射,把权限的String参数映射成checkOp的整形参数):

class AppOpsChecker implements PermissionChecker {private Context context;private String permission;AppOpsChecker(Context context, String permission) {this.context = context;this.permission = permission;}/*** 老的通過反射方式檢查權限狀態* 结果可能不准确,如果返回false一定未授予* 按需在里面添加* 如果没匹配上或者异常都默认权限授予** @return 检查结果*/@Overridepublic boolean check() {if (null == permission) {return true;}switch (permission) {case Manifest.permission.READ_CONTACTS:return checkOp(4);case Manifest.permission.WRITE_CONTACTS:return checkOp(5);case Manifest.permission.CALL_PHONE:return checkOp(13);case Manifest.permission.READ_PHONE_STATE:return checkOp(51);case Manifest.permission.CAMERA:return checkOp(26);case Manifest.permission.READ_EXTERNAL_STORAGE:return checkOp(59);case Manifest.permission.WRITE_EXTERNAL_STORAGE:return checkOp(60);case Manifest.permission.ACCESS_FINE_LOCATION:case Manifest.permission.ACCESS_COARSE_LOCATION:return checkOp(2);default:break;}return true;}boolean checkOp(int op) {if (Build.VERSION.SDK_INT < KITKAT) {return true;}try {AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);Method method = AppOpsManager.class.getDeclaredMethod("checkOp", int.class, int.class, String.class);return 0 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());} catch (Exception e) {e.printStackTrace();}return true;}
}
复制代码

和版本判断起来就是这样:

 public static PermissionChecker create(Context context, String permission) {if (PermissionTools.isOldPermissionSystem(context)) {return new AppOpsChecker(context, permission);} else {return new RunTimePermissionChecker(context, permission);}
}
复制代码

再到我们最终调用的权限检测方法:

private boolean checkPermission(Context context, String permission) {return CheckerFactory.create(context, permission).check();}
复制代码

最终我们权限库一行代码从权限检测、权限请求联合起来的操作就是这样:

   /*** 多个权限的检查与申请* 在敏感操作前,先检查权限和请求权限,当完成操作后可做后续的事情** @param permissions 多个权限的申请  Permissions.build(Manifest.permission.CALL_PHONE,Manifest.permission.CAMERA)* @param listener    请求之后的回调*/public void checkAndRequestPermissions(@NonNull Permissions permissions, @NonNull final CheckRequestPermissionsListener listener) {//首先检查权限Permission[] checkResult = checkPermissions(permissions.getPermissionsString());//得到有多少权限被拒绝了final Permission[] refusedPermissionList = filterRefusedPermissions(checkResult);if (refusedPermissionList.length > 0) {//是否可以请求运行时权限,即6.0以上if (canRequestRunTimePermission()) {//请求权限,并把listener传下去,也就是我们一开始看请求流程分析中的那个方法requestPermissions(Permissions.build(refusedPermissionList), listener);} else {//无法请求权限,本次操作失败listener.onPermissionDenied(refusedPermissionList);}} else {//没有权限被拒绝,认为所有权限都ok,回调成功listener.onAllPermissionOk(checkResult);}}
复制代码

至此,我们的三个主要需求的源码分析基本完成,如果有啥疑问和细节上的实现,可以自行阅读源码即可。

总结:

SoulPermission很好的适配了真运行时权限、除了上述三个个主要功能以外还提供以下功能:

  • 支持多项权限同时请求
  • 支持检查通知权限
  • 支持系统权限页面跳转
  • 支持debug模式

GitHub以及Sample地址

欢迎各位小伙伴加入我的qq群:开发一群:454430053 开发二群:537532956   这里已经有很多小伙伴在等你了,快来加入我们吧!

这篇关于SoulPermission-Android一句话权限适配的更优解决方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/411790

相关文章

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

数据库oracle用户密码过期查询及解决方案

《数据库oracle用户密码过期查询及解决方案》:本文主要介绍如何处理ORACLE数据库用户密码过期和修改密码期限的问题,包括创建用户、赋予权限、修改密码、解锁用户和设置密码期限,文中通过代码介绍... 目录前言一、创建用户、赋予权限、修改密码、解锁用户和设置期限二、查询用户密码期限和过期后的修改1.查询用

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

Xshell远程连接失败以及解决方案

《Xshell远程连接失败以及解决方案》本文介绍了在Windows11家庭版和CentOS系统中解决Xshell无法连接远程服务器问题的步骤,在Windows11家庭版中,需要通过设置添加SSH功能并... 目录一.问题描述二.原因分析及解决办法2.1添加ssh功能2.2 在Windows中开启ssh服务2

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

python 字典d[k]中key不存在的解决方案

《python字典d[k]中key不存在的解决方案》本文主要介绍了在Python中处理字典键不存在时获取默认值的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录defaultdict:处理找不到的键的一个选择特殊方法__missing__有时候为了方便起见,

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Linux限制ip访问的解决方案

《Linux限制ip访问的解决方案》为了修复安全扫描中发现的漏洞,我们需要对某些服务设置访问限制,具体来说,就是要确保只有指定的内部IP地址能够访问这些服务,所以本文给大家介绍了Linux限制ip访问... 目录背景:解决方案:使用Firewalld防火墙规则验证方法深度了解防火墙逻辑应用场景与扩展背景:

SpringBoot嵌套事务详解及失效解决方案

《SpringBoot嵌套事务详解及失效解决方案》在复杂的业务场景中,嵌套事务可以帮助我们更加精细地控制数据的一致性,然而,在SpringBoot中,如果嵌套事务的配置不当,可能会导致事务不生效的问题... 目录什么是嵌套事务?嵌套事务失效的原因核心问题:嵌套事务的解决方案方案一:将嵌套事务方法提取到独立类