AOP概念
百度百科中對(duì)AOP的解釋如下:
在軟件業(yè),AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率。
說白了,AOP其實(shí)就是OOP的補(bǔ)充,OOP從橫向上區(qū)分出一個(gè)個(gè)的類來,而AOP則從縱向上向?qū)ο笾屑尤胩囟ǖ拇a。
AOP在網(wǎng)易新聞android客戶端的應(yīng)用
看過之前文章的同學(xué),可能了解,網(wǎng)易新聞android客戶端的熱更新技術(shù)使用的AOP的技術(shù)。除此之外AOP還有哪些能夠解決我們痛點(diǎn)的使用場(chǎng)景的。我簡(jiǎn)單列舉一下目前我們?cè)賏ndroid客戶端上對(duì)AOP技術(shù)的應(yīng)用
網(wǎng)易新聞熱補(bǔ)丁技術(shù)
網(wǎng)易新聞的android客戶端熱更新技術(shù)使用的是AspectJ, AspectJ就是AOP技術(shù)的一種框架。詳情網(wǎng)易新聞熱補(bǔ)丁技術(shù)實(shí)踐
檢測(cè)方法耗時(shí)
新聞客戶端開發(fā)了一套能夠根據(jù)指定的sdk進(jìn)行排查方法耗時(shí)的工具,原理就是使用的AspectJ處理字節(jié)碼包裝方法。
方法耗時(shí),這個(gè)其實(shí)android上已經(jīng)有一些現(xiàn)成的工具,比如trace view等等,這些工具都可以進(jìn)行方法耗時(shí)的檢測(cè)。但是痛點(diǎn)是這些工具使用起來都比較麻煩,效率低下, 而且無法針對(duì)某一個(gè)塊代碼或者某個(gè)指定的sdk進(jìn)行查看方法耗時(shí)。
我們?yōu)榱四軌蛱岣呖蛻舳说腇PS,其中有一個(gè)思路就是希望降低主線程方法耗時(shí)。 最初的思路就是使用trace View等工具進(jìn)行排查。不用不知道一用你就會(huì)發(fā)現(xiàn)有多么的繁瑣。于是我們希望能有一種方式能夠快速打印出我們的方法耗時(shí)。
于是我們采用了AOP的技術(shù),對(duì)每個(gè)方法做一個(gè)切點(diǎn),在執(zhí)行之后打印方法耗時(shí)。
具體的實(shí)現(xiàn)原理和網(wǎng)易新聞熱更新技術(shù)原理差不多,都是對(duì)方法做切點(diǎn),注入一段自己邏輯,只不過注入的是計(jì)算方法耗時(shí)的邏輯而已。
具體實(shí)現(xiàn)仍然使用的是AspectJ的方案:
I 編譯完成之后使用AspectJ編譯器處理字節(jié)碼, 兩種方案
a. hook java compiler的方案
直接hook java Compiler的Task,在java源碼編譯完成之后執(zhí)行AspectJ的編譯器,進(jìn)行字節(jié)碼插樁操作。
project.android.applicationVariants.all { variant ->
if (variant.buildType.name != "release") {
log.debug("Skipping non-release build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.5",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
}
}
}
}
此中方案的缺陷就是對(duì)參與編譯過程的代碼處理很簡(jiǎn)單,但是對(duì)于一些不參與編譯過程的jar/aar等處理比較困難。
b. gradle Transform API方案
對(duì)于上述方法存在一些問題,對(duì)于一些jar包,其實(shí)已經(jīng)是字節(jié)碼了不會(huì)走這個(gè)過程,因此對(duì)一些jar的插樁操作不是很好處理。因此我們采用了Transform API的方案。 transform api是Android gradle plugin 1.5之后新api, 作用就是在生成dex之前,給開發(fā)者一個(gè)機(jī)會(huì)能夠統(tǒng)一進(jìn)行修改字節(jié)碼, 這個(gè)過程中你能拿到所有的源碼生成的class文件和jar/aar中的class文件。因此,此時(shí)處理能夠滿足我們對(duì)所有的class文件進(jìn)行處理。 為了方便我們實(shí)現(xiàn)了一個(gè)gradle plugin 專門進(jìn)行AspectJ處理class文件,完成字節(jié)碼插樁操作。
并且為了處理對(duì)第三方j(luò)ar包的方法耗時(shí)最終,我們可以配置對(duì)指定的jar或者aar進(jìn)行字節(jié)碼插樁,查看方法耗時(shí).
如: 插件的配置如下,再buildType為debug的時(shí),對(duì)AMap_Location jar包(高德地圖sdk)進(jìn)行字節(jié)碼插樁
aspectj { includeJarFilter 'AMap_Location' enableBuildType 'debug'//, 'debug' //buildType為debug時(shí),會(huì)輸出主線程方法耗時(shí)時(shí)間, 需要關(guān)閉instantRun,clean再run enable true printlog true }
II 注入計(jì)算方法耗時(shí)的邏輯
@Aspect
public class AspectJSpectControler {
@Around(value = "execution(* com.netease.newsreader..*.*(..)")
public Object weavePatchLogic(ProceedingJoinPoint joinPoint) throws Throwable {
if (BuildConfig.DEBUG) { //debug 狀態(tài)下計(jì)算方法耗時(shí)
long startT = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long consume = System.currentTimeMillis() - startT;
if (consume > 40 && Thread.currentThread().getId() == BaseApplication.getInstance().getMainThreadId()) { // 方法耗時(shí)大于40毫秒,并且當(dāng)前線程是主線程,則直接打印當(dāng)前方法的簽名
NeteaseLog.d(METHOD_TIME_TAG, consume + " ms " + joinPoint.getSignature() + " main thread method");
}
return proceed;
}
return joinPoint.proceed();
}
}
II 運(yùn)行app,過濾log查看方法耗時(shí)
打印log過濾關(guān)鍵字
adb logcat | grep method

對(duì)第三方sdk或開源哭某些方法進(jìn)行hook修改
原理同上
處理權(quán)限問題
處理權(quán)限問題,目前我們沒有使用AspectJ的方案。大家如果感興趣的話,可以從github查一下,很多公司確實(shí)是使用了這種方案。