EasyPermissions-- Android权限管理

        当前对于大部分手机来说,Android6.0及以上的版本已经是基本配置了,我们在开发的时候,需要对6.0的新特性进行适配,最让我们熟悉的就是6.0以上权限的动态申请,今天我们来学习一下权限动态申请相关的知识。

        对于动态权限的申请,Google为我们提供了EasyPermissions这个框架,该框架可以帮我们更加简单的完成权限申请的操作。本篇文章我们来学习如何使用这个框架及该框架是怎么封装的。

        先贴上gitHub上google示例的地址  EasyPermissions的GitHub地址


        我们学习一个框架的时候,肯定是想先知道怎么使用这个框架,所以我们先来看EasyPermissions如何使用!

一、如何使用EasyPermissions?

        此处,我们根据上面Google在gitHub中的示例代码进行使用的介绍。示例非常简单,只有两个类,一个Activity,一个Fragment,显示了如何在这两种场景下使用EasyPermissions。

        首先,在我们的build.gradle文件中先添加EasyPermissions

   implementation 'pub.devrel:easypermissions:1.2.0'

        我们先看Activity中的代码是如何写的。

        先在Activity的回调onRequestPermissionsResult(此为我们申请权限的回调)方法中,将回调的数据透传给EasyPermissions,让EasyPermissions帮我们管理权限回调。

  @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // EasyPermissions handles the request result.
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

        我们的准备工作,将权限回调给EasyPermissions帮我们管理之后,就可以进行我们的权限申请了,我们知道,在请求权限的时候,需要先判断是否有这个权限,那么我们来看该如何用EasyPermissions判断是否拥有某个权限。

    private boolean hasCameraPermission() {
        return EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA);
    }
        非常简单,Google给的示例代码中,只需要调用EasyPermissions的hasPermissions方法就行了
  /**
     * Check if the calling context has a set of permissions.
     *
     * @param context the calling context.
     * @param perms   one ore more permissions, such as {@link Manifest.permission#CAMERA}.
     * @return true if all permissions are already granted, false if at least one permission is not
     * yet granted.
     * @see Manifest.permission
     */
    public static boolean hasPermissions(@NonNull Context context,@Size(min = 1) @NonNull String... perms) 
        这个方法的源码介绍是这样的,其是一个可变长参数,当所有权限都满足的时候,返回true,否则返回false。

        当判定有权限的时候,我们去做该做的事就行了,那没有权限怎么办呢?

        当然是用EasyPermissions请求权限啦!

    public void cameraTask() {
        if (hasCameraPermission()) {
            // Have permission, do the thing!
            Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
        } else {
            // Ask for one permission
            EasyPermissions.requestPermissions(
                    this,
                    getString(R.string.rationale_camera),
                    RC_CAMERA_PERM,
                    Manifest.permission.CAMERA);
        }
    }

        请求权限只需要调用EasyPermissions的requestPermissions方法即可,让我们看一下这个方法的各个参数都是什么作用,先看一下源码中的注释。

    /**
     * Request a set of permissions, showing a rationale if the system requests it.
     *
     * @param host        requesting context.
     * @param rationale   a message explaining why the application needs this set of permissions;
     *                    will be displayed if the user rejects the request the first time.
     * @param requestCode request code to track this request, must be < 256.
     * @param perms       a set of permissions to be requested.
     * @see Manifest.permission
     */
    public static void requestPermissions(
            @NonNull Activity host, @NonNull String rationale,
            int requestCode, @Size(min = 1) @NonNull String... perms)
        第一个为Activity对象,第二个是一个字符串,其展示的场景是  当用户第一次拒绝这个权限时,将展示这个信息( 个人手机实际测试,第一次弹出权限请求,点击拒绝并且不再提醒的时候,将没有这个信息展示的场景 。第三个是requestCode,是做校验用的,需要注意的是,必须小于256。第四个参数就是我们需要请求的权限,同样是一个可变长参数。

        接下来我们看四个回调,分别是在我们请求权限时对应的回调信息,EasyPermissions类内部的两个接口。

    /**
     * Callback interface to receive the results of {@code EasyPermissions.requestPermissions()}
     * calls.
     */
    public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {

        void onPermissionsGranted(int requestCode, @NonNull List perms);

        void onPermissionsDenied(int requestCode, @NonNull List perms);
    }

    /**
     * Callback interface to receive button clicked events of the rationale dialog
     */
    public interface RationaleCallbacks {
        void onRationaleAccepted(int requestCode);

        void onRationaleDenied(int requestCode);
    }
        我们的Activity实现这两个接口之后,其对应的回调将传递回来。PermissminCallbacks是权限的回调,当权限被拒绝的时候,会执行onPermissionsDenied方法,当权限通过的时候会执行onPermissionsGranted方法。而RationaleCallbacks就是信息提示框对应的回调,也就是我们上面请求权限第二个参数展示的那个提示框对应的回调,当用户同意的时候,执行onRationaleAccepted方法,取消的时候执行onRationaleDenied方法,也是考虑的很全面了,能满足我们实际使用的场景需求。上面四个方法的参数均是对应权限的requestCode和权限信息,此处不做过多解释。

        当我们不再提示并拒绝权限申请的时候,再次使用EasyPermissions的requestPermissions申请权限,将弹出提示信息,确认后跳转到系统对该应用的设置界面,提醒用户手动打开权限。

EasyPermissions-- Android权限管理_第1张图片

        上面这个界面在示例中是这样调用的:

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List perms) {
        Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());

        // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
        // This will display a dialog directing them to enable the permission in app settings.
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            Log.d(TAG, "AppSettingsDialog show" );
            new AppSettingsDialog.Builder(this).build().show();
        }
    }

        Google自定义了一个AppSettingsDialog(其实是一个Activity),我们可以通过传参来修改显示的样式,跳转相关的信息已经帮我们封装好了,使用起来很便捷。

        另外,在启动这个的时候,实际的启动方式是startActivityForResult的,所以我们应该重写Activity的onActivityResult方法,接收权限设置之后的结果,示例中的参考代码如下:

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
            String yes = getString(R.string.yes);
            String no = getString(R.string.no);

            // Do something after user returned from app settings screen, like showing a Toast.
            Toast.makeText(
                    this,
                    getString(R.string.returned_from_app_settings_to_activity,
                              hasCameraPermission() ? yes : no,
                              hasLocationAndContactsPermissions() ? yes : no,
                              hasSmsPermission() ? yes : no),
                    Toast.LENGTH_LONG)
                    .show();
        }
    }
        判断一下权限,然后做我们需要执行的逻辑。

        当我们的权限请求被允许之后(去系统设置页面的不会直接回调到这里,建议在onActivityResult时处理),需要通过注解接收,Google对其描述是这样的

  • Use of the AfterPermissionGranted annotation. This is optional, but provided for convenience. If all of the permissions in a given request are granted, all methods annotated with the proper request code will be executed(be sure to have an unique request code). The annotated method needs to be void and without input parameters (instead, you can use onSaveInstanceState in order to keep the state of your suppressed parameters). This is to simplify the common flow of needing to run the requesting method after all of its permissions have been granted. This can also be achieved by adding logic on the onPermissionsGranted callback.
