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

简介: 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");
            }
        }
    }
    @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等。虽然也能实现我们的功能,但无论自己适配还是现有开源库方案大体上都会或多或少有以下几个问题:

现有权限库存在的问题:

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

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

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

对权限适配的期望:

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

带着上述几个问题,我们今天的主角: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();
                    }
                });
    }
复制代码

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() {
                    @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也做了兼容,可以通过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;
    }
    @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);
            }
        });
复制代码

代码细节请参考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;
    @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怎么来呢?我们继续想办法。

再舍去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初始化即可。

能否更简便一点?

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

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

//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都没有关系了。

去除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);
            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);
        }
    }
复制代码


相关文章
|
4月前
|
存储 安全 Android开发
"解锁Android权限迷宫:一场惊心动魄的动态权限请求之旅,让你的应用从平凡跃升至用户心尖的宠儿!"
【8月更文挑战第13天】随着Android系统的更新,权限管理变得至关重要。尤其从Android 6.0起,引入了动态权限请求,增强了用户隐私保护并要求开发者实现更精细的权限控制。本文采用问答形式,深入探讨动态权限请求机制与最佳实践,并提供示例代码。首先解释了动态权限的概念及其重要性;接着详述实现步骤:定义、检查、请求权限及处理结果;最后总结了六大最佳实践,包括适时请求、解释原因、提供替代方案、妥善处理拒绝情况、适应权限变更及兼容旧版系统,帮助开发者打造安全易用的应用。
81 0
|
28天前
|
开发框架 前端开发 Android开发
探索安卓和iOS应用开发中的跨平台解决方案
【10月更文挑战第42天】在移动应用开发的广阔天地中,安卓和iOS系统如同两座巍峨的山峰,分别占据着半壁江山。开发者们在这两座山峰之间穿梭,努力寻找一种既能节省资源又能提高效率的跨平台开发方案。本文将带你走进跨平台开发的世界,探讨各种解决方案的优势与局限,并分享一些实用的代码示例,助你在应用开发的道路上更加游刃有余。
|
1月前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
2月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
91 7
|
3月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
|
2月前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
24 0
|
3月前
|
存储 API Android开发
"解锁Android权限迷宫:一场惊心动魄的动态权限请求之旅,让你的应用从平凡跃升至用户心尖的宠儿!"
随着Android系统的更新,权限管理成为应用开发的关键。尤其在Android 6.0(API 级别 23)后,动态权限请求机制的引入提升了用户隐私保护,要求开发者进行更精细的权限管理。
75 2
|
3月前
|
调度 Android开发 UED
Android经典实战之Android 14前台服务适配
本文介绍了在Android 14中适配前台服务的关键步骤与最佳实践,包括指定服务类型、请求权限、优化用户体验及使用WorkManager等。通过遵循这些指南,确保应用在新系统上顺畅运行并提升用户体验。
254 6
|
4月前
|
前端开发 开发工具 Android开发
探索安卓与iOS应用开发:跨平台解决方案的崛起
【8月更文挑战第27天】在移动设备日益普及的今天,安卓和iOS系统占据了市场的主导地位。开发者们面临着一个重要问题:是选择专注于单一平台,还是寻找一种能够同时覆盖两大系统的解决方案?本文将探讨跨平台开发工具的优势,分析它们如何改变了移动应用的开发格局,并分享一些实用的开发技巧。无论你是新手还是资深开发者,这篇文章都将为你提供有价值的见解和建议。
|
4月前
|
Android开发
Android编译出现Warning: Mapping new ns to old ns的解决方案
Android编译出现Warning: Mapping new ns to old ns的解决方案
399 3