[JAVA] You can do it with copy! Aspect-oriented programming (Android)

Introduction

For example, if you want to log at the start and end of a method, is it realistic to write it for every method? Assuming you embed that process in a particular method to measure processing time for performance measurement, would you like to modify your code for that? Both are No, aren't they? Aspect-oriented programming provides a "relatively easy" way to solve them. By incorporating this, you can focus on each interest.

I searched for various articles, but I couldn't find an article that was written in Japanese, so [here](https://fernandocejas.com/2014/08/03/aspect-oriented-programming- Write an article based on in-android /). Basically, the code follows the reference page.

Aspect-oriented programming

I will omit the question of what aspect-oriented programming is. Please refer to other articles and literature for academic content and terms.

Installation procedure

From here, I will explain the procedure for making something that works concretely.

Before that, this article will take you to include the source code in your project. If you cut it out as another project and add it to the project as a library, please read as appropriate. I think the only thing you need to read is rewrite build.gradle.

I will explain the following two methods for that.

  1. How to use the plugin
  2. How to use AspectJ live

The former also uses AspectJ, but the plugin does the tedious settings for you. Therefore, it is easy to set up. The latter uses AspectJ as it is, so you need to write a build process in app / build.gradle to build.

Mark creation

First, make a mark to interrupt the process. The landmark is an annotation.

DebugTraceBefore.java


@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD })
public @interface DebugTraceBefore {}

DebugTraceAround.java


@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD })
public @interface DebugTraceAround {}

Please refer to another article for Retention and Target of annotations.

We have prepared two types of landmarks. debugtracebeforeBefore the method that set this annotation, debugtracearoundMakes the process interrupt before and after the method that sets this annotation. The name of the annotation is arbitrary.

Interrupt processing implementation

Next, write what kind of processing is interrupted. This time I will write the process to output the log.

@aspectIs a mark that the process of interrupting by aspect-oriented programming is written here.

@pointcutNow, set a mark to interrupt the process. Let's call the method with this annotation set as pointcut method </ b>.

@beforeOr@after@aroundIndicates where to interrupt the process, and specifies which pointcut method to interrupt.

The method name is arbitrary, but it's a good idea to give it a descriptive name.

Since DebugTraceBefore etc. are annotations, execution(...)among@Is attached.


 If it is a method of the specified class, it can be specified as ```execution (void android.app.Activity.onCreate (..))` ``.

#### **`execution(...)You must specify the full package name for.`**

AspectDebugLog.java


@Aspect
public final class AspectDebugLog {

    private static final String POINTCUT_BEFORE_METHOD =
            "execution(@com.test.aspectorientationprogrammingsample.aspect.DebugTraceBefore * *(..))";
    
    private static final String POINTCUT_AROUND_METHOD =
            "execution(@com.test.aspectorientationprogrammingsample.aspect.DebugTraceAround * *(..))";

    @Pointcut(POINTCUT_BEFORE_METHOD)
    public void pointcutDebugTraceBefore() {}

    @Pointcut(POINTCUT_AROUND_METHOD)
    public void pointcutDebugTraceAround() {}

    @Pointcut("execution(void android.app.Activity.onCreate(..))")
    public void pointcutOnCreate() {}

    @Before("pointcutOnCreate()")
    public void weavePreOnCreate(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();

        Log.d("Aspect", "### weavePreOnCreate: " + className);
    }

    @After("pointcutOnCreate()")
    public void weavePostOnCreate(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();

        Log.d("Aspect", "### weavePostOnCreate: " + className);
    }

    @Before("pointcutDebugTraceBefore()")
    public void weaveDebugTraceBefore(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        Log.d("Aspect", "### weaveDebugTraceBefore: " + className + " " + methodName);
    }

    @Around("pointcutDebugTraceAround()")
    public Object weaveDebugTraceAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String className = methodSignature.getDeclaringType().getSimpleName();
        String methodName = methodSignature.getName();

        Log.d("Aspect", "### weaveDebugTraceAround - start: " + className + " " + methodName);

        Object result = joinPoint.proceed();

        Log.d("Aspect", "### weaveDebugTraceAround - end: " + className + " " + methodName);

        return result;
    }
}

Screen creation

Next, create a screen.

MainActivity.java



public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button1).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    @DebugTraceAround
                    public void onClick(View view) {
                        Log.d("Log", "### onClick: button1");
                    }
                });

        findViewById(R.id.button2).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    @DebugTraceBefore
                    public void onClick(View view) {
                        Log.d("Log", "### onClick: button2");
                    }
                });

    }