大概意思是,使用AfterPermissionGranted注释来标记一个方法,当对应的权限全部被允许之后,将执行这个方法。但是这个方法得参数必须是空的,如果该方法有参数,将会抛出异常,引起程序异退。
    @AfterPermissionGranted(RC_CAMERA_PERM)
    public void cameraTask() {
        if (hasCameraPermission()) {
            // Have permission, do the thing!
            Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
        } else {
            // Ask for one permission
            EasyPermissions.requestPermissions(
                    this,
                    getString(R.string.rationale_camera),
                    RC_CAMERA_PERM,
                    Manifest.permission.CAMERA);
        }
    }

        示例中的这个写法很合理,请求权限后继续执行该方法,进行后续操作,大家可以参考使用。

        最后,对于权限请求的提示框,Google也给我们提供了自定义的接口,

    /**
     * Request a set of permissions.
     *
     * @param request the permission request
     * @see PermissionRequest
     */
    public static void requestPermissions(PermissionRequest request) 
        请求权限的时候,直接传递一个PermissionRequest对象。
    private PermissionRequest(PermissionHelper helper,
                              String[] perms,
                              int requestCode,
                              String rationale,
                              String positiveButtonText,
                              String negativeButtonText,
                              int theme) {
        mHelper = helper;
        mPerms = perms.clone();
        mRequestCode = requestCode;
        mRationale = rationale;
        mPositiveButtonText = positiveButtonText;
        mNegativeButtonText = negativeButtonText;
        mTheme = theme;
    }

        使用PermissionRequest.Builder对象来设置自定义的Request,参考示例如下。

