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的使用整理
本人最近也在開始準備面試。費曼學習法,從本身弄明白開始,用淺白的語言敘述。寫博客也是這個目的吧。在準備面試資料的同事遇到新知識點,也要各個擊破、喜歡的話,能夠關注下公衆號