AOP+动态权限申请
最近在利用AspectJ做埋点,突发奇想,能否也用类似方法做动态权限申请?发现可以。我来介绍一下我的思路。
AOP是什么
AOP,百度一下就知道,是 Aspect Oriented Programming,面向切面编程。怎么理解面向切面编程呢?
我用一个场景来解释一下:
我们在一个项目中会有很多模块或者函数(在图中统称为任务,下同)要使用,而在进行某些任务之前,或者之后,或者进行任务的前后,这里假设是从数据库获取数据的任务,都需要做同样的某个动作,比如计算进行这个任务耗时多久。
那么这里的计算任务耗时多久,我们一般写法大概是这样的:
public void getDataFromDB(){
long beginTime = System.currentTimeMillis();
//Do something
doSomeThing();
long duration = System.currentTimeMillis() - beginTime;
Log.d(TAG,"it wasted "+duration+" millis");
}
也就是在某个任务前后加入一个时间标志,来计算这个过程的耗时情况。
但是在我们项目中可能会有很多地方需要这样的计算耗时的性能评估的方法,需要我们都要用这种方式去做。
这个时候就希望我们的计算时间差的代码能够抽离出来,而且能应用在各个需要计算耗时操作的地方。
例如我们下图中的动作A, 我们项目中的任务A、B、E、F都需要计算耗时,那么我们通过某种办法,把计算时间差的相同的代码抽离出来,作为动作A,在任务A、B、E、F调用的前后调用,进行耗时计算。
同样的,可能任务C、D、E也有需要抽出的相同代码,比如验证当前用户是否登录账号。
那么如何实现这种代码抽离呢? 动态代理Proxy是可以的,当然AOP也是可以的,我们使用AspectJ。
对于AspectJ网络上很多介绍,这里就不多赘述。直接切入正题。
我找了几个链接,需要的可以去学习一下:
http://www.eclipse.org/aspectj/
https://www.jianshu.com/p/c66f4e3113b3
为什么AOP+动态权限申请?
并不是我们的应用一打开,就必须要求用户允许一些权限(比如读写权限)才能运行的,而是在做特定任务之前需要确保有这些权限才能接着往下走,比如读取本地文件,读取本地相册之前,需要读写权限;比如需要拍照之前,需要相机权限;比如需要悬浮窗、全局弹窗之前,需要悬浮窗权限等等。
所以我们就是在某些行为开始之前要去验证是否有权限,如果没有,要申请权限。可我们应用那么多地方需要各种权限,也就是要在好多处写几乎一模一样的权限申请代码。这时候面向切面编程就会是我们的一个少写代码的出路。
我们需要把验证权限、申请权限的代码抽离出来。
撸码开始
首先我们发现验证权限、申请权限需要上下文Activity
private String[] PERMISSIONS = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"};
//读取相册
public void readGallery(){
if(checkPermission()){
//如果有权限那么浏览本地相册
//TODO: 浏览本地相册
}
}
public boolean checkPermission(){
int permission = ContextCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
ActivityCompat.requestPermissions(activity, PERMISSIONS, 1);
return false;
}else{
return true;
}
}
那么我们可能会在很多个Activity中都要写 checkPermission()的代码,很麻烦。所以当我们把checkPermission()
中的代码抽离出来。同时因为切面的上下文并不是Activity,所以我们在切面中编写权限代码的时候,还需要借助工具获得当前的Activity。
AOPContextHelper
工具类,获取到当前的Activity
import android.app.Activity;
public class AOPContextHelper {
//当前Activity
private Activity mActivity;
private AOPContextHelper(){}
//单例实例
private static AOPContextHelper instance;
//获得单例实例
public static AOPContextHelper getInstance(){
if (instance == null){
synchronized (AOPContextHelper.class){
if (instance == null){
instance = new AOPContextHelper();
}
}
}
return instance;
}
public Activity getActivity() {
return mActivity;
}
public void setActivity(Activity mActivity) {
this.mActivity = mActivity;
}
}
在Application中注册Activity生命周期监听,不断更新得知当前Activity是谁
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initActivityLifecycleCallbacks();
}
//注册Activity生命周期监听
private void initActivityLifecycleCallbacks(){
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
//当有Activity Create的时候,告诉它当前是哪个Activity
AOPContextHelper.getInstance().setActivity(activity);
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
});
}
}
接下来环境有了,我们开始写AOP部分代码
注解 PermissionTrace.java
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 PermissionTrace {
}
切面 PermissionTraceAspect.java
//声明为切面
@Aspect
public class PermissionTraceAspect {
//这里简单起见只列出读写权限
private String[] PERMISSIONS = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE"};
/**
* 其中 @Pointcut注解表示切入点
* 要进入切面,都会进入这个方法
* execution(注解名字 注解的作用域(*表示任意.java文件) *(..)不傲视不限方法 不限参数)
*/
@Pointcut("execution(@com.example.pass.aop.permissionTrace.PermissionTrace * *(..))")
public void methodAnnotatedWithPermissionTrace(){}
//@Before 在切入点之前运行
//@After 在切入点之后运行
//@Around 前后都运行
//需要确保返回的值是void或者实体类
@Around("methodAnnotatedWithPermissionTrace()")
public Object joinPoint(ProceedingJoinPoint joinPoint) throws Throwable{
//先判断是否有权限
Activity activity = AOPContextHelper.getInstance().getActivity();
if (activity!=null){
int permission = ContextCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框,并不再调用原方法去进行读取本地相册
ActivityCompat.requestPermissions(activity, PERMISSIONS, 1);
}else{
//如果有权限,则通过joinPoint.proceed();方法正常调用PermissionTrace标记的方法
return joinPoint.proceed();
}
}
return null;
}
}
至此我们的准备工作做好了,做个Demo测试一下
public class MyActivity extends AppCompatActivity{
List<File> fileList= new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ImageView ivButton = findViewById(R.id.image_test);
ivButton.setOnClickListener(new View.OnClickListener{
@Override
public void onClick(View view){
//点击按钮 加载本地文件
loadLocalFiles();
}
});
}
/**
* 加载本地文件
*/
@PermissionTrace
private void loadLocalFiles(){
//加载本地文件
...
fileList.addAll(list);
}
}
跑一下,运行正常
打开App,弹出Activity,生命周期回调使得AOPContextHelper
有了Activity引用。
一开始没有权限,点击按钮,进入loadLocalFiles()
方法,在进入之前,由于有 @PermissionTrace注解,就进入到切面中。
切面中首先进行了权限判定,如果没有权限,则拉起动态权限申请弹窗,同时不会进入loadLocalFiles()
方法中,下一次再点击该按钮,有了权限,则进入loadLocalFiles()
方法中,正常使用。
不全面,待续