EasyPermissions.requestPermissions(
        new PermissionRequest.Builder(this, RC_CAMERA_AND_LOCATION, perms)
                .setRationale(R.string.camera_and_location_rationale)
                .setPositiveButtonText(R.string.rationale_ask_ok)
                .setNegativeButtonText(R.string.rationale_ask_cancel)
                .setTheme(R.style.my_fancy_style)
                .build());    

        Builder的构造方法中,第一个为Activity或Fragment对象,第二个是requestCode,就是我们一开始使用的Code,在回调的时候使用的,第三个是权限的可变长参数。下面依次为设置提示语,设置按钮的文字,设置style样式。

        Fragment和Activity的使用基本是一致的,大家可以下载文章开头的Google示例代码,一看便知。

        好了,EasyPermissions的使用就到此为止了,上面的示例已经可以满足我们平时开发对于权限的需求了,接下来我们分析一下Google对于EastPermissions是怎么封装的,与我们平时自己直接使用又有哪些有点和好处。


二、EasyPermissions的源码分析

        个人水平有限,此处按照我们请求调用的顺序,跟进源码,做简单的分析,不一定能完全解释每一行源码,有一些内容也可能理解有限,没有完美的理解,尽力而为!

        我们先从请求权限开始,看一下requestPermissions方法。

    public static void requestPermissions(
            @NonNull Activity host, @NonNull String rationale,
            int requestCode, @Size(min = 1) @NonNull String... perms) {
        requestPermissions(
                new PermissionRequest.Builder(host, requestCode, perms)
                        .setRationale(rationale)
                        .build());
    }

        此处直接默认构造了一个PermissionRequest对象,然后调用重载的requestPermissions方法。

