API 23之前的版本都是自动获取权限,而从 Android 6.0 开始添加了权限申请的需求,更加安全。并且目前android系统已经发展到10.0,加上国家对用户隐私的保护,了解这一技术不可避免。google也为我们提供了RxPermission这一框架RxPermission,有兴趣的可以自行了解。本文将采用AspectJ的方式实现。
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'org.aspectj:aspectjtools:1.8.6'
}
(1)添加依赖
implementation 'org.aspectj:aspectjrt:1.8.13'
(2)添加aspect编译相关
下面这一段代码,是在主module下的配置,如果想要在library中使用,可以自行查看如何配置。
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
//在构建工程时,执行编织
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.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.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
/**
*
* author : QB
* time : 2019/10/21
* version : v1.0.0
* desc : 权限和请求码
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
/**
* 请求权限
*/
String[] value();
/**
* 请求码
*/
int requestCode();
}
/**
*
* author : QB
* time : 2019/10/21
* version : v1.0.0
* desc : 权限申请结果回调
*
*/
public interface PermissionRequestCallback {
/**
* 申请权限成功
*/
void permissionSuccess();
/**
* 申请权限失败,用户点击拒绝了
*/
void permissionCanceled();
/**
* 申请权限失败,用户点击了不再询问
*/
void permissionDenied();
}
(1)判断权限是否已经申请
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null) {
String[] permissions = intent.getStringArrayExtra(REQUEST_PERMISSIONS);
int requestCode = intent.getIntExtra(REQUEST_CODE, REQUEST_CODE_DEFAULT);
//三者一项不符合,结束当前页面
if (permissions == null || requestCode == -1 || requestCallback == null) {
this.finish();
return;
}
//判断是否已经授权了
if(PermissionUtils.hasPermissionRequest(this,permissions)){
requestCallback.permissionSuccess();
this.finish();
return;
}
//开始申请权限
ActivityCompat.requestPermissions(this,permissions,requestCode);
}
}
判断权限是否已经申请,并将结果进行回调。如果没有申请,开始申请权限。
(2)权限申请系统回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//再次判断是否权限真正的申请成功
if(PermissionUtils.requestPermissionSuccess(grantResults)){
requestCallback.permissionSuccess();
this.finish();
return;
}
//权限拒绝,并且用户勾选了不在提示
if(PermissionUtils.shouldShowRequestPermissionRationale(this,permissions)){
requestCallback.permissionCanceled();
this.finish();
return;
}
//权限拒绝
requestCallback.permissionDenied();
this.finish();
}
回调权限申请结果。
核心代码:
//启动透明的activity
DialogActivity.launchDialog(context, permission.value(), permission.requestCode(), new PermissionRequestCallback() {
@Override
public void permissionSuccess() {
//代码继续执行下去
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void permissionCanceled() {
//反射执行permissionCanceled()方法
PermissionUtils.invokeAnnotation(object,PermissionFailed.class);
}
@Override
public void permissionDenied() {
//反射执行permissionDenied()方法
PermissionUtils.invokeAnnotation(object,PermissionDenied.class);
//跳转到系统设置界面
PermissionUtils.startAndroidSettings(finalContext);
}
});
首先拿到当前activity的上下文,然后用它来启动这个权限申请的透明的activity。得到申请的回调结果。三种情况:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionFailed {
/**
* 请求码
*/
int requestCode();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionDenied {
/**
* 请求码
*/
int requestCode();
}
public static void invokeAnnotation(Object object, Class annotationClass) {
// 获取 object 的 Class对象
Class<?> objectClass = object.getClass();
// 遍历 所有的方法
Method[] methods = objectClass.getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true); // 让虚拟机,不要去检测 private
// 判断方法 是否有被 annotationClass注解的方法
boolean annotationPresent = method.isAnnotationPresent(annotationClass);
if (annotationPresent) {
// 当前方法 代表包含了 annotationClass注解的
try {
method.invoke(object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
由于不同的手机厂商,跳转到设置界面的代码是不一样的,所以,这里要做适配。适配采用依赖倒置原则。所谓的依赖倒置是面向过程,不面向细节的。
(1)创建依赖接口
public interface ISetting {
Intent getStartSettingsIntent(Context context);
}
public static void startAndroidSettings(Context context) {
// 拿到当前手机品牌制造商,来获取 具体细节
Class aClass = permissionMenu.get(Build.MANUFACTURER.toLowerCase());
if (aClass == null) {
aClass = permissionMenu.get(MANUFACTURER_DEFAULT);
}
try {
Object newInstance = aClass.newInstance(); // new OPPOStartSettings()
ISetting iMenu = (ISetting) newInstance; // ISetting iMenu = (ISetting) oPPOStartSettings;
// 高层 面向抽象,而不是具体细节
Intent startActivityIntent = iMenu.getStartSettingsIntent(context);
if (startActivityIntent != null) {
context.startActivity(startActivityIntent);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
在目标activity中添加如下代码。
@Permission(value = "android.permission.READ_EXTERNAL_STORAGE", requestCode = 1)
private void requestPermission() {
Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
}
@PermissionFailed(requestCode = 1)
private void requestPermissionFailed() {
Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
}
@PermissionDenied(requestCode = 1)
private void requestPermissionDenied() {
Toast.makeText(this, "权限申请失败,不再询问", Toast.LENGTH_SHORT).show();
}