OOP(Object Oriented Programming): 这就是咱们android中的面向对象开发。面向对象的三大特征,封装、继承和多态。这里很少赘述。java
AOP(Aspect Oriented Programming):面向切面编程;AOP则是面对业务逻辑处理过程当中的切面进行提取,也就是程序处理的某个步骤或者阶段,以达到代码间的低耦合、代码分离、提升代码重用性android
在咱们andorid开发中都使用过注解功能,第三方库有注解的有ButterKnif、dagger二、EventBus、Retrofit,其实这些库部分核心功能也是基于AOP实现的。只不过他们还用到了其余插件,好比APT,APT在程序编译期,扫描代码中的注解信息,并为咱们生成java代码,实现咱们的功能,无需咱们手动去处理。面试
Java Annotation是JDK5.0引入的注解机制。在咱们代码里。常常能够看到@Override:表示方法覆盖父类方法。编程
java中的Annotation: @Deprecated -- 所标注内容,再也不被建议使用。 @Override -- 只能标注方法,表示该方法覆盖父类中的方法。 @Documented -- 所标注内容,能够出如今javadoc中。 @Inherited -- 只能被用来标注“Annotation类型”,它所标注的Annotation具备继承性。 @Retention -- 只能被用来标注“Annotation类型”,并且它被用来指定Annotation的RetentionPolicy属性。 @Target -- 只能被用来标注“Annotation类型”,并且它被用来指定Annotation的ElementType属性。 @SuppressWarnings -- 所标注内容产生的警告,编译器会对这些警告保持静默。 复制代码
自定义Annotation,实现本身的注解markdown
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyInject { int value(); } 复制代码
public class MyInjectUtils { public static void injectViews(Activity activity) { Class<? extends Activity> object = activity.getClass(); // 获取activity的Class Field[] fields = object.getDeclaredFields(); // 经过Class获取activity的全部字段 for (Field field : fields) { // 遍历全部字段 // 获取字段的注解,若是没有ViewInject注解,则返回null MyInject viewInject = field.getAnnotation(MyInject.class); if (viewInject != null) { int viewId = viewInject.value(); // 获取字段注解的参数,这就是咱们传进去控件Id if (viewId != -1) { try { // 获取类中的findViewById方法,参数为int Method method = object.getMethod("findViewById", int.class); // 执行该方法,返回一个Object类型的View实例 Object resView = method.invoke(activity, viewId); field.setAccessible(true); // 把字段的值设置为该View的实例 field.set(activity, resView); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } } } 复制代码
@MyInject(R.id.button) Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyInjectUtils.injectViews(this); } 复制代码
这样咱们就实现了findViewById的功能了。不难发现,此功能和ButterKnif的findViewById很是类似,可是有本质的区别。由于咱们采用了反射,在android中是很是消耗性能的。因此那些第三方库则会采起Annotation+APT来作,把注解译成Java代码,避免性能损耗。可是你知道了这些,面试官继续问你这些注解第三方库的原理,也不至于哑口无言!!app
AspectJ:是一个代码生成工具,AspectJ语法就是用来定义代码生成规则的语法ide
项目build.gradle中:函数
dependencies { //... classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.8' } 复制代码
app里的build.gradle中顶部添加工具
apply plugin: 'android-aspectjx' 复制代码
一般咱们数据埋点都会经过Application中的registerActivityLifecycleCallbacks监听。但这里咱们使用AspectJ。代码以下(这里的标注@Before、@After,关键字execution,call后面详细讲解,这里咱们先把功能实现了):oop
//标注咱们要经过Aspect语法生成代码的辅助类 @Aspect public class AspectHelper { private final String TAG = this.getClass().getSimpleName(); //com.lihang.aoptestpro.BaseActivity 是我项目里的BaseActivity //这句代码实现的功能是:会打印咱们项目里全部Activity里全部的on开头的方法 //joinPoint.getThis().getClass().getSimpleName() 当前Activity的类名 @Before("execution(* com.lihang.aoptestpro.BaseActivity.on**(..))") public void onActivityStart(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.i(TAG, key + "============" + joinPoint.getThis().getClass().getSimpleName()); } //会打印咱们项目里全部Activity里的onPause方法。 @Before("execution(* com.lihang.aoptestpro.BaseActivity.onPause(..))") public void onActivityPause(JoinPoint joinPoint) throws Throwable { String key = joinPoint.getSignature().toString(); Log.i(TAG, key + "============" + joinPoint.getThis().getClass().getSimpleName()); } } 复制代码
至此,拿到全部Activity的生命周期,埋点功能就能够实现了;注意及总结:
@Before("execution(* android.app.Activity.on**(..))") 复制代码
网上大部分都是用这句,实践发现会除了会走咱们的Activity还会走系统的Activity及FragmentActivity.至少3次
@Before("execution(* com.lihang.aoptestpro.BaseActivity.on**(..))") 复制代码
若是你BaseActivity不去实现系统生命周期,你会发现根本不走。因此好比要抓onStart、onPause生命周期时,必定要在BaseActivity去实现,即便方法体内是空也行
其实这里还能用下面的语法实现,前提是你全部的Activity必须以“Activity”字符串做为类名的尾部
@Before("execution(* com.lihang.aoptestpro.*Activity.on**(..))") 复制代码
咱们在项目开发时,有些功能每每须要登陆后才能使用,若是没有登陆,就去跳转登陆页面。这样就避免不了if/else的判断。以下,点击关注时的代码
public void follow() { if (MyApplication.getInstance().getLoginUser() != null) { User user = MyApplication.getInstance().getLoginUser(); Log.i(TAG, "已登陆,user不为空,用user信息去实现关注"); } else { Log.i(TAG, "未登陆,跳转登陆页面"); } } 复制代码
那么使用AOP非侵入式怎么使用呢? 首先咱们先定义个标注
@Target(ElementType.METHOD)//这里是标注方法,以前那个Filed是标注属性 @Retention(RetentionPolicy.RUNTIME) public @interface IsLogin { } 复制代码
而后看咱们的Aspect里:
@Aspect public class AspectHelper { private final String TAG = this.getClass().getSimpleName(); @Around("execution(@com.lihang.aoptestpro.IsLogin * *(..))") public void isLoginOn(ProceedingJoinPoint joinPoint) throws Throwable { if (MyApplication.getInstance().getLoginUser() != null) { //joinPoint.proceed()能够当作就是咱们用@IsLogin标注的那个方法,调用proceed意思就是继续执行方法 //这里的意思就是全部用标注@IsLogin标注的,是登陆状态才会继续执行方法,不然会执行咱们下面的去登陆,不会执行原方法 joinPoint.proceed(); } else { Log.i(TAG, "user为空,快去登陆把!!"); } } } 复制代码
而后再看看咱们的follow方法。用@IsLogin标注后,就能够直接处理登陆状态就好了。真的是低耦合,代码复用性高
@IsLogin public void follow() { User user = MyApplication.getInstance().getLoginUser(); Log.i(TAG, "已登陆,user不为空,用user信息去实现关注"); } 复制代码
//意思是onActivityPause会在BaseActivity.onPause()方法前执行 @Before("execution(* com.lihang.aoptestpro.BaseActivity.onPause(..))") public void onActivityPause(JoinPoint joinPoint) throws Throwable { } 复制代码
//joinPoint.proceed()是控制方法是否继续往下执行 //在joinPoint.proceed()前的逻辑代码,就是实现@Before的功能,在方法前执行 //在joinPoint.proceed()后的逻辑代码,就是实现@After的功能,在方法后执行 @Around("execution(@com.lihang.aoptestpro.IsLogin * *(..))") public void isLoginOn(ProceedingJoinPoint joinPoint) throws Throwable { if (MyApplication.getInstance().getLoginUser() != null) { joinPoint.proceed(); } else { Log.i("MainActivity", "user为空,快去登陆把!!"); } } 复制代码
注意点:
翻阅了大量资料,一样的代码。从其生成的代码里看。
简单看就是:
Call(Before)
Pointcut{
Pointcut Method
}
Call(After)
复制代码
简单看就是:
Pointcut{
execution(Before)
Pointcut Method
execution(After)
}
复制代码
虽然知道其工做原理了。但做者也存在一个疑问,那就是什么call和execution都能实现同一的功能。可是什么场景使用哪一个更佳呢?但愿有知道的小伙伴帮忙解答下
看 AspectJ 在 Android 中的强势插入
大话AOP与Android的爱恨情仇
Android 自动化埋点:基于AspectJ的沪江SDK的使用整理
本人最近也在开始准备面试。费曼学习法,从本身弄明白开始,用浅白的语言叙述。写博客也是这个目的吧。在准备面试资料的同事遇到新知识点,也要各个击破、喜欢的话,能够关注下公众号