//    @Override
//    public void onResume() {
//        super.onResume();
//    }

    @Override
    @DebugTraceBefore
    public void onPause() {
        super.onPause();
    }
}

It is a screen with only two buttons.

onCreate(...)The annotation created this time is not set in.


```onpause() ```To output the log before```debugtracebefore```Is specified.
 I set an annotation to output a log even when the button is clicked.

 That's it for writing Java code.
 From here, rewrite build.gradle.

## How to use the plugin

 In the method of using the plug-in, I will introduce an example of using this.

[GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj)


#### **`app/build.gradle`**
```gradle

//Add from here
buildscript {
    repositories {
        jcenter()
        mavenCentral()
        maven { url "https://jitpack.io" }
    }

    dependencies {
        classpath 'com.github.Archinamon:GradleAspectJ-Android:3.0.3'
    }
}
//Add up to here

apply plugin: 'com.android.application'
apply plugin: 'com.archinamon.aspectj'//add to

android {
    compileSdkVersion 27
    buildToolsVersion "27.0.1"
    defaultConfig {
        applicationId "com.test.aspectorientationprogrammingsample"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            multiDexEnabled true
            minifyEnabled true  // true:Obfuscate(It is necessary to set classes, methods, etc. that are not obfuscated as appropriate.)、false:Do not obfuscate
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:27.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}

How to use AspectJ live

There are some comment outs along the way, but it's okay to delete them.

app/build.gradle


//Add from here
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }

    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.1'
    }
}
//Add up to here

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion "27.0.1"
    defaultConfig {
        applicationId "com.test.aspectorientationprogrammingsample"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            multiDexEnabled true
            minifyEnabled true  // true:Obfuscate(It is necessary to set classes, methods, etc. that are not obfuscated as appropriate.)、false:Do not obfuscate
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    //Add from here
    applicationVariants.all { variant ->
//        JavaCompile javaCompile = variant.javaCompile// use javaCompiler instead of javaCompile
//        javaCompile.doLast {
//            //
//        }

        variant.javaCompiler.doLast {
            String[] args = ["-showWeaveInfo",
                             "-1.8",
                             "-inpath", javaCompile.destinationDir.toString(),
                             "-aspectpath", javaCompile.classpath.asPath,
                             "-d", javaCompile.destinationDir.toString(),
                             "-classpath", javaCompile.classpath.asPath,
                             "-bootclasspath", project.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;
                }
            }
        }
    }
    //Add up to here
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:27.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'

    compile 'org.aspectj:aspectjrt:1.8.1'//add to
}

Run

When you do this, the log will be output as follows: (Since it becomes difficult to read, time etc. are deleted.)

D/Aspect: ### weavePreOnCreate: MainActivity
D/Aspect: ### weavePostOnCreate: MainActivity
D/Aspect: ### weaveDebugTraceBefore: MainActivity onPause
D/Aspect: ### weaveDebugTraceAround - start:  onClick
D/Log: ### onClick: button1
D/Aspect: ### weaveDebugTraceAround - end:  onClick
D/Aspect: ### weaveDebugTraceBefore:  onClick
D/Log: ### onClick: button2

at the end

I also confirmed that it works in my environment, but how was it in your environment? Did it work?

One point to note is that execution (void android.app.Activity.onCreate (..))` `` can be changed to execution (void android.app.Activity.onResume ())` `` No log is output. Because MyActivity does not override onResume. Logs of unimplemented methods are not output. It's natural.

Now you can cut out the extra logic!

Appendix

environment

We have confirmed the operation in the following environment.

environment version
Mac OS Sierra 10.12.6
Android Studio 2.3.3
Java version 8
Gradle version 3.3
Android Plugin version 2.3.3

Recommended Posts

You can do it with copy! Aspect-oriented programming (Android)
Can you do it? Java EE
Notify the build result with slack with CircleCI. You can do it in 5 minutes.
Firebase-Realtime Database on Android that can be used with copy
You can do anything with your Android smartphone or tablet! Summary of Arekore to move with Termux
If you remember this much, you can manage it. AutoLayout written with code
The end of catastrophic programming # 01 "Do it with the absolute value of an integer"
When is it said that you can use try with a Swift error?
[Android Studio] If you think you can't find the latest Support Library, you can find it.
You can do it right away with Serverless Framework Serverless on AWS (API + Lambda [java] is easy to set up)
[Good news] You can still go with Java 8 !!
With Tomcat you can use placeholders ($ {...}) in web.xml
You can eliminate @Param with Kotlin 1.1 and MyBatis 3.4.1+! !!