美文网首页
Android应用模板之页面准入框架与动态权限检查

Android应用模板之页面准入框架与动态权限检查

作者: 唐洪峰 | 来源:发表于2019-08-06 12:41 被阅读0次

    应用模板代码地址:https://github.com/thfhongfeng/AndroidAppTemplate

    界面准入框架和动态权限检查均集成在基础的Activity中,以注解方式使用

    界面准入框架

    目的
    1. 通用准入条件(比如登陆,会员等)统一化。
    2. 在具体开发中,注解需要的准入条件和参数即可,减少重复代码。
    使用方式
    @UiAccessAnnotation(AccessTypes = {UiAccessType.LOGIN, UiAccessType.VIP_LEVEL}, AccessArgs = {"", "100"},
            AccessActions = {UiAccessAction.LOGIN_ACCESS_FALSE_ON_RESUME_NOT_GO_LOGIN,
                    UiAccessAction.LOGIN_ACCESS_FALSE_ON_CREATE_NOT_FINISH_UI})
    public class MvpTravelNoteReleaseActivity
    
    检查入口

    com.pine.tool.ui.Activity.java, com.pine.tool.ui.Fragment.java(以Activity为例):

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mOnAllAccessRestrictionReleasedMethodCalled = false;
            beforeInitOnCreate(savedInstanceState);
            setContentView(savedInstanceState);
    
            findViewOnCreate();
    
            // 进入界面准入流程
            mUiAccessReady = true;
            UiAccessAnnotation uiAccessAnnotation = getClass().getAnnotation(UiAccessAnnotation.class);
            if (uiAccessAnnotation != null) {
                mUiAccessTypes = uiAccessAnnotation.AccessTypes();
                mUiAccessArgs = uiAccessAnnotation.AccessArgs();
                String[] actions = uiAccessAnnotation.AccessAction();
                if (actions != null && actions.length > 0) {
                    for (String action : actions) {
                        mUiAccessActionsMap.put(action, action);
                    }
                }
            }
            if (!UiAccessManager.getInstance().checkCanAccess(this,
                    UiAccessTimeInterval.UI_ACCESS_ON_CREATE, mUiAccessTypes, mUiAccessArgs,
                    mUiAccessActionsMap)) {
                mUiAccessReady = false;
                onUiAccessForbidden(UiAccessTimeInterval.UI_ACCESS_ON_CREATE);
            }
    
            // 进入动态权限判断和申请流程
            mPermissionReady = true;
            PermissionsAnnotation permissionsAnnotation = getClass().getAnnotation(PermissionsAnnotation.class);
            if (permissionsAnnotation != null) {
                String[] permissions = permissionsAnnotation.Permissions();
                if (permissions != null) {
                    if (!hasPermissions(permissions)) {
                        mPermissionReady = false;
                        requestPermission(REQUEST_ACCESS_PERMISSION, null, permissions);
                    }
                }
            }
    
            tryInitOnAllRestrictionReleased();
        }
    

    检查方式很简单,通过UiAccessManager.getInstance().checkCanAccess(this)进行判断,通过mUiAccessReady参数标识检查结果。如果检查不通过,则在后面的tryInitOnAllRestrictionReleased中不会进行初始化,也就是说非准入界面是静态的,不会动态初始化。开发者还可以重写onUiAccessForbidden,定制当前界面检查不通过时的额外行为。

    private void tryInitOnAllRestrictionReleased() {
            if (!mOnAllAccessRestrictionReleasedMethodCalled &&
                    mUiAccessReady && mPermissionReady) {
                mOnAllAccessRestrictionReleasedMethodCalled = true;
                onAllAccessRestrictionReleased();
            }
        }
    
     private void onAllAccessRestrictionReleased() {
            if (!parseIntentData()) {
                init();
                afterInit();
            }
        }
    

    检查分三个阶段:onCreate阶段,onNewIntent阶段,onResume阶段这三个阶段为Activity的创建和恢复入口,因此在这三个阶段进行检查。检查之后都会尝试初始化界面(如果界面没有初始化的话)。

    // UiAccess检查阶段
    public enum UiAccessTimeInterval {
        UI_ACCESS_ON_CREATE,
        UI_ACCESS_ON_NEW_INTENT,
        UI_ACCESS_ON_RESUME
    }
    

    开发者通过UiAccessAnnotation的AccessActions参数来定制检查不通过时的行为。
    比如登陆准入检查,默认行为在检查失败后会结束当前界面,并跳转到登陆界面。开发者可以通过Args参数来阻止这些行为,并可以自定义自己的行为(通过修改UiAccessLoginExecutor)。

    public interface UiAccessArgs {
    
        // UiAccessTimeInterval.UI_ACCESS_ON_CREATE阶段的UI Login准入检查不通过时,不跳转到登陆界面
        String LOGIN_ACCESS_FALSE_ON_CREATE_NOT_GO_LOGIN = "login_access_false_on_create_not_go_login";
        // UiAccessTimeInterval.UI_ACCESS_ON_CREATE阶段的UI Login准入检查不通过时,不结束当前UI
        String LOGIN_ACCESS_FALSE_ON_CREATE_NOT_FINISH_UI = "login_access_false_on_create_not_finish_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_NEW_INTENT阶段的UI Login准入检查不通过时,不跳转到登陆界面
        String LOGIN_ACCESS_FALSE_ON_NEW_INTENT_NOT_GO_LOGIN = "login_access_false_on_new_intent_not_go_login";
        // UiAccessTimeInterval.UI_ACCESS_ON_NEW_INTENT阶段的UI Login准入检查不通过时,不结束当前UI
        String LOGIN_ACCESS_FALSE_ON_NEW_INTENT_NOT_FINISH_UI = "login_access_false_on_new_intent_not_finish_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_RESUME阶段的UI Login准入检查不通过时,不跳转到登陆界面
        String LOGIN_ACCESS_FALSE_ON_RESUME_NOT_GO_LOGIN = "login_access_false_on_resume_not_go_login";
        // UiAccessTimeInterval.UI_ACCESS_ON_RESUME阶段的UI Login准入检查不通过时,不结束当前UI
        String LOGIN_ACCESS_FALSE_ON_RESUME_NOT_FINISH_UI = "login_access_false_on_resume_not_finish_ui";
    
        // UiAccessTimeInterval.UI_ACCESS_ON_CREATE阶段的UI Vip准入检查不通过时,不跳转到VIP界面
        String VIP_ACCESS_FALSE_ON_CREATE_NOT_GO_VIP_UI = "vip_access_false_on_create_not_go_vip_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_CREATE阶段的UI Vip准入检查不通过时,不结束当前UI
        String VIP_ACCESS_FALSE_ON_CREATE_NOT_FINISH_UI = "vip_access_false_on_create_not_finish_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_NEW_INTENT阶段的UI Vip准入检查不通过时,不跳转到VIP界面
        String VIP_ACCESS_FALSE_ON_NEW_INTENT_NOT_GO_VIP_UI = "vip_access_false_on_new_intent_not_go_vip_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_NEW_INTENT阶段的UI Vip准入检查不通过时,不结束当前UI
        String VIP_ACCESS_FALSE_ON_NEW_INTENT_NOT_FINISH_UI = "vip_access_false_on_new_intent_not_finish_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_RESUME阶段的UI Vip准入检查不通过时,不跳转到VIP界面
        String VIP_ACCESS_FALSE_ON_RESUME_NOT_GO_VIP_UI = "vip_access_false_on_resume_not_go_vip_ui";
        // UiAccessTimeInterval.UI_ACCESS_ON_RESUME阶段的UI Vip准入检查不通过时,不结束当前UI
        String VIP_ACCESS_FALSE_ON_RESUME_NOT_FINISH_UI = "vip_access_false_on_resume_not_finish_ui";
    }
    
    检查方式

    com.pine.tool.access.UiAccessManager.java

        public boolean checkCanAccess(@NonNull Activity activity, UiAccessTimeInterval accessTimeInterval,
                                      @NonNull String[] types, @NonNull String[] args,
                                      @NonNull HashMap<String, String> actionsMap) {
            if (activity == null || types == null || args == null ||
                    types.length < 1 || types.length != args.length) {
                return true;
            }
            for (int i = 0; i < types.length; i++) {
                if (mAccessExecutorMap.get(types[i]) != null &&
                        !mAccessExecutorMap.get(types[i]).onExecute(activity, args[i], actionsMap, accessTimeInterval)) {
                    return false;
                }
            }
            return true;
        }
    

    首先看这个界面的是否需要界面注入检查,如果有类注解UiAccessAnnotation,说明需要准入检查。然后通过遍历mAccessExecutorMap中保存的IUiAccessExecutor,找到Type值与UiAccessAnnotation注解中一致的IUiAccessExecutor进行界面准入检查处理。下图所示的就是登陆注入检查的执行器UiAccessLoginExecutor的检查过程

        @Override
        public boolean onExecute(final Activity activity, String arg, HashMap<String, String> actionsMap,
                                 UiAccessTimeInterval accessTimeInterval) {
            boolean canAccess = BaseApplication.isLogin();
            if (!canAccess) {
                if (!doNotGoLoginActivity(actionsMap, accessTimeInterval)) {
                    BaseRouterClient.goLoginActivity(activity, null, new IRouterCallback() {
                        @Override
                        public void onSuccess(Bundle responseBundle) {
    
                        }
    
                        @Override
                        public boolean onFail(int failCode, String errorInfo) {
                            if (activity != null && !activity.isFinishing()) {
                                activity.finish();
                            }
                            return true;
                        }
                    });
                }
                if (!doNotFinishActivity(actionsMap, accessTimeInterval)) {
                    activity.finish();
                }
            }
            return canAccess;
        }
    

    那这个mAccessExecutorMap中的IUiAccessExecutor从何而来呢?还是UiAccessManager方法中:

     public void addAccessExecutor(String key, IUiAccessExecutor accessExecutor) {
            mAccessExecutorMap.put(key, accessExecutor);
        }
    
        public void removeAccessExecutor(IUiAccessExecutor accessExecutor) {
            mAccessExecutorMap.remove(accessExecutor);
        }
    

    原来是在App初始化的时候添加进来的,TemplateApplication的onCreate方法中执行initManager:

     private void initManager() {
            ……
    
            UiAccessManager.getInstance().addAccessExecutor(UiAccessType.LOGIN,
                    new UiAccessLoginExecutor());
    
            UiAccessManager.getInstance().addAccessExecutor(UiAccessType.VIP_LEVEL,
                    new UiAccessVipLevelExecutor());
        }
    

    总结一下界面准入检查过程:

    1. 通过实现IUiAccessExecutor,写好自己的界面准入检查执行类;
    2. 在App初始化时将该执行类添加到UiAccessManager中;
    3. 在需要进行界面准入检查的界面类中添加UiAccessAnnotation注解;
    4. Activity和Fragment基类实现检查流程。

    动态权限检查

    目的
    1. 动态权限检查统一化。
    2. 在具体开发中,注解需要的动态权限即可,减少重复代码。
    使用方式
    @PermissionsAnnotation(Permissions = {Manifest.permission.ACCESS_FINE_LOCATION})
    public class MvpHomeActivity
    
    检查入口
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mOnAllAccessRestrictionReleasedMethodCalled = false;
            beforeInitOnCreate(savedInstanceState);
            setContentView(savedInstanceState);
    
            findViewOnCreate();
    
            // 进入界面准入流程
            mUiAccessReady = true;
            UiAccessAnnotation uiAccessAnnotation = getClass().getAnnotation(UiAccessAnnotation.class);
            if (uiAccessAnnotation != null) {
                mUiAccessTypes = uiAccessAnnotation.AccessTypes();
                mUiAccessArgs = uiAccessAnnotation.AccessArgs();
                String[] actions = uiAccessAnnotation.AccessActions();
                if (actions != null && actions.length > 0) {
                    for (String action : actions) {
                        mUiAccessActionsMap.put(action, action);
                    }
                }
            }
            if (!UiAccessManager.getInstance().checkCanAccess(this,
                            UiAccessTimeInterval.UI_ACCESS_ON_CREATE, mUiAccessTypes, mUiAccessArgs,
                            mUiAccessActionsMap)) {
                mUiAccessReady = false;
                onUiAccessForbidden(UiAccessTimeInterval.UI_ACCESS_ON_CREATE);
            }
    
            // 进入动态权限判断和申请流程
            mPermissionReady = true;
            PermissionsAnnotation permissionsAnnotation = getClass().getAnnotation(PermissionsAnnotation.class);
            if (permissionsAnnotation != null) {
                String[] permissions = permissionsAnnotation.Permissions();
                if (permissions != null) {
                    if (!hasPermissions(permissions)) {
                        mPermissionReady = false;
                        requestPermission(REQUEST_ACCESS_PERMISSION, null, permissions);
                    }
                }
            }
    
            tryInitOnAllRestrictionReleased();
        }
    
      public void requestPermission(int requestCode, IPermissionCallback callback,
                                      @Size(min = 1) @NonNull String... perms) {
            PermissionManager.requestPermission(this, requestCode, callback, perms);
        }
    
       public static void requestPermission(@NonNull Activity activity, int requestCode,
                                             IPermissionCallback callback,
                                             @Size(min = 1) @NonNull String... perms) {
            PermissionBean bean = new PermissionBean(requestCode, perms);
            bean.setRationaleContent(activity.getString(R.string.tool_rationale_need));
            bean.setCallback(callback);
            requestPermission(activity, bean);
        }
    

    首先看这个界面的是否需要动态权限检查,如果有类注解PermissionsAnnotation ,说明需要动态权限检查。然后获取注解的Permissions属性,得到需要的动态权限。如果没有这些权限,调用requestPermission进行动态权限申请。requestPermission最终是通过EasyPermission这个动态权限检查框架来实现的。EasyPermission是github上比较流行的一个动态权限检查库,读者可以自行去了解下,这里就不在赘述。

    综上,使用本框架:

    1. 页面级的动态权限检查,只需要添加相关注解就可以了;
    2. 方法功能级的动态权限检查,这通过requestPermission来进行即可。

    权限检查中关于onResume和init的执行顺序说明:
    因为EasyPermission实质是另起了一个界面,所以当前Activity在进行权限检查时,会先onResume,再onPause,权限检查完后会先执行init,再执行onResume。

    综上有两种情况:

    1. 不执行权限检查时,onCreate(onNewIntent)-->init-->onResume;
    2. 执行权限检查时,onCreate(onNewIntent)-->onResume-->onPause-->init-->onResume;
      这就会出现在onResume、onPause的时候有可能init未执行,
      具体使用的时候如果onResume、onPause中有需要在init之后才能做的操作时,有以下两种方式:
      a. 在onResume可以使用isInit方法来判断;
      b. 不重写onResume,而通过重写onRealResume来做onResume操作。
      推荐通过重写onRealResume来解决以上问题。

    相关文章

      网友评论

          本文标题:Android应用模板之页面准入框架与动态权限检查

          本文链接:https://www.haomeiwen.com/subject/cemtdctx.html