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

2019-08-06  本文已影响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来解决以上问题。
上一篇下一篇

猜你喜欢

热点阅读