在上篇文章Android編譯期插樁,讓程序本身寫代碼(一)的前言部分我放了一張圖,用來講明編譯期插樁的位置和相應的技術。這裏,我還打算這張圖來開篇。 java
在上圖中,咱們能夠清楚的看到AspectJ的插樁位置是.java與.class之間。這很容易令人聯想到編譯器。事實上,AspectJ就是一種編譯器,它在Java編譯器的基礎上增長了關鍵字識別和編譯方法。所以,AspectJ能夠編譯Java代碼。它還提供了Aspect程序。在編譯期間,將開發者編寫的Aspect程序織入到目標程序中,擴展目標程序的功能。android
AspectJ能夠應用於Android和後端開發中。在後端,AspectJ 應用更爲普遍一些,著名的Spring框架就對AspectJ提供了支持。不過,近些年,AspectJ技術在Android領域也開始嶄露頭角,比較知名的有JakeWharton的hugo。另外,一些企業也開始探索AspectJ在埋點、權限管理等方面的應用。git
關於AspectJ更爲詳細的介紹,請你們移步鄧平凡大神的博客深刻理解Android之AOP。這篇文章對於初次接觸AspectJ的人來講十分友好,筆者最初就是經過它進入AspectJ殿堂的。珠玉在前,本文就再也不介紹AspectJ的基礎知識了。那本文要說些什麼呢?github
Hugo是JakeWharton基於AspectJ開源的一個調試框架,其功能是經過註解的方式能夠打印出方法的運行時間,方便開發者性能調優。今天,咱們就來看一下它的廬山真面目。後端
Hugo是基於AspectJ的,那首先咱們就要支持AspectJ。這裏向你們推薦滬江的AspectJX,它不只使用簡單,並且還支持過濾一些aar或jar包。api
首先咱們須要在根build.gradle中依賴AspectJXbash
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
}
複製代碼
在app項目的build.gradle裏應用插件,並添加aspectj的依賴庫app
apply plugin: 'android-aspectjx'
api 'org.aspectj:aspectjrt:1.8.9'
複製代碼
這樣就配置完成了,是否是很簡單啊。框架
注意:筆者這裏採用的gradle版本是3.0.1,若是沒有編譯經過,檢查一下gradle版本。post
十分簡單,直接上代碼
@Target({METHOD, CONSTRUCTOR})
@Retention(CLASS)
public @interface DebugLog {
}
複製代碼
@Aspect
public class Hugo {
@Pointcut("execution(@com.hugo.example.lib.DebugLog * *(..))")
public void method() {}
@Pointcut("execution(@com.hugo.example.lib.DebugLog *.new(..))")
public void constructor() {}
@Around("method() || constructor()")
public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
Class<?> cls = codeSignature.getDeclaringType();
String methodName = codeSignature.getName();
long startNanos = System.nanoTime();
Object result = joinPoint.proceed();
long stopNanos = System.nanoTime();
long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
StringBuilder builder = new StringBuilder();
builder.append("methodName:")
.append(methodName)
.append(" ---- executeTime:")
.append(lengthMillis);
Log.e(asTag(cls), builder.toString());
return result;
}
private static String asTag(Class<?> cls) {
if (cls.isAnonymousClass()) {
return asTag(cls.getEnclosingClass());
}
return cls.getSimpleName();
}
}
複製代碼
若是你學習了深刻理解Android之AOP,那麼這段代碼應該很容易能看懂。這裏我簡單解釋一下。
到此,這個簡化版的Hugo基本就介紹完了。你能夠作個Demo試一下了。
- 筆者DebugLog的包名是com.hugo.example.lib。所以在method()和constractor()中的註解內容是com.hugo.example.lib.DebugLog。
- 其實,真正的Hugo框架核心也只有一個類。它只是在日誌打印時,輸出了更多的方法信息。本文爲了方便讀者理解做了簡化。
咱們定義一個Test類,而後在Activity啓動的時候調用myThread()方法。Test類以下:
public class Test {
@DebugLog
public void myMethod1() throws Exception{
Thread.sleep(1000);
}
}
複製代碼
咱們看一下日誌,方法的運行時間被完美的打印出來了。
咱們仍然以Test爲例,看一下Test反編以後的字節碼。
反編譯的內容看起來不太方便,我在這裏把它轉換成了以下代碼:
爲了觀看方便,上圖將代碼分爲4部分。
咱們先看第一部分,這是一個靜態代碼塊,也就是說在類加載的時候,程序會AspectJ提供的Factory類,建立一個類型爲JoinPoint.StaticPart靜態實例STATIC_PART。深刻理解Android之AOP中對JoinPoint.StaticPart介紹以下:
thisJoinPointStaticPart對象:在advice代碼中可直接使用,表明JPoint中那些不變的東西。好比這個JPoint的類型,JPoint所處的代碼位置等。這裏thisJoinPointStaticPart就是代碼中的JoinPoint.StaticPart。
第二部分是咱們以前定義的myThread方法,它在編譯期間被替換了。在運行時,它首先經過Factory的靜態方法makeJP建立一個JoinPoint對象。makeJp是一個重載方法,咱們看一下。
public static JoinPoint makeJP(JoinPoint.StaticPart staticPart, Object _this, Object target) {
return new JoinPointImpl(staticPart, _this, target, NO_ARGS);
}
public static JoinPoint makeJP(JoinPoint.StaticPart staticPart, Object _this, Object target, Object[] args) {
return new JoinPointImpl(staticPart, _this, target, args);
}
複製代碼
經過我列出來了兩個,能夠看到JoinPoint除了包含了咱們第一步中提到了STATIC_PART對象,還包括了this,target對象,以及方法參數。這和深刻理解Android之AOP中對thisJoinpoint描述也是一致的:
thisJoinpoint對象:在advice代碼中可直接使用。表明JPoint每次被觸發時的一些動態信息,好比參數啊之類的。
建立完JoinPoint對象後,隨後調用了第三部分中的advice()方法。advice()方法大部分都是咱們在Hugo中編寫的織入代碼,這裏只有一個不一樣,那就是joinPoint.proceed()不見了,替換成了源代碼中具體的處理邏輯。
經過上述分析,咱們能夠清楚的感知到AspectJ提供了很是強大的功能。但同時,因爲其爲每一個切入點生成一個JoinPoint.StaticPar靜態實例和在運行過程當中生成的JoinPoint以及一些其它的封裝,這必然會致使程序在內存和處理速度等方面受影響。所以,在小範圍內使用AspectJ是能夠的,可是若是涉及範圍較大就要慎重考慮了。