Aspectj with android library

前端 未结 1 1256
南旧
南旧 2021-02-01 17:52

I have a lib that use aspects and is available via maven, now I\'m trying to use that lib in an android application.

If I include this plug-in in the app gradle file, e

相关标签:
1条回答
  • 2021-02-01 18:40

    I had the same problem. This is all I did to solve it.

    Root/Main Project

    In your root project add the AspectJ tools that contains the ajc compiler that is necessary for weaving your classes. ( You can also add this to your library's build.gradle file but it's better to add it here as the gradle plugin that you will be creating to accommodate your library will be using the ajc.

    buildscript {
        repositories {
            jcenter()
    
    
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:1.2.3'
            classpath 'org.aspectj:aspectjtools:1.8.5'
        }
    

    Library Project

    In your library's build.gradle file ensure that it looks like this similar to this. The main additions are the import statements at the top and the code beneath the android build properties.

    import com.android.build.gradle.LibraryPlugin
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    
    apply plugin: 'com.android.library'
    
    
    dependencies {
        compile 'org.aspectj:aspectjrt:1.8.5'
    }
    android {
        compileSdkVersion 22
        buildToolsVersion "22.0.1"
    
        defaultConfig {
            minSdkVersion 14
            targetSdkVersion 22
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    android.libraryVariants.all { variant ->
        LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
        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;
                }
            }
        }
    }
    
            
    

    So what's happening is when the project is being compiled the ajc(AspectJ's weaver) command compiles and weaves AspectJ and Java source and .class files, producing .class files compliant with any Java VM.

    For this to take place the task needs arguments about your library. That's the reason for creating the args variable.

     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)
                ]
    

    Then the message handler that's created is simply being passed to ajc to accumulate messages of events that are taking place while ajc is compiling/weaving the classes. Then it's being passed to a project logger that then outputs any important errors or warnings that the ajc produced. For example if a pointcut can't be referenced by an advice it will be detected and shown in the gradle console. enter image description here

    So all of what was describe above is basically taking place right here. Where the args and message handler are being passed to the Main function of the ajc(AspectJ compiler).

     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
    

    Gradle Plugin

    The pointcuts/advices of your library weren't being triggered because you were targeting the app module while Aspects were only being woven into your library's module with the com.uphyca.gradle:gradle-android-aspectj-plugin AspectJ plugin. So in order for the Aspects of your library to be woven into your App's module you have to create a gradle plugin for your project. So what you have defined as your goal is your question is not possible, this is the only way it can be done.

    This is how the plugin should look. (Plugin is done in groovy).

    Plugin's build.gradle

    apply plugin: 'groovy'
    
    targetCompatibility = JavaVersion.VERSION_1_7
    sourceCompatibility = JavaVersion.VERSION_1_7
    
    dependencies {
      compile gradleApi()
      compile localGroovy()
      compile 'com.android.tools.build:gradle:1.1.0-rc3'
      compile 'org.aspectj:aspectjtools:1.8.5'
      compile 'org.aspectj:aspectjrt:1.8.5'
    }
    

    Then the actual class.

    import com.android.build.gradle.AppPlugin
    import com.android.build.gradle.LibraryPlugin
    import org.aspectj.bridge.IMessage
    import org.aspectj.bridge.MessageHandler
    import org.aspectj.tools.ajc.Main
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    public class YourPlugin 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 {
                compile 'com.name:example:1.0'
                // TODO this should come transitively
                compile 'org.aspectj:aspectjrt:1.8.5'
            }
    
            variants.all { variant ->
    
                variant.dex.doFirst {
                    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;
                        }
                    }
                }
            }
        }
    }
    

    I know this might seem like a lot but it's alot of copy and pasting because the solution remains the same. If you look closely at the class the same things being done in your library module is now being applied to your app's module. The main modification you would do to this is add your library module to the project's dependencies via the plugin that's done here.

     project.dependencies {
                    compile 'com.letz:example-library:1.0'
                    // TODO this should come transitively
                    compile 'org.aspectj:aspectjrt:1.8.5'
                }
    

    For your library to be available to your plugin while developing you have to ensure that it's being deployed to your local maven repository. This can be done by applying this plugin(https://github.com/dcendents/android-maven-gradle-plugin) to your library module and running the gradle install task.

    Final Steps

    Once all of that is done you can then apply it to a sample app for testing by adding this to it's build.gradle file

    buildscript {
        repositories {
            mavenCentral()
    
            //Only necessary when developing locally.
            mavenLocal()
        }
    
        dependencies {             
    
            classpath 'com.letz:example-plugin:1.0'
        }
    }
    apply plugin: 'example-plugin'
    

    Once that's done your library will be available to the app because it's being added to the project once the plugin has been applied.

    If things are still confusing you are in good luck because the project I implemented this solution is on Github so you can fork it, copy the plugin's project and make the necessary changes.

    The project is called Flender and it's used to annotate methods that require connectivity checking. Here's the link https://github.com/jd-alexander/flender

    Hope this answer helps.

    0 讨论(0)
提交回复
热议问题