Android如何通过aspectj打造一个无侵入式动态权限申请框架
目录
一,背景
二,通过Aspectj管理所有的注解
三,配置注解
四,通过空白Activity完成真正的权限申请
五,引入依赖配置
一,背景
在Activity或者fragment中,写在几个方法写一些注释,用来表示权限申请成功
,申请失败
,多次拒绝
。同时需要无侵入式,让业务开发者尽可能的少些代码,把核心的业务逻辑下沉到框架层
二,通过Aspectj管理所有的注解
它的作用就是劫持被注释的方法的执行。我在ASPECT中配置拦截@permission注释的方法。先做判断。如果没有了解过Aspect的话,AOP面向切面编程,大家应该听说过,它可以用来配置事务、做日志、权限验证、在用户请求时做一些处理等等。而用@Aspect做一个切面,就可以直接实现。
package com.example.myapplication;import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;import java.lang.reflect.Method;import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;@Aspect
public class PermissionAspect {//com.example.myapplicationprivate static final String POINTCUT_METHOD = "execution(@com.example.myapplication.Permission * *(..))";@Pointcut(POINTCUT_METHOD)public void methodAnnotatedWithPermission() {}@Around("methodAnnotatedWithPermission()")public Object permissionMethod(final ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Permission permission = method.getAnnotation(Permission.class);String[] permissions = permission.value();int requestCode = permission.requestCode();Object object = joinPoint.getThis();Context context = null;if (object instanceof Activity) {context = (Activity) object;} else if (object instanceof FragmentActivity) {context = (FragmentActivity) object;} else if (object instanceof Fragment) {context = ((Fragment) object).getContext();} else if (object instanceof Service) {context = (Service) object;}Object o = null;if (checkPermissions(context, permissions)) {o = joinPoint.proceed();} else {Intent intent = new Intent();intent.setClass(context, PermissionActivity.class);intent.putExtra("permissions", permissions);intent.putExtra("requestcode", requestCode);context.startActivity(intent);}return o;}private boolean checkPermission(Context context, String permission) {if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {return true;}return false;}private boolean checkPermissions(Context context, String[] permissions) {for(String permission : permissions) {if (!checkPermission(context, permission)) {return false;}}return true;}}
这样@Permission就被切点劫持了,然后方法就会跑到切面aProceedingJoinPoint。然后获取上下文Context,把权限请求交给一个透明的Activity来做。做完之后判断结果,用户是同意了还是拒绝了还是曲线了。同意了直接执行point.proceed(),其他方式则通过Activity或者fragment获取带注解的方法,反射执行即可。
三,配置注解
package com.example.myapplication;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {String[] value();int requestCode() default 1;
}
四,通过空白Activity完成真正的权限申请
package com.example.myapplication;import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;import java.util.ArrayList;
import java.util.List;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;public class PermissionActivity extends Activity {private String[] permissions;private int requestCode;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Intent intent = getIntent();permissions = intent.getStringArrayExtra("permissions");requestCode = intent.getIntExtra("requestcode", 0);setContentView(R.layout.activity_permission);if (permissions != null && permissions.length > 0) {requestPermission(permissions);}}private void requestPermission(String[] permissions) {List<String> failure = new ArrayList<>();for (String permission : permissions) {if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {failure.add(permission);}}if (failure.size() == 0) {requestPermissionSuccess();return;}ActivityCompat.requestPermissions(this, failure.toArray(new String[]{}), requestCode);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {if (requestCode == this.requestCode) {if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {requestPermissionSuccess();} else {boolean alwaysHidePermission = false;for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {//判断是否勾选禁止后不再询问boolean showRequestPermission = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);if (!showRequestPermission) {alwaysHidePermission = true;}}}requestPermissionFailed();}}super.onRequestPermissionsResult(requestCode, permissions, grantResults);}private void requestPermissionSuccess() {setResult(RESULT_OK);finish();}private void requestPermissionFailed() {setResult(RESULT_CANCELED);finish();}
}
五,引入依赖配置
apply plugin: 'com.android.application'buildscript {repositories {mavenCentral()}dependencies {classpath 'org.aspectj:aspectjtools:1.8.9'classpath 'org.aspectj:aspectjweaver:1.8.9'}
}android {compileSdkVersion 29buildToolsVersion "29.0.2"defaultConfig {applicationId "com.netease.premissionstudy"minSdkVersion 19targetSdkVersion 29versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'implementation 'org.aspectj:aspectjrt:1.8.13'
}import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Mainfinal def log = project.logger
final def variants = project.android.applicationVariantsvariants.all { variant ->if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.8","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {repositories {google()jcenter()}dependencies {classpath 'com.android.tools.build:gradle:3.5.2'classpath 'org.aspectj:aspectjtools:1.8.9'classpath 'org.aspectj:aspectjweaver:1.8.9'// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}
}allprojects {repositories {google()jcenter()}
}task clean(type: Delete) {delete rootProject.buildDir
}