Archinamon / android-gradle-aspectj

gradle plug-in adding supports of AspectJ into Android project
Apache License 2.0
363 stars 58 forks source link

GradleAspectJ-Android not working within my app #59

Closed zmorris closed 6 years ago

zmorris commented 6 years ago

Hi I'm on Mac OS X 10.12.6 with Android Studio 2.3.3 and trying to write my own trace functionality so that I can see all of my app's method calls with arguments on Android and stream them to my computer with adb logcat. This trace will give me a broad view of the projects I work on to cut through callback hell in Android API.

I tried your example app at https://github.com/Archinamon/AspectJExampleAndroid and it worked (after many blind alleys with AppMon, Frida, etc) thank you for that!

I've spent many days searching the web and as far as I can tell, your GradleAspectJ-Android library is the only one that provides a working example for catching all app method calls using aspectj on Android without having to annotate every method.

I even managed to strip out the kotlin, groovy, proguard, dagger and annotion stuff, so it simply has these aspectj hooks in Profiler and MyProfilerImpl:

before(): catchAny() {
    writeEnterTime(thisJoinPointStaticPart);
}

after(): catchAny() {
    writeExitTime(thisJoinPointStaticPart);
}

I left me.tatarka.retrolambda in the project because I wasn't sure if it was required for detecting anonymous classes (which I use in place of anonymous methods for now).

However, a few things:


Your AppStartNotifier never gets called if I create AspectJExampleAndroid/app/src/main/aspectj/com/archinamon/xpoint/AppStartNotifier.aj with:

import android.app.Application;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v4.app.NotificationCompat;

aspect AppStartNotifier {

    pointcut postInit(): within(Application) && execution(* Application.onCreate());

    after() returning: postInit() {
        Application app = (Application) thisJoinPoint.getTarget();
        NotificationManager nmng = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
        nmng.notify(9999, new NotificationCompat.Builder(app)
            .setTicker("Hello AspectJ")
            .setContentTitle("Notification from aspectJ")
            .setContentText("privileged aspect AppAdvice")
            .setSmallIcon(R.drawable.ic_launcher)
            .build());
    }
}

You can try it yourself (I don't know if it's not detecting the Application.onCreate() etc). Which means I have no starting point for writing my own aspectj code.


Also, I've spent 2 days so far trying to copy your build.gradle settings to my app and no matter how hard I try, it never calls my Profiler or MyProfilerImpl. I'm pretty sure I've nested it properly in src/my/app/id/aspectj but I can't get it to work. Without a better understanding of how the ajc call works, how aspectj is detecting the app's methods, and how that gets called from gradle, there's simply no further I can go with this.


I even tried building issue https://github.com/Archinamon/GradleAspectJ-Android/issues/12 https://github.com/ardmn/TestAspectJApplication but no matter how hard I try, it no longer builds and runs as of Android Studio 2.3.3. If someone can fork that somewhere and get it working, that might give me a starting point.


So without a minimalist app containing an Empty Activity, Basic Activity (or some other example from the Android Studio project stationary) with the addition of your library and a single aspectj example that demonstrates detecting the app's methods without annotations, developers are going to have a hard time integrating your work. There are simply too many permutations to explore if it doesn't work.

I'm unable to post my app to GitHub, but do you have any suggestions I can try to show:

Here is a minimal AspectJ method autodetection example, forked from yours with the above steps applied https://github.com/zmorris/AspectJExampleAndroid and even though it works, I can't get the same build.gradle settings to work within my app.

Thanks in advance for any help you can provide!

zmorris commented 6 years ago

I finally got it working. I had to rewind to https://github.com/Archinamon/AspectJExampleAndroid/commit/9c08089e4fedf58e0cb1fc644c4af9c021ab2f23 and painstakingly track down why it was working and mine wasn't. It turned out that since my application uses a com.domain.subdomain.appname instead of a com.domain.appname app id, AspectJ wasn't finding my .aj files. Once I separated the signal from the noise, I pushed clean code to:

https://github.com/zmorris/AspectJExampleAndroid

So to add the Profiler aspects to a fresh project, here are the steps:

package com.appname.xpoint;

private pointcut strict(): within(com.domain.subdomain.appname.*) && !within(*.xpoint.*);

...

///// GradleAspectJ-Android /////

android {
    defaultConfig {
        multiDexEnabled true    // avoid 64K method references in .dex file err
    }
}

buildscript {
    repositories {
        mavenCentral()
        maven { url "https://jitpack.io" }
    }
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.3.1'
        classpath 'com.github.Archinamon:GradleAspectJ-Android:3.0.3'
    }
}

///// comment out this section if Android Studio 3+ (retrolambda isn't compatible) /////
apply plugin: 'me.tatarka.retrolambda'

retrolambda {
    defaultMethods true
}
//////////

apply plugin: 'com.archinamon.aspectj'

aspectj {
}

/////////////////////////////////

Now when you run your app, you can set a breakpoint inside before(): catchAny() { and filter by ms to proceed in your Android Monitor tab to see a trace of every method call.

From here you can copy the annotations and other code from AspectJExampleAndroid if you desire.

I still haven't solved why AppStartNotifier isn't being called, however. If someone figures it out, please add a comment to this issue.

@Archinamon please update your documentation or add my branches to your example app thanx.

Well, hope this helps someone down the road!

Archinamon commented 6 years ago

Hello, @zmorris!

Big thanks for your investigation! That tells I have to extract some code out of AjExample app into separate app/lib project. Nowadays also makes sense to update examples upon fresh AS beta and new gradle plugin.

Please, stay tuned for news :)

