SoFunction
Updated on 2025-03-08

Detailed explanation of Android AOP framework AspectJ usage

Preface

I have learned about the AOP framework of Android before, and the usage is mainly used to log. Now there is a requirement that the function needs to be executed in a new thread, and after the function body is executed, the result will be returned in the UI thread. When I think of handwritten words, I have to operate new Thread every time, which is more troublesome; so I try to solve this problem by using annotation.

The core of AspectJ is its compiler. It does one thing: insert AspectJ's code into the target program during the compilation period, and the runtime is no different from other places. Therefore, the most important thing to use it is to use its compiler to compile the code ajc. ajc will build the connection between the target program and the AspectJ code, insert the AspectJ code into the cut out PointCut during the compilation period, which has achieved the purpose of AOP.

Therefore, no matter what IDE is on (if you use the command line, you can directly compile with ajc), the problem is to let the IDE use ajc as the compiler to compile the code.

Code implementation

Annotation usage

The code is mainly associated with the AOP container through three annotations: TraceLog, RunOnNewThread, and RunOnNewThreadWithUICallback. How to use it is as follows:

@TraceLog
@RunOnNewThread
public void checkAndRestartDownloadTask(final boolean isAutoCache) {
 ().startService(isAutoCache);
}


@TraceLog
@RunOnNewThreadWithUICallback
public Boolean isShowTipsForFirstVideoCache(DBQueryCallback<Boolean> callback) {
 if (!PreferenceClient.is_first_video_cache_done.getBoolean() &&
   (().getFinishedTaskSize(true, false) > 0 ||
     ().getFinishedTaskSize(true, true) > 0)) {
  PreferenceClient.is_first_video_cache_done.setBoolean(true);
  return true;
 }
 return false;
}

checkAndRestartDownloadTask method, expect the method body to be executed in a new thread and print the method execution Log; isShowTipsForFirstVideoCache method, expect the method body to be executed in a new thread, and pass the result of the function back to the UI thread through the DBQueryCallback callback, and print the method execution Log.

The AOP container recognizes these three annotations and implements an annotation interpreter.

@Aspect
public class TudouDownloadAspect {
 public static final String TAG = ();

 private static final String THREAD_CALLBACK_POINT_METHOD =
   "execution(@ * *(.., ))";
 private static final String THREAD_CALLBACK_POINT_CONSTRUCTOR =
   "execution(@ *.new(.., ))";

 private static final String THREAD_POINT_METHOD =
   "execution(@ * *(..))";
 private static final String THREAD_POINT_CONSTRUCTOR =
   "execution(@ *.new(..))";

 private static final String LOG_POINT_METHOD =
   "execution(@ * *(..))";
 private static final String LOG_POINT_CONSTRUCTOR =
   "execution(@ *.new(..))";


 @Pointcut(THREAD_CALLBACK_POINT_METHOD)
 public void methodAnnotatedWithThread(){}
 @Pointcut(THREAD_CALLBACK_POINT_CONSTRUCTOR)
 public void constructorAnnotatedWithThread(){}

 @Pointcut(THREAD_POINT_METHOD)
 public void methodAnnotatedWithNewThread(){}
 @Pointcut(THREAD_POINT_CONSTRUCTOR)
 public void constructorAnnotatedWithNewThread(){}

 @Pointcut(LOG_POINT_METHOD)
 public void methodAnnotatedWithLog(){}
 @Pointcut(LOG_POINT_CONSTRUCTOR)
 public void constructorAnnotatedWithLog(){}

 /**
   * @RunOnNewThreadWithUICallback annotation interpreter
   * */
 @Around("methodAnnotatedWithThread() || constructorAnnotatedWithThread()")
 public Object wrapNewThreadWithCallback(final ProceedingJoinPoint joinPoint) throws Throwable {
  (TAG, "in wrapNewThreadWithCallback");
  Object[] objs = ();
  final DBQueryCallback callback = (DBQueryCallback) objs[-1];
  new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     final Object obj = ();
     ().(new Runnable() {
      @Override
      public void run() {
       if (obj != null)
        (obj);
       else
        ();
      }
     });
    } catch (Throwable throwable) {
     ();
    }
   }
  }).start();
  return null;
 }

 /**
   * @RunOnNewThread's annotation interpreter
   * */
 @Around("methodAnnotatedWithNewThread() || constructorAnnotatedWithNewThread()")
 public void wrapNewThread(final ProceedingJoinPoint joinPoint) throws Throwable {
  (TAG, "in wrapNewThread");
  new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     ();
    } catch (Throwable throwable) {
     ();
    }
   }
  }).start();

 }

 /**
   * @TraceLog's annotation interpreter
   * */
 @Before("methodAnnotatedWithLog() || constructorAnnotatedWithLog()")
 public void wrapWithLog(JoinPoint joinPoint) throws Throwable {
  (TAG, "before-&gt;" + ().toString() + "---" + ().getName());
 }

}

  1. @Aspect: Declare an AOP container
  2. @Pointcut: Declare a point-cut
  3. @Around: Wrap the function body and insert the code before and after the function body
  4. @Before: Insert code before function body execution

Loading AOP container using Gradle script

buildscript {
  repositories {
    mavenLocal()
    maven { url "" }
  }
  dependencies {
    classpath ':aspectjtools:1.8.+' //AspectJ script dependency  }
}

 dependencies {
    compile ':aspectjrt:1.8.+' //AspectJ code dependency  }

//AspectJ AOP container loading scriptfinal def log = 
final def variants = 
 { variant -&gt;
  JavaCompile javaCompile = 
   {
    String[] args = ["-showWeaveInfo",
             "-1.5",
             "-inpath", (),
             "-aspectpath", ,
             "-d", (),
             "-classpath", ,
             "-bootclasspath", ()]
     "ajc args: " + (args)

    MessageHandler handler = new MessageHandler(true);
    new Main().run(args, handler);
    for (IMessage message : (null, true)) {
      switch (()) {
        case :
        case :
        case :
           , 
          break;
        case :
           , 
          break;
        case :
           , 
          break;
        case :
           , 
          break;
      }
    }
  }
}

Remark

The matching rule for @RunOnNewThreadWithUICallback Annotation requires that the last parameter of the function is DBQueryCallback (there must be a callback parameter, otherwise how to pass it back to the UI thread~). The return value of the function must be consistent with the generic type of DBQueryCallback, because the return value needs to be passed into the callback;

new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     final Object obj = ();
     ().(new Runnable() {
      @Override
      public void run() {
       if (obj != null)
        (obj);
       else
        ();
      }
     });
    } catch (Throwable throwable) {
     ();
    }
   }
  }).start();

Note that final Object obj = ();, after executing the function body, we get a return value of Object type by default, so we cannot use the basic data type (bool is used for Boolean, int is used for Interger). Another point is that null in Java can be converted to any type, so even if null is returned directly in the function body and final Object obj = ();, there will be no problem with this type conversion. Testing is effective, can be used with confidence

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.