我们都知道,在Android6.0后,权限申请需要动态授权处理,才能通过。这样的设计也更加符合现在用户的安全体验。那么,对于一个应用,我们可能在不同的场景,需要多次申请不同的权限。比如,在做缓存的时候,需要申请sd卡读写权限,在拍照上传图片的时候,需要申请拍照权限等。对于这样的请求,我们一般是怎样去处理封装的呢?
项目地址
一、常见的权限处理封装
可以看到,这样处理并不理想,耦合性还是太高了。
那么,有没有更加优越的方法处理呢?最终到达请求发出端,跟请求处理端完全隔离开来呢?方法肯定是有的。这里,所用到的核心就是AOP编程。
二、AOP介绍
1、AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2、它是一种关注点分离的技术。我们软件开发时经常提一个词叫做“业务逻辑”或者“业务功能”,我们的代码主要就是实现某种特定的业务逻辑。但是我们往往不能专注于业务逻辑,比如我们写业务逻辑代码的同时,还要写事务管理、缓存、日志等等通用化的功能,而且每个业务功能都要和这些业务功能混在一起,非常非常地痛苦。为了将业务功能的关注点和通用化功能的关注点分离开来,就出现了AOP技术。
3、关于AOP的语法,可以先去看看,这里就不详细说明了。主要关注的点是:
1)如何选取切点?
2)对切面的内容,如何处理?
三、AOP权限处理
1、终于到正题了。这里方便理解,先上一张图来说明:
2、代码过程
1)权限申请
1、在清单文件中申明:略
2、
/**
* 发出权限申请请求
*/
@Permission(values = {Manifest.permission.ACCESS_FINE_LOCATION},requestCode = 200)
private void requestRequest200() {
Toast.makeText(this, "请求定位权限成功,200", Toast.LENGTH_SHORT).show();
}
@PermissionCancled(requestCode = 200)
private void cancelCode200(){
Toast.makeText(this, "取消__200", Toast.LENGTH_SHORT).show();
}
/**
* @return
* true 按照框架内部定义的执行,权限请求拒绝
* false 自定义权限请求拒绝内容
*/
@PermissionDenied(requestCode = 200)
private boolean denyCode200(){
Toast.makeText(this, "禁止__200", Toast.LENGTH_SHORT).show();
return true;
}
2)AOP处理权限过程
此过程发生在项目编译时进行
@Aspect
public class PermissionAspect {
private static final String TAG = "PermissionAspect";
//定义切面的规则
//1.就在原来应用中哪些注释的地方放到当前切面进行处理
//execution(注释名 注释用的地方)
// 1、 execution( @com.dn.tim.lib_permission.annotation.Permission(切点函数)
// *(类名,*表示任意的类都可以使用切点函数) *(方法名,*表示任意方法)(..(方法的参数,..表示任意参数)) )
// 2、@annotation(permission):传入切点函数需要传入的参数是注解类型的permission
@Pointcut("execution(@com.alin.commonlibrary.permission.annotation.Permission * * (..)) && @annotation(permission)")
public void requestPermission(Permission permission){
Log.i(TAG,"Pointcut===>");
}
//2.对进入切面的内容如何处理
//@Before() 在切入点之前运行
//@After() 在切入点之后运行
//@Around() 在切入点前后都运行
@Around("requestPermission(permission)")
public void aroundJointPoint(final ProceedingJoinPoint joinPoint, final Permission permission){
final Object object = joinPoint.getThis();
Context context = null;
/**
* 兼容Fragment、Service、Activity处理
*/
if (object instanceof Context) {
context = (Context) object;
}else if (object instanceof Fragment){
context = ((Fragment)object).getActivity();
}else if (object instanceof android.app.Fragment){
context = ((android.app.Fragment)object).getActivity();
}
if (context == null || permission == null) {
Log.d(TAG, "aroundJonitPoint error ");
return;
}
int statusBarColor = PermissionUtil.findStatusBarColor(object);
final Context finalContext = context;
PermissionActivity.requestPermission(context, permission.requestCode(), permission.values(),statusBarColor ,new IPermission() {
@Override
public void granted() {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void denied(String[] permissions, int code) {
}
@Override
public void canceled(String[] permissions, int code) {
}
});
}
3)PermissionActivity的权限处理过程
PermissionActivity要求:
1、PermissionActivity主题必须是完全透明的。
2、由于不同项目中,状态栏statusBar的颜色设置都不一样,为了不让用户发现这个假的PermissionActivity,需要提供设置状态栏的方法。
3、PermissionActivity的进入和退出动画都要被禁止掉。
4、启动模式必须是单例的。
public class PermissionActivity extends Activity{
public static final String PERMISSION_VALUE = "permission_value";
public static final String PERMISSION_CODE = "permission_code";
public static final String STATUS_BAR_COLOR = "status_bar_color";
private static IPermission sCallback;
public static void requestPermission(Context context, int code, String[] value,int statusBarColor,IPermission callback) {
sCallback = callback;
Intent intent = new Intent(context,PermissionActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Bundle bundle = new Bundle();
bundle.putStringArray(PERMISSION_VALUE,value);
bundle.putInt(PERMISSION_CODE,code);
bundle.putInt(STATUS_BAR_COLOR,statusBarColor);
intent.putExtras(bundle);
context.startActivity(intent);
if (context instanceof Activity) {
((Activity)context).overridePendingTransition(0,0);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permssion);
Intent intent = getIntent();
if (intent == null || intent.getExtras() == null || sCallback == null) {
finish();
return;
}
Bundle bundle = intent.getExtras();
String[] permissions = bundle.getStringArray(PERMISSION_VALUE);
int code = bundle.getInt(PERMISSION_CODE);
int statusBarColor = bundle.getInt(STATUS_BAR_COLOR,Integer.valueOf(PermissionUtil.DEFAULT_PERMISSION_COLOR));
// 设置状态栏颜色
if (statusBarColor != Integer.valueOf(PermissionUtil.DEFAULT_PERMISSION_COLOR)) {
PermissionUtil.resetStatusBar(this,statusBarColor);
}
if (PermissionUtil.hasPermission(this,permissions)) {
sCallback.granted();
finish();
return;
}
ActivityCompat.requestPermissions(this,permissions,code);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//请求权限成功
if (PermissionUtil.verifyPermission(this,grantResults)){
sCallback.granted();
finish();
return;
}
//用户点击了不再显示,拒绝授权
if (PermissionUtil.shouldShowRequestPermissionRationale(this,permissions)){
sCallback.denied(permissions,requestCode);
finish();
return;
}
//取消授权
sCallback.canceled(permissions,requestCode);
finish();
return;
}
@Override
public void finish() {
super.finish();
overridePendingTransition(0,0);
}
4)AOP处理回调过程
此过程都是先反射获取权限申请端Activity的注解,最终反射调用被注解修饰的方法。
/**
* 成功
*/
@Override
public void granted() {
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* 拒绝
*/
@Override
public void denied(String[] permissions, int code) {
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionDenied.class, code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
/**
* 取消
*/
@Override
public void canceled(String[] permissions, int code) {
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionCancled.class,code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
再来看下工具类中的方法:
/**
* 其实就是反射调用被注解修饰的方法过程
* @return
* true 按照框架内部定义的执行,权限请求拒绝、取消
* false 自定义权限请求拒绝、取消内容
*/
public static <T extends Annotation> boolean invokeAnnotation(Object target, Class<T> annotationClass, int code) {
Method[] methods = target.getClass().getDeclaredMethods();
if (methods == null || methods.length == 0 || annotationClass == null) {
return false;
}
for (Method method : methods) {
T annotation = method.getAnnotation(annotationClass);
boolean isFindCode = false;
if (annotation instanceof PermissionCancled) {
isFindCode = ((PermissionCancled)annotation).requestCode() == code;
Log.e(TAG,"caceled : code="+ code + ", realCode=" + ((PermissionCancled)annotation).requestCode() );
}else if (annotation instanceof PermissionDenied){
isFindCode = ((PermissionDenied)annotation).requestCode() == code;
Log.e(TAG,"denied , code="+ code + ", realCode=" + ((PermissionDenied)annotation).requestCode() );
}
boolean isHasAnnotation = method.isAnnotationPresent(annotationClass);
if (isHasAnnotation && isFindCode){
try {
method.setAccessible(true);
Object invoke = method.invoke(target);
LogUtil.i(TAG,"invoke====>" + invoke);
if (invoke != null && invoke instanceof Boolean) {
return (boolean) invoke;
}else if (invoke == null){
return true;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return false;
}
5)补充说明
1、如何动态的设置透明PermissionActivity的statusBar颜色?
由于AOP是编译时自动处理框架,所以,不能对外暴露方法给外面用。此时,也只能通过注解的方式处理。
<1>在权限发起端,定义状态栏色值
@PermissionColor(color = "#C81432")
public class TestPermissionActivity extends CommonActivity{}
<2>在PermissionAspect中反射获取注解色值,然后传给PermissionActivity进行状态栏statusBar改变
//获取注解色值
int statusBarColor = PermissionUtil.findStatusBarColor(object);
//将色值穿给PermissionActivity,从而最终改变statusBar色值。
PermissionActivity.requestPermission(context, permission.requestCode(), permission.values(),statusBarColor ,new IPermission())
2、权限拒绝后的处理?
这里的灵感来自于Android事件处理机制,onTouchEvent(){ return true}。
如果权限发起端Activity中。被@PermissionDenied修饰的方法的返回值来处理的。
1、如果返回的是true,则使用的是框架内部的方法。直接跳到控制面板
2、如果返回的是false,则不使用的是框架内部的方法。用户可以自定义
/**
* @return
* true 按照框架内部定义的执行,权限请求拒绝
* false 自定义权限请求拒绝内容
*/
@PermissionDenied(requestCode = 200)
private boolean denyCode200(){
Toast.makeText(this, "禁止__200", Toast.LENGTH_SHORT).show();
return true;
}
/**
* 拒绝
*/
@Override
public void denied(String[] permissions, int code) {
//如果返回的是true,则使用的是框架内部的方法。直接跳到控制面板
//如果返回的是false,则不使用的是框架内部的方法。用户可以自定义
boolean dispatch = PermissionUtil.invokeAnnotation(object, PermissionDenied.class, code);
if (dispatch) {
PermissionUtil.goToAppMenu(finalContext);
}
}
如果上述有不当的,或者有优化的地方,欢迎指出。
项目地址