Android高手進階:性能調優hugo中全面分析AOP切面編程使用詳解
前言
Android 性能調優中,通常存在需要對方法的執行時間進行統計的需求,這樣就可以看出哪些方法耗時多,是系統的瓶頸。最容易想到的方案是在每個方法的開頭處獲取系統時間,在方法的結尾處再次獲取系統時間,前后兩個時間戳的差值就是這個方法執行所消耗的總時間;
Hugo項目是一個調試函數調用耗時的工具,通過對方法或者類添加@DebugLog注解,在運行時會將函數的耗時打印在控制臺中,通常用于排查函數耗時,或者用于卡頓檢測;
hugo 這個框架麻雀雖小但五臟俱全,它使用了很多 Android 開發中流行的技術,例如注解,AOP,AspectJ,Gradle 插件;
一、hugo插件詳解
1、hugo使用
hugo 以 gradle 插件的形式供開發者集成和使用,分為兩步:
- 在項目全局添加對 hugo 插件的依賴
- 在需要使用 hugo 的 module 中應用 hugo 插件
- buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' // 添加 Hugo 的 Gradle 插件依賴
- }
- }
- apply plugin: 'com.jakewharton.hugo' // 應用 Hugo 插件
2、hugo源碼分析
①aspectjrt.jar:aspectJ 運行時的依賴庫,想要使用 aspectJ 的功能都需要引入這個庫;
hugo-annotations:hugo 的注解庫,定義了 DebugLog 這個注解;
- @Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
- public @interface DebugLog {
- }
②hugo-runtime:hugo 的運行時庫,是實現 hugo 日志功能的核心庫;
③hugo-plugin:hugo 的插件庫,主要實現了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、代碼實操
需要進行日志記錄的類名或者方法名處使用 @DebugLog 注解標記即可;
- @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,全稱為 Aspect Oriented Programming,即面向切面編程;AOP 是軟件開發中的一個編程范式,通過預編譯方式或者運行期動態代理等實現程序功能的統一維護的一種技術,它是 OOP(面向對象編程)的延續,利用 AOP 開發者可以實現對業務邏輯中的不同部分進行隔離,從而進一步降低耦合,提高程序的可復用性,進而提高開發的效率;
aop涉及到的關鍵知識點:
- 橫切關注點(Cross-cutting concerns):在面向對象編程中,經常需要在不同的模塊代碼中添加一些類似的代碼,例如在函數入口處打印日志,在 View 的點擊處添加點擊事件的埋點統計,在 AOP 中把軟件系統分成兩個部分:核心關注點和橫切關注點,核心關注點就是業務邏輯處理的主要流程,而橫切關注點就是上面所說的經常發生在核心關注點的多個地方,且基本相似的日志紀錄,埋點統計等等;
- 連接點(Joint point):在核心關注點中可能會存在橫切關注點的地方,例如方法調用的入口,View 的點擊處理等地方,在 AOP 中習慣稱為連接點;
- 增強(Advice):特定連接點處所執行的動作,也就是 AOP 織入的代碼,目的是對原有代碼進行功能的增強;
- before:在目標方法執行之前的動作;
- around:在目標方法之前前后的動作;
- after:在目標方法執行之后的動作;
- 切入點(Pointcut):連接點的集合,這些連接點可以確定什么時機會觸發一個通知;
- 切面(Aspect):切入點和通知可以組合成一個切面;
- 織入(Weaving):將通知注入到連接點的過程;
AOP 中代碼的織入根據類型的不同,主要可以分為三類:
- 編譯時織入:在 Java 類文件編譯的時候進行織入,這需要通過特定的編譯器來實現,例如使用 AspectJ 的織入編譯器;
- 類加載時織入:通過自定義類加載器 ClassLoader 的方式在目標類被加載到虛擬機之前進行類的字節代碼的增強;
- 運行時織入:切面在運行的某個時刻被動態織入,基本原理是使用 Java 的動態代理技術;
2、Android中aop實現:用aspectj實現aop
2.1、什么是AspectJ
①AspectJ實際上是對AOP編程思想的一個實踐,AOP雖然是一種思想,但就好像OOP中的Java一樣,一些先行者也開發了一套語言來支持AOP;
基礎知識點:
- Aspect 切面:切面是切入點和通知的集合;
- PointCut 切入點:切入點是指那些通過使用一些特定的表達式過濾出來的想要切入Advice的連接點;
- Advice 通知:通知是向切點中注入的代碼實現方法;
- Joint Point 連接點:所有的目標方法都是連接點;
- Weaving 編織:主要是在編譯期使用AJC將切面的代碼注入到目標中, 并生成出代碼混合過的.class的過程;
涉及到的注解:
- @Aspect:聲明切面,標記類
- @Pointcut(切點表達式):定義切點,標記方法
- @Before(切點表達式):前置通知,切點之前執行
- @Around(切點表達式):環繞通知,切點前后執行
- @After(切點表達式):后置通知,切點之后執行
- @AfterReturning(切點表達式):返回通知,切點方法返回結果之后執行
- @AfterThrowing(切點表達式):異常通知,切點拋出異常時執行
2.2、實現一個網絡狀態檢測的AOP
①aspectj配置
項目的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實現
定義annotation:
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CheckNetwork {
- }
代碼注解:
- @CheckNetwork()
- private void checkNetwork() {
- LogUtil.i("AnnotationFragment", "檢測完畢");
- }
關鍵處理切入點:
- @Aspect
- public class CheckNetworkAspect {
- private static final String TAG = CheckNetworkAspect.class.getSimpleName();
- /**
- * 找到處理的切點
- * * *(..) “**”表示是任意包名 “..”表示任意類型任意多個參數
- */
- @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, "當前網絡正常", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(context, "此時沒有網絡連接", Toast.LENGTH_SHORT).show();
- }
- return joinPoint.proceed();
- }
- return null;
- }
- }
總結:
AOP 能夠實現將日志紀錄,性能統計,埋點統計,安全控制,異常處理等代碼從具體的業務邏輯代碼中抽取出來,放到統一的地方進行處理;
利用 AOP 開發者可以實現對業務邏輯中的不同部分進行隔離,從而進一步降低耦合,提高程序的可復用性,進而提高開發的效率;
可以自定義屬于你的功能比如:日志記錄,性能統計,安全控制,事務處理,異常處理等等。
本文轉載自微信公眾號「Android開發編程」,可以通過以下二維碼關注。轉載本文請聯系Android開發編程公眾號。