一种横向业务的解决方案 -- AOP
AOP(Aspect Oriented Programming)即面向切片编程。所谓面向切片编程,就是可以按照时间,将程序分成无数个时间节点,利用AOP的思想,可以在任何一个时间节点插入其他的代码,来实现自己的业务需求。换句话说,对于那些非用户需求,如:性能监控、日志记录、埋点统计、动态权限、安全控制、异常处理等,可以用AOP完美地实现。
以一个方法的执行为例子:
方法的执行顺序是A->B->C,通过AOP的方式,完全可以在B方法执行的前后插入其他的代码,以解决某种业务需求,例如方法执行时间的检测。
动态代理也是可以说是一种AOP思想的体现,只是它只能在接口方法执行的时候进行一些操作,而AOP可以在任何方法执行前,执行时和执行后做一些操作。
1. AOP涉及到的基本术语
横切关注点
AOP把软件分成两部分:核心关注点和横切关注点。核心关注点一般指的是一些涉及到用户业务的主线业务,而横切关注点就是指非用户业务(横向需求),但又需要穿插到主线业务中执行的代码,如日志记录等。
连接点
在核心关注点的地方可能存在横切关注点的地方,例如方法的入口、点击事件发生的地方等,这些就叫做连接点。
通知
在连接点处所执行的动作,也就是AOP织入的代码,就叫做通知,一般用三种:
before: 在目标方法执行之前的动作。
around: 替换目标代码的动作。
after: 在目标方法执行之后的动作。
切入点
连接点的集合,这些连接点可以确定什么时机会触发一个通知。也就是将代码切入到的目的类,目的方法就叫切入点。
切入点通常使用正则表达式或者通配符语法表示,可以指定执行某个方法,也可以制定执行多个方法,例如指定执行了标记某个注解的所有方法。
切面
切入点和通知可以组合成一个切面,简单地说,切面就是切入到指定类指定方法的代码片段。
织入
将通知注入到连接点的过程。
其实开发人员主要关心的就是切入点和切面这两个东西。举个简单的例子,当程序写好了之后,忽然来了一了收集错误日志的功能,也就是需要在所有调用了Log.e(TAG,Msg)
的地方将Msg
和类信息保存到本地或则服务器。这时候总不可能挨个去改代码吧,利用AOP的思想就可以直接将“收集错误信息的代码段”做成一个切片,插入到Log
类的e
方法中,就像这样(使用Lancet实现AOP):
@Proxy("e")
@TargetClass("android.util.Log")
public static int rocordLogInfo(String tag, String msg){
RecordSDK.recordLog(This.get().getClass().getSimpleName(),
msg);
return (int) Origin.call();
}
只需要添加极少的代码就可以解决这个需求了。一个字,爽!
2. AOP代码织入的时机
代码的织入过程一般有三个时机:
编译时织入:例如在Java类文件编译的时候插入完成特定业务需求的代码,需要通过特定的编译器插件来完成。
类加载时织入:通过自定义的类加载器ClassLoader的方式在目标咧的贝加载到虚拟机之前进行类的字节码的增强(插入字节码)。
运行时时织入:切面在运行中的某个时刻插入,例如动态代理。
3. AOP的应用 : 结合注解、APT实现Android动态权限的申请
众所周知,在Android 6.0以后,系统对权限的控制更加严格了,某系涉及到用户隐私的危险权限需要在用户使用的时候动态申请,对用户来说,这是一件好事儿,但是对开发者来说,这却是一个非用户需求(横向业务),在用户需求代码中融入这种横向业务无疑会增加代码的复杂性,所以,使用AOP的思想来实现这个需求对开发者来说是非常合适的,因为这并不会让代码变得复杂。
当然,基于不同的国产ROM,申请权限和对权限的处理可能有许多差异,这里只提供一种解决思路和基于官方处理权限的流程的一个库:PermissionManager,算是对这部分内容学习的一个总结。
3.1 官方的动态权限申请流程
一般会使用到v4包中的三个方法:
ContextCompat.checkSelfPermission()
ActivityCompat.requestPermissions()
ActivityCompat.shouldShowRequestPermissionRationale()
1.检查是否有某个权限:
if (ContextCompat.checkSelfPermission(context, Manifest.permission. SEND_SMS)
!= PackageManager.PERMISSION_GRANTED) {
// 没有权限,需要申请。
}else{
// 有权限
}
2.如果没有权限就去申请:
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.SEND_SMS},10001 );
3.处理权限结果的回调。
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case 10001: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户同意授权
} else {
// 用户拒绝授权
}
return;
}
}
}
4.如果用户点击了不再询问,shouldShowRequestPermissionRationale()会返回flase。只点击了拒绝授权,这个方法会返回true,所以也可以根据这个方法的返回值弹出一些说明,来提示用户为什么需要这个权限。
3.2 注解 + APT + AOP 优化申请权限的逻辑
1.注解 + APT的作用
根据注解,找到发起权限请求的类,并在编译时生成特定的回调Listener,用于将权限请求的结果回调到Activity或者Fragment中,如:
发起权限请求的是MainActivity
,
@NeedPermission
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionManager.requestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE});
}
@OnGranted
void granted(){
Log.d(TAG, "YES!用户同意了授权!");
}
@OnDenied
void denied(){
Log.d(TAG, "oh NO!用户拒绝了授权!");
}
@OnShowRationale
void showRationale(){
Log.d(TAG, "用户选择了了不再询问");
}
}
那么则会在编译时生成如下代码:
public class MainActivity_PermissionListener implements PermissionListener<MainActivity> {
@Override
public void onGranted(MainActivity target) {
target.granted();;
}
@Override
public void onDenied(MainActivity target) {
target.denied();;
}
@Override
public void onShowRationale(MainActivity target) {
target.showRationale();;
}
}
2. AOP的使用时机
使用AOP可以将处理权限结果的相关代码织入onRequestPermissionsResult()
方法中,这样就不必再重写这个方法了,所有对权限结果处理的逻辑都在使用注解(@OnGranted,@OnDenied, @OnShowRationale)标记的方法中处理了。
就比如像这样:
public class PremissionResultAspect {
@TargetClass(value = "android.support.v7.app.AppCompatActivity", scope = Scope.LEAF)
@Insert(value = "onRequestPermissionsResult", mayCreateSuper = true)
public void onRequestPermissionsResultAspect(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Log.d("xsz", "onRequestPermissionsResultAspect -> 权限的数量是 " + permissions.length);
Origin.callVoid();
PermissionListener listener = PermissionManager.getPermissionListener(This.get());
if (grantResults.length > 0 && listener != null) {
for (int i = 0; i < grantResults.length; i++) {
if (PermissionHelper.hasGrantedPermission(grantResults[i])) {
//已经获得用户权限
listener.onGranted(this);
} else {
// 用户拒绝授权
if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) This.get(), permissions[i])) {
//用户拒绝了授权,但是没有点击不再询问
//提示用户为什么APP需要这个权限
listener.onDenied(this);
} else {
//返回false -> 用户点击了不再询问
listener.onShowRationale(this);
}
}
}
}
}
}
这就完成了对动态权限的请求的过程的简单封装,其主要目的还是学习这种AOP思想,代码细节以后再来慢慢完善。
上一篇: 快速学习PHP数据库文件使用
下一篇: Oracle--Produce