/**
     * Request a set of permissions.
     *
     * @param request the permission request
     * @see PermissionRequest
     */
    public static void requestPermissions(PermissionRequest request) {

        // Check for permissions before dispatching the request
        if (hasPermissions(request.getHelper().getContext(), request.getPerms())) {
            notifyAlreadyHasPermissions(
                    request.getHelper().getHost(), request.getRequestCode(), request.getPerms());
            return;
        }

        // Request permissions
        request.getHelper().requestPermissions(
                request.getRationale(),
                request.getPositiveButtonText(),
                request.getNegativeButtonText(),
                request.getTheme(),
                request.getRequestCode(),
                request.getPerms());
    }
        先检测是否有权限,检测方法如下,非常简单,注释也很清楚,此处不再解释。
    /**
     * Check if the calling context has a set of permissions.
     *
     * @param context the calling context.
     * @param perms   one ore more permissions, such as {@link Manifest.permission#CAMERA}.
     * @return true if all permissions are already granted, false if at least one permission is not
     * yet granted.
     * @see Manifest.permission
     */
    public static boolean hasPermissions(@NonNull Context context,
                                         @Size(min = 1) @NonNull String... perms) {
        // Always return true for SDK < M, let the system deal with the permissions
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(TAG, "hasPermissions: API version < M, returning true by default");

            // DANGER ZONE!!! Changing this will break the library.
            return true;
        }

        // Null context may be passed if we have detected Low API (less than M) so getting
        // to this point with a null context should not be possible.
        if (context == null) {
            throw new IllegalArgumentException("Can't check permissions for null context");
        }

        for (String perm : perms) {
            if (ContextCompat.checkSelfPermission(context, perm)
                    != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }

        return true;
    }

        如果有权限,回调onRequestPermissionRequest方法,此方法在我们请求权限成功,Activity的同名方法中,我们也会手动抛给这个方法,所以在后面一起分析,notifyAlreadyHasPermissions方法如下:

    /**
     * Run permission callbacks on an object that requested permissions but already has them by
     * simulating {@link PackageManager#PERMISSION_GRANTED}.
     *
     * @param object      the object requesting permissions.
     * @param requestCode the permission request code.
     * @param perms       a list of permissions requested.
     */
    private static void notifyAlreadyHasPermissions(@NonNull Object object,
                                                    int requestCode,
                                                    @NonNull String[] perms) {
        int[] grantResults = new int[perms.length];
        for (int i = 0; i < perms.length; i++) {
            grantResults[i] = PackageManager.PERMISSION_GRANTED;
        }

        onRequestPermissionsResult(requestCode, perms, grantResults, object);
    }

        再回来继续requestPermissions方法,当没有权限时,通过request对象获取PermissionHelper对象,然后执行requestPermissions方法,将request中的布局信息,请求code,权限内容传了进去。

        requestPermissions方法如下。

    public void requestPermissions(@NonNull String rationale,
                                   @NonNull String positiveButton,
                                   @NonNull String negativeButton,
                                   @StyleRes int theme,
                                   int requestCode,
                                   @NonNull String... perms) {
        if (shouldShowRationale(perms)) {
            showRequestPermissionRationale(
                    rationale, positiveButton, negativeButton, theme, requestCode, perms);
        } else {
            directRequestPermissions(requestCode, perms);
        }
    }

        根据权限先判断是否需要显示提示信息(其实就是确认是否是第一次请求该权限),shouldShowRationale方法如下:

    private boolean shouldShowRationale(@NonNull String... perms) {
        for (String perm : perms) {
            if (shouldShowRequestPermissionRationale(perm)) {
                return true;
            }
        }
        return false;
    }

        遍历这个可变长参数,依次确认是否需要显示提示信息。

        如果不需要展示提示信息,就直接执行directRequestPermissions方法,directRequestPermissions和showRequestPermissionRationale均是抽象方法,那么我们来看PermissionHelper是的具体实现。

        查看PermissionRequest类可以看到,PermissionHelper的初始化是执行PermissionHelper的newInstance方法。

        public Builder(@NonNull Activity activity, int requestCode,
                       @NonNull @Size(min = 1) String... perms) {
            mHelper = PermissionHelper.newInstance(activity);
            mRequestCode = requestCode;
            mPerms = perms;
        }
    @NonNull
    public static PermissionHelper newInstance(Activity host) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return new LowApiPermissionsHelper<>(host);
        }

        if (host instanceof AppCompatActivity)
            return new AppCompatActivityPermissionHelper((AppCompatActivity) host);
        else {
            return new ActivityPermissionHelper(host);
        }
    }

        当小于23的时候,LowApiPermissionsHelper的对应方法都应该是无效的,所以其都抛出了异常或者不作处理,我们正常使用也不应该会执行到这个类里面。

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        throw new IllegalStateException("Should never be requesting permissions on API < 23!");
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return false;
    }

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {
        throw new IllegalStateException("Should never be requesting permissions on API < 23!");
    }

        ActivityPermissionHelper和AppCompatActivityPermissionHelper重写的shouldShowRequestPermissionRationale、directRequestPermissions方法是一样的,均交给了ActivityCompat去处理,和我们自己适配的时候操作一样。代码如下:

    @Override
    public void directRequestPermissions(int requestCode, @NonNull String... perms) {
        ActivityCompat.requestPermissions(getHost(), perms, requestCode);
    }

    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String perm) {
        return ActivityCompat.shouldShowRequestPermissionRationale(getHost(), perm);
    }

        他们的不同点在于showRequestPermissionRationale,我们来看showRequestPermissionRationale的实现。

ActivityPermissionHelper的父类BaseFrameworkPermissionsHelper:

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {
        FragmentManager fm = getFragmentManager();

        // Check if fragment is already showing
        Fragment fragment = fm.findFragmentByTag(RationaleDialogFragment.TAG);
        if (fragment instanceof RationaleDialogFragment) {
            Log.d(TAG, "Found existing fragment, not showing rationale.");
            return;
        }

        RationaleDialogFragment
                .newInstance(positiveButton, negativeButton, rationale, theme, requestCode, perms)
                .showAllowingStateLoss(fm, RationaleDialogFragment.TAG);
    }

