百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

这也许是Android一句话权限适配的最优 解决方案_因android权限限制无法访问此目录怎么解决

toyiye 2024-04-03 23:10 25 浏览 0 评论

现状

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

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

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

/**
 * 拨打指定电话
 */
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");
 }
 }
}
@Override
public 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等。

虽然也能实现我们的功能,但无论自己适配还是现有开源库方案大体上都会或多或少有以下几个问题:

1、每个页面都要重写onPermissionResult方法、维护requestCode、或者第三方库封装的onPermissionResult方法,如果项目庞大,适配到每个业务点会非常繁琐

2、权限申请还区分Activity和Fragment,又要分别处理

3、每个权限都要写大量的if else代码去做版本判断,判断新老机型分别处理

基于第一个业务繁琐的问题,很多应用选择适配权限的时候,把所用到的敏感权限放在一个特定的页面去申请,比如欢迎页(某知名音乐播放器等),如果授权不成功,则会直接无法进入应用,这样虽然省事,但是用户体验不好,我在应用一打开,提示需要电话权限,用户会很疑惑。

这样其实就违背了“运行时授权”的初衷,谷歌希望我们在真正调用的该功能的时候去请求,这样权限请求和用户的目的是一致的,也更容易授予权限成功。

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

1、不需要Activity和Fragment作为载体、不需要去重写onPermissionResult。

2、去除版本判断。只需要在一个工具类中把某个方法(如打电话)适配,然后全局调用,做到真正的运行时请求。

3、一行代码完成从权限检查、请求、到最终完成后做事情。

答案当然是有,下面是我们今天的主角:

SoulPermission

SoulPermission应运而生。

https://github.com/soulqw/SoulPermission/

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

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

解决问题:

1、解耦Activity和Fragment、不再需要Context、不再需要onPermissionResult

2、内部涵盖版本判断,一行代码解决权限相关操作,无需在调用业务方写权限适配代码,继而实现真正调用时请求的“真运行时权限”

3、接入成本低,零入侵,仅需要在gradle配置一行代码

大致工作流程:

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

A:可以做

B:不能做

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

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

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

上述便是其在满足运行时权限下的完整工作流程。

那么关于版本兼容呢?

针对部分手机6.0以下手机,SoulPermission也做了兼容,可以通过AppOpps 检查权限,内部将权限名称做了相应的映射,它的大体流程就是下图:

(这个检查结果不一定准确,但是即使不准确,也默认成功(A),保证我们回调能往下走,不会阻塞流程,其他自己实现了权限系统的手机,如vivo,魅族等也是走此方法,最终走他们自己的权限申请流程)

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

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;
 }
 @Override
 public 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() {
 @Override
 public 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() {
 @Override
 public 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() {
 @Override
 public void run() {
 //retry
 makeCall(context, phoneNumber);
 }
 }) {
 @Override
 public 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() {
 @Override
 public void onClick(View view) {
 UtilsWithPermission.makeCall(getActivity(), "10086");
 }
});

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

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

public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {
 @Override
 public void onPermissionDenied(Permission permission) {
 //SoulPermission提供栈顶Activity
 Activity activity = SoulPermission.getInstance().getTopActivity();
 if (null == activity) {
 return;
 }
 String permissionDesc = permission.getPermissionNameDesc();
 new AlertDialog.Builder(activity)
 .setTitle("提示")
 .setMessage(permissionDesc + "异常,请前往设置->权限管理,打开" + permissionDesc + "。")
 .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
 @Override
 public 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() {
 @Override
 public 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() {
 @Override
 public void onClick(View view) {
 UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);
 }
});

我们再来看看效果:

主要源码分析

1、优雅的避掉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;
 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 //当状态发生改变,比如设备旋转时候,Fragment不会被销毁
 setRetainInstance(true);
 }
 /**
 * 外部请求的最终调用方法
 * @param permissions 权限
 * @param listener 回调
 */
 @TargetApi(M)
 @Override
 public void requestPermissions(String[] permissions, RequestPermissionListener listener) {
 requestPermissions(permissions, REQUEST_CODE);
 this.listener = listener;
 }
 @TargetApi(M)
 @Override
 public 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() {
 @Override
 public 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怎么来呢?

2、再舍去Activity

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

public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {
 WeakReference<Activity> topActWeakReference;
 @Override
 public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
 //原则上只需要onResume,兼容如果在onCreate的时候做权限申请保证此时有Activity对象
 topActWeakReference = new WeakReference<>(activity);
 }
 //.....
 @Override
 public 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 failed
 if (null == lifecycle || null == lifecycle.topActWeakReference) {
 throw new InitException();
 }
 // activity status error
 if (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 status
 final Activity activity;
 try {
 activity = getContainer();
 } catch (Exception e) {
 //activity status error do not request
 return;
 }
 //......
 //finally request
 requestRuntimePermission(activity, permissions.getPermissions(), listener);
}

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

3、能否更简便一点?

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

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

http://chaosleong.github.io/2017/05/27/How-Lifecycle-aware-Components-actually-works/

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

和它的Manifest文件:

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

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

public class InitProvider extends ContentProvider {
 @Override
 public 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) {
 //即我们的应用的Application
 installContentProviders(mInitialApplication, Arrays.asList(info));
 }

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

4. 去除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)
 @Override
 public 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 检查结果
 */
 @Override
 public 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);
 ...
 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);
 }
}

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

总结:

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

  1. 支持多项权限同时请求
  2. 支持检查通知权限
  3. 支持系统权限页面跳转
  4. 支持debug模式
  5. https://github.com/soulqw/SoulPermission

想学习更多Android知识,或者获取相关资料请关注我,并私信回复【资料】。 有面试资源系统整理分享,Java语言进阶和Kotlin语言与Android相关技术内核,APP开发框架知识, 360°Android App全方位性能优化。Android前沿技术,高级UI、Gradle、RxJava、小程序、Hybrid、 移动架构师专题项目实战环节、React Native、等技术教程!架构师课程、NDK模块开发、 Flutter等全方面的 Android高级实践技术讲解。还有在线答疑

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码