Android高手進(jìn)階:性能調(diào)優(yōu)hugo中全面分析AOP切面編程使用詳解
前言
Android 性能調(diào)優(yōu)中,通常存在需要對(duì)方法的執(zhí)行時(shí)間進(jìn)行統(tǒng)計(jì)的需求,這樣就可以看出哪些方法耗時(shí)多,是系統(tǒng)的瓶頸。最容易想到的方案是在每個(gè)方法的開(kāi)頭處獲取系統(tǒng)時(shí)間,在方法的結(jié)尾處再次獲取系統(tǒng)時(shí)間,前后兩個(gè)時(shí)間戳的差值就是這個(gè)方法執(zhí)行所消耗的總時(shí)間;
Hugo項(xiàng)目是一個(gè)調(diào)試函數(shù)調(diào)用耗時(shí)的工具,通過(guò)對(duì)方法或者類(lèi)添加@DebugLog注解,在運(yùn)行時(shí)會(huì)將函數(shù)的耗時(shí)打印在控制臺(tái)中,通常用于排查函數(shù)耗時(shí),或者用于卡頓檢測(cè);
hugo 這個(gè)框架麻雀雖小但五臟俱全,它使用了很多 Android 開(kāi)發(fā)中流行的技術(shù),例如注解,AOP,AspectJ,Gradle 插件;
一、hugo插件詳解
1、hugo使用
hugo 以 gradle 插件的形式供開(kāi)發(fā)者集成和使用,分為兩步:
- 在項(xiàng)目全局添加對(duì) hugo 插件的依賴(lài)
- 在需要使用 hugo 的 module 中應(yīng)用 hugo 插件
- buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' // 添加 Hugo 的 Gradle 插件依賴(lài)
- }
- }
- apply plugin: 'com.jakewharton.hugo' // 應(yīng)用 Hugo 插件
2、hugo源碼分析
①aspectjrt.jar:aspectJ 運(yùn)行時(shí)的依賴(lài)庫(kù),想要使用 aspectJ 的功能都需要引入這個(gè)庫(kù);
hugo-annotations:hugo 的注解庫(kù),定義了 DebugLog 這個(gè)注解;
- @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
- public @interface DebugLog {
- }
②hugo-runtime:hugo 的運(yùn)行時(shí)庫(kù),是實(shí)現(xiàn) hugo 日志功能的核心庫(kù);
③hugo-plugin:hugo 的插件庫(kù),主要實(shí)現(xiàn)了aop的插件;
- class HugoPlugin implements Plugin<Project> {
- @Override void apply(Project project) {
- def hasApp = project.plugins.withType(AppPlugin)
- def hasLib = project.plugins.withType(LibraryPlugin)
- if (!hasApp && !hasLib) {
- throw new IllegalStateException("'android' or 'android-library' plugin required.")
- }
- final def log = project.logger
- final def variants
- if (hasApp) {
- variants = project.android.applicationVariants
- } else {
- variants = project.android.libraryVariants
- }
- project.dependencies {
- debugCompile 'com.jakewharton.hugo:hugo-runtime:1.2.2-SNAPSHOT'
- // TODO this should come transitively
- debugCompile 'org.aspectj:aspectjrt:1.8.6'
- compile 'com.jakewharton.hugo:hugo-annotations:1.2.2-SNAPSHOT'
- }
- project.extensions.create('hugo', HugoExtension)
- variants.all { variant ->
- if (!variant.buildType.isDebuggable()) {
- log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
- return;
- } else if (!project.hugo.enabled) {
- log.debug("Hugo is not disabled.")
- 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);
- for (IMessage message : handler.getMessages(null, true)) {
- switch (message.getKind()) {
- case IMessage.ABORT:
- case IMessage.ERROR:
- case IMessage.FAIL:
- log.error message.message, message.thrown
- break;
- case IMessage.WARNING:
- log.warn message.message, message.thrown
- break;
- case IMessage.INFO:
- log.info message.message, message.thrown
- break;
- case IMessage.DEBUG:
- log.debug message.message, message.thrown
- break;
- }
- }
- }
- }
- }
- }
3、代碼實(shí)操
需要進(jìn)行日志記錄的類(lèi)名或者方法名處使用 @DebugLog 注解標(biāo)記即可;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- @DebugLog
- private void test(String... tests) {
- for (String arg : tests) {
- Log.i("Args", arg);
- }
- }
二、AOP詳解
1、什么是aop
AOP,全稱(chēng)為 Aspect Oriented Programming,即面向切面編程;AOP 是軟件開(kāi)發(fā)中的一個(gè)編程范式,通過(guò)預(yù)編譯方式或者運(yùn)行期動(dòng)態(tài)代理等實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù),它是 OOP(面向?qū)ο缶幊?的延續(xù),利用 AOP 開(kāi)發(fā)者可以實(shí)現(xiàn)對(duì)業(yè)務(wù)邏輯中的不同部分進(jìn)行隔離,從而進(jìn)一步降低耦合,提高程序的可復(fù)用性,進(jìn)而提高開(kāi)發(fā)的效率;
aop涉及到的關(guān)鍵知識(shí)點(diǎn):
- 橫切關(guān)注點(diǎn)(Cross-cutting concerns):在面向?qū)ο缶幊讨校?jīng)常需要在不同的模塊代碼中添加一些類(lèi)似的代碼,例如在函數(shù)入口處打印日志,在 View 的點(diǎn)擊處添加點(diǎn)擊事件的埋點(diǎn)統(tǒng)計(jì),在 AOP 中把軟件系統(tǒng)分成兩個(gè)部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn),核心關(guān)注點(diǎn)就是業(yè)務(wù)邏輯處理的主要流程,而橫切關(guān)注點(diǎn)就是上面所說(shuō)的經(jīng)常發(fā)生在核心關(guān)注點(diǎn)的多個(gè)地方,且基本相似的日志紀(jì)錄,埋點(diǎn)統(tǒng)計(jì)等等;
- 連接點(diǎn)(Joint point):在核心關(guān)注點(diǎn)中可能會(huì)存在橫切關(guān)注點(diǎn)的地方,例如方法調(diào)用的入口,View 的點(diǎn)擊處理等地方,在 AOP 中習(xí)慣稱(chēng)為連接點(diǎn);
- 增強(qiáng)(Advice):特定連接點(diǎn)處所執(zhí)行的動(dòng)作,也就是 AOP 織入的代碼,目的是對(duì)原有代碼進(jìn)行功能的增強(qiáng);
- before:在目標(biāo)方法執(zhí)行之前的動(dòng)作;
- around:在目標(biāo)方法之前前后的動(dòng)作;
- after:在目標(biāo)方法執(zhí)行之后的動(dòng)作;
- 切入點(diǎn)(Pointcut):連接點(diǎn)的集合,這些連接點(diǎn)可以確定什么時(shí)機(jī)會(huì)觸發(fā)一個(gè)通知;
- 切面(Aspect):切入點(diǎn)和通知可以組合成一個(gè)切面;
- 織入(Weaving):將通知注入到連接點(diǎn)的過(guò)程;
AOP 中代碼的織入根據(jù)類(lèi)型的不同,主要可以分為三類(lèi):
- 編譯時(shí)織入:在 Java 類(lèi)文件編譯的時(shí)候進(jìn)行織入,這需要通過(guò)特定的編譯器來(lái)實(shí)現(xiàn),例如使用 AspectJ 的織入編譯器;
- 類(lèi)加載時(shí)織入:通過(guò)自定義類(lèi)加載器 ClassLoader 的方式在目標(biāo)類(lèi)被加載到虛擬機(jī)之前進(jìn)行類(lèi)的字節(jié)代碼的增強(qiáng);
- 運(yùn)行時(shí)織入:切面在運(yùn)行的某個(gè)時(shí)刻被動(dòng)態(tài)織入,基本原理是使用 Java 的動(dòng)態(tài)代理技術(shù);
2、Android中aop實(shí)現(xiàn):用aspectj實(shí)現(xiàn)aop
2.1、什么是AspectJ
①AspectJ實(shí)際上是對(duì)AOP編程思想的一個(gè)實(shí)踐,AOP雖然是一種思想,但就好像OOP中的Java一樣,一些先行者也開(kāi)發(fā)了一套語(yǔ)言來(lái)支持AOP;
基礎(chǔ)知識(shí)點(diǎn):
- Aspect 切面:切面是切入點(diǎn)和通知的集合;
- PointCut 切入點(diǎn):切入點(diǎn)是指那些通過(guò)使用一些特定的表達(dá)式過(guò)濾出來(lái)的想要切入Advice的連接點(diǎn);
- Advice 通知:通知是向切點(diǎn)中注入的代碼實(shí)現(xiàn)方法;
- Joint Point 連接點(diǎn):所有的目標(biāo)方法都是連接點(diǎn);
- Weaving 編織:主要是在編譯期使用AJC將切面的代碼注入到目標(biāo)中, 并生成出代碼混合過(guò)的.class的過(guò)程;
涉及到的注解:
- @Aspect:聲明切面,標(biāo)記類(lèi)
- @Pointcut(切點(diǎn)表達(dá)式):定義切點(diǎn),標(biāo)記方法
- @Before(切點(diǎn)表達(dá)式):前置通知,切點(diǎn)之前執(zhí)行
- @Around(切點(diǎn)表達(dá)式):環(huán)繞通知,切點(diǎn)前后執(zhí)行
- @After(切點(diǎn)表達(dá)式):后置通知,切點(diǎn)之后執(zhí)行
- @AfterReturning(切點(diǎn)表達(dá)式):返回通知,切點(diǎn)方法返回結(jié)果之后執(zhí)行
- @AfterThrowing(切點(diǎn)表達(dá)式):異常通知,切點(diǎn)拋出異常時(shí)執(zhí)行
2.2、實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)狀態(tài)檢測(cè)的AOP
①aspectj配置
項(xiàng)目的gradle中配置build.gradle(project)
- buildscript {
- repositories {
- google()
- mavenCentral()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:4.1.1'
- classpath 'org.aspectj:aspectjtools:1.8.6'
- }
- }
- ......
- ......
主app中build.gradle(app)
- dependencies {
- compile 'org.aspectj:aspectjrt:1.8.6'
- }
- android.libraryVariants.all { variant ->
- 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", android.bootClasspath.join(File.pathSeparator)
- ]
- MessageHandler handler = new MessageHandler(true);
- new Main().run(args, handler)
- def log = project.logger
- for (IMessage message : handler.getMessages(null, true)) {
- switch (message.getKind()) {
- case IMessage.ABORT:
- case IMessage.ERROR:
- case IMessage.FAIL:
- log.error message.message, message.thrown
- break;
- case IMessage.WARNING:
- case IMessage.INFO:
- log.info message.message, message.thrown
- break;
- case IMessage.DEBUG:
- log.debug message.message, message.thrown
- break;
- }
- }
- }
- }
②aop實(shí)現(xiàn)
定義annotation:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CheckNetwork {
- }
代碼注解:
- @CheckNetwork()
- private void checkNetwork() {
- LogUtil.i("AnnotationFragment", "檢測(cè)完畢");
- }
關(guān)鍵處理切入點(diǎn):
- @Aspect
- public class CheckNetworkAspect {
- private static final String TAG = CheckNetworkAspect.class.getSimpleName();
- /**
- * 找到處理的切點(diǎn)
- * * *(..) “**”表示是任意包名 “..”表示任意類(lèi)型任意多個(gè)參數(shù)
- */
- @Pointcut("execution(@la.xiong.androidquick.demo.features.function.annotation.aspect.CheckNetwork * *(..))")
- public void executionCheckNetwork() {
- }
- /**
- * 處理切面
- *
- * @param joinPoint
- * @return
- */
- @Around("executionCheckNetwork()")
- public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
- MethodSignature signature = (MethodSignature) joinPoint.getSignature();
- CheckNetwork annotation = signature.getMethod().getAnnotation(CheckNetwork.class);
- if (annotation != null) {
- Context context = AspectUtils.getContext(joinPoint.getThis());
- if (NetworkUtils.isConnected()) {
- Toast.makeText(context, "當(dāng)前網(wǎng)絡(luò)正常", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(context, "此時(shí)沒(méi)有網(wǎng)絡(luò)連接", Toast.LENGTH_SHORT).show();
- }
- return joinPoint.proceed();
- }
- return null;
- }
- }
總結(jié):
AOP 能夠?qū)崿F(xiàn)將日志紀(jì)錄,性能統(tǒng)計(jì),埋點(diǎn)統(tǒng)計(jì),安全控制,異常處理等代碼從具體的業(yè)務(wù)邏輯代碼中抽取出來(lái),放到統(tǒng)一的地方進(jìn)行處理;
利用 AOP 開(kāi)發(fā)者可以實(shí)現(xiàn)對(duì)業(yè)務(wù)邏輯中的不同部分進(jìn)行隔離,從而進(jìn)一步降低耦合,提高程序的可復(fù)用性,進(jìn)而提高開(kāi)發(fā)的效率;
可以自定義屬于你的功能比如:日志記錄,性能統(tǒng)計(jì),安全控制,事務(wù)處理,異常處理等等。
本文轉(zhuǎn)載自微信公眾號(hào)「Android開(kāi)發(fā)編程」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開(kāi)發(fā)編程公眾號(hào)。