Android应用模板之页面准入框架与动态权限检查
应用模板代码地址:https://github.com/thfhongfeng/AndroidAppTemplate
界面准入框架和动态权限检查均集成在基础的Activity中,以注解方式使用
界面准入框架
目的
- 通用准入条件(比如登陆,会员等)统一化。
- 在具体开发中,注解需要的准入条件和参数即可,减少重复代码。
使用方式
@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());
}
总结一下界面准入检查过程:
- 通过实现IUiAccessExecutor,写好自己的界面准入检查执行类;
- 在App初始化时将该执行类添加到UiAccessManager中;
- 在需要进行界面准入检查的界面类中添加UiAccessAnnotation注解;
- Activity和Fragment基类实现检查流程。
动态权限检查
目的
- 动态权限检查统一化。
- 在具体开发中,注解需要的动态权限即可,减少重复代码。
使用方式
@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上比较流行的一个动态权限检查库,读者可以自行去了解下,这里就不在赘述。
综上,使用本框架:
- 页面级的动态权限检查,只需要添加相关注解就可以了;
- 方法功能级的动态权限检查,这通过requestPermission来进行即可。
权限检查中关于onResume和init的执行顺序说明:
因为EasyPermission实质是另起了一个界面,所以当前Activity在进行权限检查时,会先onResume,再onPause,权限检查完后会先执行init,再执行onResume。
综上有两种情况:
- 不执行权限检查时,onCreate(onNewIntent)-->init-->onResume;
- 执行权限检查时,onCreate(onNewIntent)-->onResume-->onPause-->init-->onResume;
这就会出现在onResume、onPause的时候有可能init未执行,
具体使用的时候如果onResume、onPause中有需要在init之后才能做的操作时,有以下两种方式:
a. 在onResume可以使用isInit方法来判断;
b. 不重写onResume,而通过重写onRealResume来做onResume操作。
推荐通过重写onRealResume来解决以上问题。