zmorris commented 6 years ago

Hey thanks for your response. That would be cool to have some standalone examples!

Speaking of which, for anyone who stumbles onto this and just wants to get a barebones trace working on Android, here are the steps I use:

(1) aop-common.zip and aspectj.zip get installed into Android Studio following the instructions

(2) app/src/main/aspectj/com/appname/xpoint/Tracer.aj gets created with the following contents:

package com.appname.xpoint;

import android.util.Log;
import java.util.Arrays;
import org.aspectj.lang.reflect.SourceLocation;

aspect Tracer{
    protected int indentationLevel = 0;

    pointcut traceMethods() : (execution(* *(..)) && !cflow(within(*.xpoint.*)));

    before(): traceMethods() {
        String indent = indentationLevel > 0 ? new String(new char[indentationLevel]).replace("\0", "\t") : "";
        String method = thisJoinPoint.getSignature().toShortString();
        String params = Arrays.toString(thisJoinPoint.getArgs());
        SourceLocation location = thisJoinPoint.getSourceLocation();
        Thread thread = Thread.currentThread();

        Log.d("Method ->", indent + method.substring(0, method.indexOf("(")) + "(" + params.substring(0, params.length() - 1).substring(1) + ") { // " + location.getFileName() + ":" + String.valueOf(location.getLine()) + " [" + thread.getName() + ":" + thread.getId() + "]");

        indentationLevel++;
    }

    after(): traceMethods() {
        indentationLevel--;

        String indent = indentationLevel > 0 ? new String(new char[indentationLevel]).replace("\0", "\t") : "";
        String method = thisJoinPoint.getSignature().toShortString();
        SourceLocation location = thisJoinPoint.getSourceLocation();
        Thread thread = Thread.currentThread();

        Log.d("Method <-", indent + "} // " + method.substring(0, method.indexOf("(")) + "() " + location.getFileName() + ":" + String.valueOf(location.getLine()) + " [" + thread.getName() + ":" + thread.getId() + "]");
    }
}

(3) app/build.gradle gets the following lines added to it:

///// GradleAspectJ-Android /////

buildscript {
    repositories {
        mavenCentral()
        maven { url "https://jitpack.io" }
    }
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.3.1'
        classpath 'com.github.Archinamon:GradleAspectJ-Android:3.0.3'
    }
}

apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.archinamon.aspectj'

retrolambda {
    defaultMethods true
}

aspectj {
}

/////////////////////////////////

(4) build.gradle optionally gets the following lines added to it (this way you can clean your project to prevent caching issues after adding/removing the aj stuff):

task clean(type: Delete) {
    delete rootProject.buildDir
}

(5) adb logcat | grep "D Method" gets called from any terminal window to print a realtime method trace as the app runs on the device or emulator (optionally use adb -d for device or edb -e for emulator). Try grep "D Method ->" to log only entry points and reduce the lines written by half.

The output looks like the following:

10-04 18:15:10.427 10132 10132 D Method ->: MyApplication.onCreate() { // MyApplication.java:32 [main:1]
10-04 18:15:10.433 10132 10132 D Method ->:     MyDatabase.Database.getAssociatedDatabaseClassFile() { // MyDatabase$Database.java:405 [main:1]
10-04 18:15:10.434 10132 10132 D Method <-:     } // MyDatabase.Database.getAssociatedDatabaseClassFile() MyDatabase$Database.java:405 [main:1]
10-04 18:15:10.729 10132 10132 D Method <-: } // MyApplication.onCreate() MyApplication.java:32 [main:1]
10-04 18:15:10.735 10132 10132 D Method ->: SplashActivity.onCreate(null) { // SplashActivity.java:25 [main:1]
10-04 18:15:10.737 10132 10132 D Method ->:     DeviceUtils.isUserDeveloper(com.domain.appname.activities.SplashActivity@6ba700c) { // DeviceUtils.java:43 [main:1]
10-04 18:15:10.738 10132 10132 D Method ->:         DeviceUtils.getDeviceID(com.domain.appname.activities.SplashActivity@6ba700c) { // DeviceUtils.java:55 [main:1]
10-04 18:15:10.738 10132 10132 D Method <-:         } // DeviceUtils.getDeviceID() DeviceUtils.java:55 [main:1]
10-04 18:15:10.738 10132 10132 D Method <-:     } // DeviceUtils.isUserDeveloper() DeviceUtils.java:43 [main:1]
10-04 18:15:10.757 10132 10132 D Method <-: } // SplashActivity.onCreate() SplashActivity.java:25 [main:1]

Each function shows its comma-separated parameter list, with numbers/strings printed in plaintext (in this case SplashActivity is a class so just prints its hash for identification). // MyApplication.java:32 [main:1] indicates the file MyApplication.java line 32 in the main thread (other threads show their unique ids).

See http://www.eclipse.org/aspectj/doc/next/progguide/semantics-pointcuts.html for more information.

Hope this helps someone.

Archinamon commented 6 years ago

Hello again!

This holydays I've made a refine of example project. There has been removed groovy and simplified/updated dependencies. Also added example of new aspectj-provides plugin that will bundle since 3.1.0 version. Next I'll make a separate example with simple tracer/logger aspects basing on your suggestions and fork. Thanks for your help and approach!

Archinamon commented 6 years ago

https://github.com/Archinamon/AndroidAspectJExample2