Aspect Android buried statistics activity page usage duration onResume onPause, and save the duration

Aspect Android buried statistics activity page usage duration onResume onPause, and save the duration

mark:

1.build.gradle under the project

dependencies {
    classpath 'com.android.tools.build:gradle:3.5.4'

    classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
}

2. build.gradle in the app folder

apply plugin: 'com.hujiang.android-aspectjx'
// Configure AspectJX
aspectjx {
    exclude 'androidx.core','androidx.appcompat'
}
dependencies {
    .......
    //Introduce AspectJX dependency
    implementation 'org.aspectj:aspectjrt: 1.9.8'
...
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android.applicationVariants.all { variant ->
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        //The 1.8 below refers to our compatible jdk version
        String[] args = [
                "-showWeaveInfo",
                "-1.8",
                "-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;
            }
        }
    }
}

Notice:

aspectjx {. . . } and 
android.applicationVariants.all {. . . . . }
To be at the same level as android {...}

Example:

@Aspect
public class ClickAspect {
    private static final String TAG = ClickAspect.class.getSimpleName();

    private Map<String, Long> resumeTimeMap = new WeakHashMap<>();
    private Map<String, Long> durationMap = new WeakHashMap<>();
    private Map<String, Boolean> eventTrackerMap = new WeakHashMap<>();

    @Pointcut("execution(* android.view.View.OnClickListener.onClick(android.view.View))")
    public void onClickPointcut() {
    }


    @Pointcut("execution(* android.app.Activity.onCreate(..))")
    public void activityOnCreatePointcut() {

    }

    @Pointcut("execution(* bg.camera.com.MainActivity + .onDestroy(..))")
    public void activityDestroyPointcut() {

    }

    //Define the entry point to match the Activity's onResume and onDestroy methods
    @Pointcut("execution(void android.app.Activity.onResume()) & amp; & amp; target(activity)")
    public void onActivityResumed(Activity activity) {
    }

    @Pointcut("execution(void android.app.Activity.onPause()) & amp; & amp; target(activity)")
    public void onActivityPaused(Activity activity) {
    }
    @Pointcut("execution(void android.app.Activity.onDestroy()) & amp; & amp; target(activity)")
    public void onActivityDestroy(Activity activity) {
    }

    // Perform statistical logic processing before Activity's onCreate method
    @Before("onActivityResumed(activity)")
    public void trackActivityResumed(JoinPoint joinPoint, Activity activity) {
        // Get the class name of Activity
        String className = activity.getClass().getSimpleName();

        // Get the current timestamp
        long startTime = System.currentTimeMillis();


        //Storage the start timestamp of the Activity
        // You can use SharedPreferences or other methods for persistent storage
        updateTrackPointInfoNameValue(activity, className, startTime);

        Log.i(TAG, activity.getClass().getSimpleName() + " onActivityResumed " + startTime);

    }

    // Perform statistical logic processing after Activity's onDestroy method
    @After("onActivityPaused(activity)")
    public void trackActivityPaused(JoinPoint joinPoint, Activity activity) {
        // Get the class name of Activity
        String className = activity.getClass().getSimpleName();

        EventBean starTimeE = getTrackPointInfoNameValue(activity, className);
        long starTime = null != starTimeE ? starTimeE.getEventTime() : 0;
        // Get the current timestamp of stay
        long pausedTime = System.currentTimeMillis() - starTime;
        // Get the storage start timestamp
        // Calculate usage time based on start timestamp and current timestamp

        // Carry out statistical reporting or other processing logic
        updateTrackPointInfoNameValue(activity, className, pausedTime);
        Log.i(TAG, activity.getClass().getSimpleName() + " onActivityPaused " + pausedTime);

    }

    // Perform statistical logic processing after Activity's onDestroy method
    @After("onActivityDestroy(activity)")
    public void trackActivityDestroy(JoinPoint joinPoint, Activity activity) {
        // Get the class name of Activity
        String className = activity.getClass().getSimpleName();

        removeTrackPointInfoNameValue(activity, System.currentTimeMillis());

        Log.i(TAG, activity.getClass().getSimpleName() + " onActivityPaused");

        // After logging out, the life cycle after Fragment onStop will no longer be executed.
        // Perform statistical reporting or other processing logic
    }