AppCompatActivityPermissionHelper的父类BaseSupportPermissionsHelper

    @Override
    public void showRequestPermissionRationale(@NonNull String rationale,
                                               @NonNull String positiveButton,
                                               @NonNull String negativeButton,
                                               @StyleRes int theme,
                                               int requestCode,
                                               @NonNull String... perms) {

        FragmentManager fm = getSupportFragmentManager();

        // Check if fragment is already showing
        Fragment fragment = fm.findFragmentByTag(RationaleDialogFragmentCompat.TAG);
        if (fragment instanceof RationaleDialogFragmentCompat) {
            Log.d(TAG, "Found existing fragment, not showing rationale.");
            return;
        }

        RationaleDialogFragmentCompat
                .newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
                .showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);
    }

        区别是对Fragment操作的不同。接下来是Dialog的展示环节,我们稍后再看,先回到,权限请求的主要流程上面。

        在前面我们看到了onRequestPermissionsResult的调用,Activity的同名方法中供我们也直接透传给了EasyPermissions的onRequestPermissionsResult,现在我们来看onRequestPermissionsResult方法:

    /**
     * Handle the result of a permission request, should be called from the calling {@link
     * Activity}'s {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int,
     * String[], int[])} method.
     * 

* If any permissions were granted or denied, the {@code object} will receive the appropriate * callbacks through {@link PermissionCallbacks} and methods annotated with {@link * AfterPermissionGranted} will be run if appropriate. * * @param requestCode requestCode argument to permission result callback. * @param permissions permissions argument to permission result callback. * @param grantResults grantResults argument to permission result callback. * @param receivers an array of objects that have a method annotated with {@link * AfterPermissionGranted} or implement {@link PermissionCallbacks}. */ public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, @NonNull Object... receivers) { // Make a collection of granted and denied permissions from the request. List granted = new ArrayList<>(); List denied = new ArrayList<>(); for (int i = 0; i < permissions.length; i++) { String perm = permissions[i]; if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { granted.add(perm); } else { denied.add(perm); } } // iterate through all receivers for (Object object : receivers) { // Report granted permissions, if any. if (!granted.isEmpty()) { if (object instanceof PermissionCallbacks) { ((PermissionCallbacks) object).onPermissionsGranted(requestCode, granted); } } // Report denied permissions, if any. if (!denied.isEmpty()) { if (object instanceof PermissionCallbacks) { ((PermissionCallbacks) object).onPermissionsDenied(requestCode, denied); } } // If 100% successful, call annotated methods if (!granted.isEmpty() && denied.isEmpty()) { runAnnotatedMethods(object, requestCode); } } }

        参数不做过多解释,可以看到,将回调的权限分类,先判断是否通过了, 然后调用接收者对应的权限回调,也是我们文章第一部分提到的PermissionCallbacks接口。如果全部成功,执行runAnnotatedMethods方法,看名字就是将执行我们注解所标注的方法,进行后续事件的执行。
    /**
     * Find all methods annotated with {@link AfterPermissionGranted} on a given object with the
     * correct requestCode argument.
     *
     * @param object      the object with annotated methods.
     * @param requestCode the requestCode passed to the annotation.
     */
    private static void runAnnotatedMethods(@NonNull Object object, int requestCode) {
        Class clazz = object.getClass();
        if (isUsingAndroidAnnotations(object)) {
            clazz = clazz.getSuperclass();
        }

        while (clazz != null) {
            for (Method method : clazz.getDeclaredMethods()) {
                AfterPermissionGranted ann = method.getAnnotation(AfterPermissionGranted.class);
                if (ann != null) {
                    // Check for annotated methods with matching request code.
                    if (ann.value() == requestCode) {
                        // Method must be void so that we can invoke it
                        if (method.getParameterTypes().length > 0) {
                            throw new RuntimeException(
                                    "Cannot execute method " + method.getName() + " because it is non-void method and/or has input parameters.");
                        }

                        try {
                            // Make method accessible if private
                            if (!method.isAccessible()) {
                                method.setAccessible(true);
                            }
                            method.invoke(object);
                        } catch (IllegalAccessException e) {
                            Log.e(TAG, "runDefaultMethod:IllegalAccessException", e);
                        } catch (InvocationTargetException e) {
                            Log.e(TAG, "runDefaultMethod:InvocationTargetException", e);
                        }
                    }
                }
            }

            clazz = clazz.getSuperclass();
        }
    }
        开始的判断,如果使用的AndroidAnnotations,就给clazz重新赋值(暂时还未了解该框架,待以后补充原因),然后遍历该对象的方法,查询AfterPermissionGranted注释,当code匹配,并且该方法没有参数的时候,使用invoke执行该方法。

        整体来说这个流程还时没有特别晦涩难懂的,Google用很简洁的语言和逻辑为我们封装了这样一个请求框架,对于我们自己封装的权限请求来说,完全可以参考这个模式来尝试一下。

        接下来我们继续看UI部分的代码是如何编写的。

        以AppCompatActivityPermissionHelper的父类BaseSupportPermissionsHelper作为分析,其调用的代码是这样的

        RationaleDialogFragmentCompat
                .newInstance(rationale, positiveButton, negativeButton, theme, requestCode, perms)
                .showAllowingStateLoss(fm, RationaleDialogFragmentCompat.TAG);

        RationaleDialogFragmentCompat其实就是一个自定义的Dialog,并不是很复杂,源码如下:

/**
 * {@link AppCompatDialogFragment} to display rationale for permission requests when the request
 * comes from a Fragment or Activity that can host a Fragment.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class RationaleDialogFragmentCompat extends AppCompatDialogFragment {

    public static final String TAG = "RationaleDialogFragmentCompat";

    private EasyPermissions.PermissionCallbacks mPermissionCallbacks;
    private EasyPermissions.RationaleCallbacks mRationaleCallbacks;

    public static RationaleDialogFragmentCompat newInstance(
            @NonNull String rationaleMsg,
            @NonNull String positiveButton,
            @NonNull String negativeButton,
            @StyleRes int theme,
            int requestCode,
            @NonNull String[] permissions) {

        // Create new Fragment
        RationaleDialogFragmentCompat dialogFragment = new RationaleDialogFragmentCompat();

        // Initialize configuration as arguments
        RationaleDialogConfig config = new RationaleDialogConfig(
                positiveButton, negativeButton, rationaleMsg, theme, requestCode, permissions);
        dialogFragment.setArguments(config.toBundle());

        return dialogFragment;
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        if (manager.isStateSaved()) {
            return;
        }

        show(manager, tag);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (getParentFragment() != null) {
            if (getParentFragment() instanceof EasyPermissions.PermissionCallbacks) {
                mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) getParentFragment();
            }
            if (getParentFragment() instanceof EasyPermissions.RationaleCallbacks){
                mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) getParentFragment();
            }
        }

        if (context instanceof EasyPermissions.PermissionCallbacks) {
            mPermissionCallbacks = (EasyPermissions.PermissionCallbacks) context;
        }

        if (context instanceof EasyPermissions.RationaleCallbacks) {
            mRationaleCallbacks = (EasyPermissions.RationaleCallbacks) context;
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mPermissionCallbacks = null;
        mRationaleCallbacks = null;
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Rationale dialog should not be cancelable
        setCancelable(false);

        // Get config from arguments, create click listener
        RationaleDialogConfig config = new RationaleDialogConfig(getArguments());
        RationaleDialogClickListener clickListener =
                new RationaleDialogClickListener(this, config, mPermissionCallbacks, mRationaleCallbacks);

        // Create an AlertDialog
        return config.createSupportDialog(getContext(), clickListener);
    }
}

        Dialog在创建时,onCreateDialog方法调用了RationaleDialogConfig的createSupportDialog方法,config对象中既有我们传入的数据,又有新创建的ClickListener对象,看一下这个方法。

    AlertDialog createSupportDialog(Context context, Dialog.OnClickListener listener) {
        AlertDialog.Builder builder;
        if (theme > 0) {
            builder = new AlertDialog.Builder(context, theme);
        } else {
            builder = new AlertDialog.Builder(context);
        }
        return builder
                .setCancelable(false)
                .setPositiveButton(positiveButton, listener)
                .setNegativeButton(negativeButton, listener)
                .setMessage(rationaleMsg)
                .create();
    }
        AlertDialog的创建,并不是很复杂的东西,将我们开始传入的数据试用一下就可以了。

        最后看一下这个ClickListener的onClick方法,当我们点击了之后会怎么做?

    @Override
    public void onClick(DialogInterface dialog, int which) {
        int requestCode = mConfig.requestCode;
        if (which == Dialog.BUTTON_POSITIVE) {
            String[] permissions = mConfig.permissions;
            if (mRationaleCallbacks != null) {
                mRationaleCallbacks.onRationaleAccepted(requestCode);
            }
            if (mHost instanceof Fragment) {
                PermissionHelper.newInstance((Fragment) mHost).directRequestPermissions(requestCode, permissions);
            } else if (mHost instanceof android.app.Fragment) {
                PermissionHelper.newInstance((android.app.Fragment) mHost).directRequestPermissions(requestCode, permissions);
            } else if (mHost instanceof Activity) {
                PermissionHelper.newInstance((Activity) mHost).directRequestPermissions(requestCode, permissions);
            } else {
                throw new RuntimeException("Host must be an Activity or Fragment!");
            }
        } else {
            if (mRationaleCallbacks != null) {
                mRationaleCallbacks.onRationaleDenied(requestCode);
            }
            notifyPermissionDenied();
        }
    }

    private void notifyPermissionDenied() {
        if (mCallbacks != null) {
            mCallbacks.onPermissionsDenied(mConfig.requestCode, Arrays.asList(mConfig.permissions));
        }
    }
        如果点击确认,先回调对应的方法,然后创建对应的PermissionHelper对象,执行后续方法。如果取消,回调对应的方法。

        最后,我们看一下跳转到系统页面的那一部分,就是在选择不再提示后,弹出的AppSettingsDialog如何实现的。

public class AppSettingsDialog implements Parcelable 
        其实这个Dialog是一个数据类,将我们要显示和传递的信息存储下来。
        @NonNull
        public AppSettingsDialog build() {
            mRationale = TextUtils.isEmpty(mRationale) ?
                    mContext.getString(R.string.rationale_ask_again) : mRationale;
            mTitle = TextUtils.isEmpty(mTitle) ?
                    mContext.getString(R.string.title_settings_dialog) : mTitle;
            mPositiveButtonText = TextUtils.isEmpty(mPositiveButtonText) ?
                    mContext.getString(android.R.string.ok) : mPositiveButtonText;
            mNegativeButtonText = TextUtils.isEmpty(mNegativeButtonText) ?
                    mContext.getString(android.R.string.cancel) : mNegativeButtonText;
            mRequestCode = mRequestCode > 0 ? mRequestCode : DEFAULT_SETTINGS_REQ_CODE;

            return new AppSettingsDialog(
                    mActivityOrFragment,
                    mThemeResId,
                    mRationale,
                    mTitle,
                    mPositiveButtonText,
                    mNegativeButtonText,
                    mRequestCode);
        }

        Builder的build方法创建了这个对象,show的时候将这些信息放入Intent传进去,启动了一个新的Activity。

    /**
     * Display the built dialog.
     */
    public void show() {
        startForResult(AppSettingsDialogHolderActivity.createShowDialogIntent(mContext, this));
    }
        AppSettingsDialogHolderActivity并不复杂,里面内置了一个真正的Dialog,当点击的时候也帮我们配置好了对应的Intent,Activity接收到系统设置界面信息之后,结束关闭这个Activity并且将数据传递回去,
public class AppSettingsDialogHolderActivity extends AppCompatActivity implements DialogInterface.OnClickListener {
    private static final int APP_SETTINGS_RC = 7534;

    private AlertDialog mDialog;

    public static Intent createShowDialogIntent(Context context, AppSettingsDialog dialog) {
        return new Intent(context, AppSettingsDialogHolderActivity.class)
                .putExtra(AppSettingsDialog.EXTRA_APP_SETTINGS, dialog);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDialog = AppSettingsDialog.fromIntent(getIntent(), this).showDialog(this, this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == Dialog.BUTTON_POSITIVE) {
            startActivityForResult(
                    new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                            .setData(Uri.fromParts("package", getPackageName(), null)),
                    APP_SETTINGS_RC);
        } else if (which == Dialog.BUTTON_NEGATIVE) {
            setResult(Activity.RESULT_CANCELED);
            finish();
        } else {
            throw new IllegalStateException("Unknown button type: " + which);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        setResult(resultCode, data);
        finish();
    }
}
        好了,这次的分析就到此为止了,从每一个模块看来,其实都是我们平时用到过的东西,并不是很复杂,重要的是将这些东西结合起来,做出这么一个简洁方便的框架。



你可能感兴趣的:(Android)