    @After("onClickPointcut()")
    public void afterOnClick(JoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        String className = "";
        if (target != null) {
            className = target.getClass().getSimpleName();
        }
        Object[] args = joinPoint.getArgs();
        if (args.length > 0 & amp; & args[0] instanceof View) {
            View view = (View) args[0];
            String entryName = view.getResources().getResourceEntryName(view.getId());
// TrackPoint.onClick(className, entryName);
        }
// joinPoint.proceed();
        // Code executed after the click event is executed
        Log.d(TAG, "Button clicked");
    }



    @Around("activityOnCreatePointcut()")
    public void pageOpen(ProceedingJoinPoint joinPoint) throws Throwable {
        String name = joinPoint.getSignature().toShortString();
        long time = System.currentTimeMillis();
        Object target = joinPoint.getTarget();
        String className = target.getClass().getSimpleName();
// updateTrackPointInfoNameValue(activity, className, 0L);
// TrackPoint.onPageOpen(className);
        joinPoint.proceed();
        Log.e(TAG, name + " activityOnCreatePointcut: " + (System.currentTimeMillis() - time));
    }

    @Around("activityDestroyPointcut()")
    public void pageClose(ProceedingJoinPoint joinPoint) throws Throwable {
        long time = System.currentTimeMillis();
        Object target = joinPoint.getTarget();
        String className = target.getClass().getSimpleName();
// TrackPoint.onPageClose(className);
        joinPoint.proceed();
        Log.e(TAG, className + " activityOnCreatePointcut: " + (System.currentTimeMillis() - time));
    }


    public static boolean hasId(int[] arr, int value) {
        for (int i : arr) {
            if (i == value)
                return true;
        }
        return false;
    }

    /**
     * Performance monitoring
     *
     *AspectJ is actually mainly used in Android for performance monitoring, log burying, etc. Here is a simple example:
     * We monitor the layout loading time to determine whether the layout is too nested or copied too much, causing the Activity to start lagging. First of all, we know that the Activity loads the layout through the setContentView method:
     * 1. Layout analysis process, IO process
     * 2. Create View as a reflection process
     * Both steps are time-consuming operations, so we need to monitor setContentView
     *
     * In daily development, we can upload the time-consuming to the server, collect user information, find stuck activities and make corresponding optimizations
  
     * @param point
     * @throws Throwable
     */
    @Around("execution(* android.app.Activity.setContentView(..))")
    public void getContentViewTime(ProceedingJoinPoint point) throws Throwable {
        String name = point.getSignature().toShortString();
        long time = System.currentTimeMillis();
        point.proceed();
        Log.e(TAG, name + " cost: " + (System.currentTimeMillis() - time));
    }
    @Around("call (* bg.eyccamera.com.MyApp.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.toShortString();
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));
    }

    private void updateTrackPointInfoNameValue(Context context, String name, long time) {
       DatabaseManager.getInstance(context).insertData(new EventBean(name, time));
    }
    private EventBean getTrackPointInfoNameValue(Context context, String name) {
        return getTrackPointInfoNameValue(context, name, 0);
    }
    private EventBean getTrackPointInfoNameValue(Context context, String name, long time) {
        List<EventBean> eventList = null;
        EventBean eventBean = null;
        eventList = DatabaseManager.getInstance(context).getAllData();
        if (null != eventList & amp; & amp; eventList.size() > 0) {
            for (int i = 0; i < eventList.size(); i + + ) {
                if (name.equals(eventList.get(i).getPath())) {
                    eventBean = eventList.get(i);
                    break;
                }
            }
        }
        return eventBean;
    }
    private void removeTrackPointInfoNameValue(Context context, long evenTime) {
        DatabaseManager.getInstance(context).removeData(evenTime/ 1000);
    }
}