preface

AOP ideas and the AspectJX framework are well known, and AspectJ provides developers with the basic capabilities to implement AOP to meet their business needs.

Here we use the AspectJX framework to implement some interesting performance enhancements. The configuration and use of the AspectJX framework are detailed in the README, as well as in the official demo.

Syntax instructions in AspectJ can be found at github.com/hiphonezhu/… Github.com/HujiangTech…

The scene of actual combat

Log print

In daily development, it is common to print a Log in a key method to output a string and parameter variable values for analysis and debugging, or to print a Log before and after method execution to check the execution time of the method.

Pain points

If you need to add logs to multiple key methods in the main flow of the business and check whether the input parameters and return results of method execution are correct, you can only add Log calls at the beginning of each method to print out each parameter. If the method returns a value, the Log printout is added before the return. If there are multiple if branches in the method that return, the Log must be printed before each branch return.

You need to record the time at the beginning of the method, calculate the time before each return, and print the Log. Not only cumbersome, but also easy to miss.

To solve

You can automatically print the input, output and elapsed time of the method by marking an annotation to the method that you want to print the log, weaving in code to the annotated method at compile time.

Custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoLog {

    /** logcat filter tag */
    String tag(a);

    /** Prints the log level (default VERBOSE) */
    LogLevel level(a) default LogLevel.VERBOSE;
}
Copy the code

AutoLog.java

A custom annotation, AutoLog, is used to mark the method that wants to print the log.

Define aspects and pointcuts
@Aspect
public class LogAspect {

    /** * pointcut adds AutoLog annotations to all method bodies */
    @Pointcut("execution (@com.cdh.aop.toys.annotation.AutoLog * *(..) )")
    public void logMethodExecute(a) {}/ / Advice...
}
Copy the code

Create a LogAspect LogAspect where you define a pointcut to code weave in all methods with AutoLog annotations.

Said the breakthrough point in the execution code in the method body weave, @ com. CDH. Aop) toys. The annotation. AutoLog said add the annotation methods, the first star said no return type, the second star means to match any method name, (..) Indicates an unlimited method entry.

@Aspect
public class LogAspect {

    / / Pointcut...
    
    /** * weaves the method of the pointcut defined above. Around replaces the code in the method body */
    @Around("logMethodExecute()")
    public Object autoLog(ProceedingJoinPoint joinPoint) {
        try {
            // Gets the signature information for the method to be woven into. MethodSignature contains method details
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // Get the AutoLog annotation added to the method
            AutoLog log = methodSignature.getMethod().getAnnotation(AutoLog.class);
            if(log ! =null) {
                // Used to concatenate log details
                StringBuilder sb = new StringBuilder();

                // The name of the splicing method
                String methodName = methodSignature.getMethod().getName();
                sb.append(methodName);

                // Concatenate the values of each parameter
                Object[] args = joinPoint.getArgs();
                if(args ! =null && args.length > 0) {
                    sb.append("(");
                    for (int i=0; i<args.length; i++) {
                        sb.append(args[i]);
                        if(i ! = args.length-1) {
                            sb.append(",");
                        }
                    }
                    sb.append(")");
                }

                // Record the time when execution starts
                long beginTime = System.currentTimeMillis();
                // Execute the original method code and get the return value
                Object result = joinPoint.proceed();
                // Calculate method execution time
                long costTime = System.currentTimeMillis() - beginTime;

                if(methodSignature.getReturnType() ! =void.class) {
                    // If the method return type is not void, concatenate the return value
                    sb.append("= >").append(result);
                }

                // Splicing time
                sb.append("|").append("cost=").append(costTime);

                // The class name and line number of the concatenation method
                String className = methodSignature.getDeclaringType().getSimpleName();
                int srcLine = joinPoint.getSourceLocation().getLine();
                sb.append("| [").append(className).append(":").append(srcLine).append("]");

                // Prints the Log, calling the corresponding method of the Log class using the tag and level set by the AutoLog annotation
                LogUtils.log(log.level(), log.tag(), sb.toString());

                returnresult; }}catch (Throwable t) {
            t.printStackTrace();
        }
        return null; }}Copy the code

LogAspect.java

Use Around can replace the original method of logic, but can be by ProceedingJoinPoint. Proceed to continue the original method logic. In addition to executing the original method logic, the method parameter information is spliced and time consuming calculation, and finally the log is printed out.

Now that you’ve completed a basic log section weaving function, add annotations to the method that you want to print the log automatically.

Use the sample

Feel free to write a few method calls and add AutoLog annotations to them.

public class AddOpWithLog extends BaseOp {

    public AddOpWithLog(BaseOp next) {
        super(next);
    }

    @Override
    @AutoLog(tag=TAG, level=LogLevel.DEBUG)
    protected int onOperate(int value) {
        return value + new Random().nextInt(10); }}Copy the code

AddOpWithLog.java

public class SubOpWithLog extends BaseOp {

    public SubOpWithLog(BaseOp next) {
        super(next);
    }

    @Override
    @AutoLog(tag=TAG, level=LogLevel.WARN)
    protected int onOperate(int value) {
        return value - new Random().nextInt(10); }}Copy the code

SubOpWithLog.java

public class MulOpWithLog extends BaseOp {

    public MulOpWithLog(BaseOp next) {
        super(next);
    }

    @Override
    @AutoLog(tag=TAG, level=LogLevel.WARN)
    protected int onOperate(int value) {
        return value * new Random().nextInt(10); }}Copy the code

MulOpWithLog.java

public class DivOpWithLog extends BaseOp {

    public DivOpWithLog(BaseOp next) {
        super(next);
    }

    @Override
    @AutoLog(tag=TAG, level=LogLevel.DEBUG)
    protected int onOperate(int value) {
        return value / (new Random().nextInt(10) +1); }}Copy the code

DivOpWithLog.java

@AutoLog(tag = BaseOp.TAG, level = LogLevel.DEBUG)
public void doWithLog(View view) {
    BaseOp div = new DivOpWithLog(null);
    BaseOp mul = new MulOpWithLog(div);
    BaseOp sub = new SubOpWithLog(mul);
    BaseOp add = new AddOpWithLog(sub);
    int result = add.operate(100);
    Toast.makeText(this, result+"", Toast.LENGTH_SHORT).show();
}
Copy the code

MainActivity.java

Run the doWithLog method to view the logcat output log:

The result looks like this, with the name of the method and the value of each input parameter and the return value of the direct result (if void, no return value is printed), as well as the execution time of the method in ms.

thread

Daily development often involves thread switching operations, such as network requests, file IO, and other time-consuming operations that need to be executed in the slave thread, and UI operations that need to be cut back to the main thread.

Pain points

Each time a thread is switched, a Runnable needs to be created to execute business logic in its run method, or to utilize AsyncTask and Executor (cutting back to the main thread also needs to utilize Handler), which needs to be added at the method call or inside the method body to switch threads.

If you can tag a method so that it is automatically executed on the main or child thread, you can make the method call process clear and greatly reduce the amount of code.

To solve

It is also possible to use annotations to mark methods in the compiler to weave in code called by threads for automatic thread switching. Note: the implementation here is weak and provides only an idea and demonstration.

Custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoThread {
    /** * Specifies the method to run on the MAIN/child thread * Optional enumeration values: MAIN (expected to run on the MAIN thread) BACKGROUND (expected to run on the child thread) */
    ThreadScene scene(a);
    /** * sets whether to block until the method completes (default true) */
    boolean waitUntilDone(a) default true;
}
Copy the code

Java custom annotation AutoThread, used to mark methods that want to automatically switch threads to run.

Define aspects and pointcuts
@Aspect
public class ThreadAspect {

    @Pointcut("execution (@com.cdh.aop.toys.annotation.AutoThread * *(..) )")
    public void threadSceneTransition(a) {}/ / Advice...
}
Copy the code

ThreadAspect. Java defines a section ThreadAspect and a pointcut threadSceneTransition.

Said the breakthrough point in the execution code in the method body weave, @ com. CDH. Aop) toys. The annotation. AutoThread said add the annotation methods, the first star said no return type, the second star means to match any method name, (..) Indicates an unlimited method entry.

@Aspect
public class ThreadAspect {

    / / Pointcut...
    
    @Around("threadSceneTransition()")
    public Object executeInThread(final ProceedingJoinPoint joinPoint) {
        // result Saves the execution result of the original method
        final Object[] result = {null};
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // Get the method annotation AutoThread we added
            AutoThread thread = methodSignature.getMethod().getAnnotation(AutoThread.class);
            if(thread ! =null) {
                // Get the ThreadScene value set in the annotation,
                ThreadScene threadScene = thread.scene();
                if(threadScene == ThreadScene.MAIN && ! ThreadUtils.isMainThread()) {// If you expect to run on the main thread, but are not currently running on the main thread
                    // Switch to main thread execution
                    ThreadUtils.runOnMainThread(new Runnable() {
                        @Override
                        public void run(a) {
                            try {
                                // Execute the original method and save the result
                                result[0] = joinPoint.proceed();
                            } catch (Throwable throwable) {
                                throwable.printStackTrace();
                            }
                        }
                    }, thread.waitUntilDone());
                } else if (threadScene == ThreadScene.BACKGROUND && ThreadUtils.isMainThread()) {
                    // If you expect to run on a child thread, but are currently running on the main thread
                    // Switch to child thread execution
                    ThreadUtils.run(new Runnable() {
                        @Override
                        public void run(a) {
                            try {
                                // Execute the original method and save the result
                                result[0] = joinPoint.proceed();
                            } catch (Throwable throwable) {
                                throwable.printStackTrace();
                            }
                        }
                    }, thread.waitUntilDone());
                } else {
                    // Run directly in the current thread
                    result[0] = joinPoint.proceed(); }}}catch (Throwable t) {
            t.printStackTrace();
        }
        // Return the value returned by the original method
        return result[0]; }}Copy the code

Here, Around is used to replace the logic of the original method. Before executing the original method, thread judgment is performed, and then the corresponding thread is switched to execute the original method.

Thread switching method

See above, when need to switch the main thread, called ThreadUtils. RunOnMainThread to perform the original method, look at the internal implementation of this method:

/** * the main thread executes **@paramRunnable Task to be executed *@paramWhether the block waits for execution to complete */
public static void runOnMainThread(Runnable runnable, boolean block) {
    if (isMainThread()) {
        runnable.run();
        return;
    }

    // Use CountDownLatch to block the current thread
    CountDownLatch latch = null;
    if (block) {
        latch = new CountDownLatch(1);
    }
    // Use Pair to save Runnable and CountDownLatch
    Pair<Runnable, CountDownLatch> pair = new Pair<>(runnable, latch);
    // Send the Pair argument to the main thread for processing
    getMainHandler().obtainMessage(WHAT_RUN_ON_MAIN, pair).sendToTarget();

    if (block) {
        try {
            // Wait for CountDownLatch to drop to 0
            latch.await();
        } catch(InterruptedException e) { e.printStackTrace(); }}}private static class MainHandler extends Handler {

    MainHandler() {
        super(Looper.getMainLooper());
    }

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == WHAT_RUN_ON_MAIN) {
            // Fetch the Pair parameters
            Pair<Runnable, CountDownLatch> pair = (Pair<Runnable, CountDownLatch>) msg.obj;
            try {
                // Run the Runnable parameter
                pair.first.run();
            } finally {
                if(pair.second ! =null) {
                    // Lower the CountDownLatch by 1, which will lower the latch to 0 to wake up the previously blocked wait
                    pair.second.countDown();
                }
            }
        }
    }
}
Copy the code

Threadutils.java switches to the main thread again using the main thread Handler. If you set waiting for results to return, CountDownLatch is created, blocking the current calling thread until the main thread completes its task.

Let’s look at the threadutils.run method that toggles child thread execution:

/** * the child thread executes **@paramRunnable Task to be executed *@paramWhether the block waits for execution to complete */
public static void run(final Runnable runnable, final boolean block) {
    Future future = getExecutorService().submit(new Runnable() {
        @Override
        public void run(a) {
            // Run on child threads through the thread poolrunnable.run(); }});if (block) {
        try {
            // Wait for the execution result
            future.get();
        } catch(Exception e) { e.printStackTrace(); }}}Copy the code

To switch to a child thread is to submit the task execution through the thread pool.

Use the sample

Write the same methods and add the AutoThread annotation

public class AddOpInThread extends BaseOp {

    public AddOpInThread(BaseOp next) {
        super(next);
    }

    @Override
    @AutoThread(scene = ThreadScene.BACKGROUND)
    protected int onOperate(int value) {
        // Prints the thread on which the method is run
        Log.w(BaseOp.TAG, "AddOpInThread onOperate: " + java.lang.Thread.currentThread());
        return value + new Random().nextInt(10); }}Copy the code

The addopinThread. Java method annotation specifies to run on child threads.

public class SubOpInThread extends BaseOp {

    public SubOpInThread(BaseOp next) {
        super(next);
    }

    @Override
    @AutoThread(scene = ThreadScene.MAIN)
    protected int onOperate(int value) {
        // Prints the thread on which the method is run
        Log.w(BaseOp.TAG, "SubOpInThread onOperate: " + java.lang.Thread.currentThread());
        return value - new Random().nextInt(10); }}Copy the code

Subopinthread. Java specifies to run on the main thread.

public class MulOpInThread extends BaseOp {

    public MulOpInThread(BaseOp next) {
        super(next);
    }

    @Override
    @AutoThread(scene = ThreadScene.MAIN)
    protected int onOperate(int value) {
        // Prints the thread on which the method is run
        Log.w(BaseOp.TAG, "MulOpInThread onOperate: " + java.lang.Thread.currentThread());
        return value * new Random().nextInt(10); }}Copy the code

Mulopinthread. Java specifies to run on the main thread.

public class DivOpInThread extends BaseOp {

    public DivOpInThread(BaseOp next) {
        super(next);
    }

    @Override
    @AutoThread(scene = ThreadScene.BACKGROUND)
    protected int onOperate(int value) {
        // Prints the thread on which the method is run
        Log.w(BaseOp.TAG, "DivOpInThread onOperate: " + java.lang.Thread.currentThread());
        return value / (new Random().nextInt(10) +1); }}Copy the code

Divopinthread. Java specifies to run on child threads.

Next call the method:

public void doWithThread(View view) {
    BaseOp div = new DivOpInThread(null);
    BaseOp mul = new MulOpInThread(div);
    BaseOp sub = new SubOpInThread(mul);
    BaseOp add = new AddOpInThread(sub);
    int result = add.operate(100);
    Toast.makeText(this, result+"", Toast.LENGTH_SHORT).show();
}
Copy the code

MainActivity.java

Run the doWithThread method to view the logcat output log:

You can see that the first method has been switched to run in the child thread, the second and third methods have been switched to run in the main thread, and the fourth method has been switched to run in the child thread.

Thread name detection

When creating a Thread, you need to set a name for it to analyze and locate the service module to which the Thread belongs.

Pain points

There are omissions in the development process or improper use of threads in the introduction of third-party libraries, such as directly created threads to run, or anonymous threads, etc. When you want to analyze threads, you will see a lot of thread-1, 2, and 3 threads. If you have a clear name, it is easy to see which business the Thread belongs to at a glance.

To solve

You can detect Thread names before start by intercepting all thread. start calls. If it is the default name, it is warned and the thread name is automatically changed.

Define aspects and pointcuts

Here we put thread-dependent weaving operations into a single section ThreadAspect:

@Aspect
public class ThreadAspect {

    private static final String TAG = "ThreadAspect";
    
    @Before("call (* java.lang.Thread.start(..) )")
    public void callThreadStart(JoinPoint joinPoint) {
        try {
            // Get the joinPoint Thread instance that executes the start method
            Thread thread = (Thread) joinPoint.getTarget();
            // Detect thread names with regex
            if (ThreadUtils.isDefaultThreadName(thread)) {
                // Prints a warning message (thread object and the location of the method call)
                LogUtils.e(TAG, "Found the starting thread [" + thread + "] No custom thread name! [" + joinPoint.getSourceLocation() + "]");
                // Sets the thread name, which concatenates this object in the context of the method call
                thread.setName(thread.getName() + "-"+ joinPoint.getThis()); }}catch(Throwable t) { t.printStackTrace(); }}}Copy the code

ThreadAspect.java

Before denotes weaving Before the pointcut, call denotes calling the method, the first star denotes unlimited return type, java.lang.thread. start denotes the start method that exactly matches Thread, (..) Indicates an unlimited method parameter.

The pointcut weaves name detection and name setting code in front of all places where Thread. start is called.

Thread name detection

If the name of a thread is not specified, the default name is used.

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum(a) {
    return threadInitNumber++;
}

public Thread(a) {
    // The third parameter is the default name
    init(null.null."Thread-" + nextThreadNum(), 0);
}
Copy the code

When a Thread is created, it is given a default name, thread-numeric increment, so you can match this name to determine whether a Thread has a custom name.

See ThreadUtils. IsDefaultThreadName method:

public static boolean isDefaultThreadName(Thread thread) {
    String name = thread.getName();
    String pattern = "^Thread-[1-9]\\d*$";
    return Pattern.matches(pattern, name);
}
Copy the code

If a match is complete, the current name is the default name.

Use the sample

Create several threads with and without names, and start running.

public void renameThreadName(View view) {
    // The name is not set
    new Thread(new PrintNameRunnable()).start();

    // Set the name
    Thread t = new Thread(new PrintNameRunnable());
    t.setName("myname-thread-test");
    t.start();
}

private static class PrintNameRunnable implements Runnable {
    @Override
    public void run(a) {
        // Prints the thread name
        Log.d(TAG, "thread name: "+ Thread.currentThread().getName()); }}Copy the code

View the logcat output after the run:

Inspection by Ministry of Industry and Information Technology

The Ministry of Industry and Information Technology issued a statement requiring apps to not collect information related to users and devices before users agree to privacy agreements, such as IMEI, DEVICE ID, installed APP list, address book and other information that can uniquely identify users and users’ device privacy.

Note that the user consent privacy agreement here is different from the APP permission application and belongs to the privacy agreement at the business level. If the user does not agree to the privacy agreement, relevant information cannot be obtained in the business code even if all permissions of the APP are opened in the system application Settings.

As shown in the figure, the required information can be obtained from the business code only after the user agrees.

Pain points

Check all areas of your code that involve obtaining private information. In case of omission, will face the Ministry of Industry and Information Technology off the shelves rectification punishment. In addition, some third-party SDKS do not strictly follow the requirements of the Ministry of Industry and Information Technology, and will privately obtain user and device information.

To solve

Check code can be woven in front of all places that call the privacy information API, covering its own business code and third-party SDK code for interception.

Note that the call behavior in dynamically loaded code and behavior in the native layer cannot be completely intercepted.

Intercepting API calls directly
@Aspect
public class PrivacyAspect {

    // Intercepts calls to get information about the list of apps installed on the phone
    private static final String POINT_CUT_GET_INSTALLED_APPLICATION = "call (* android.content.pm.PackageManager.getInstalledApplications(..) )";
    private static final String POINT_CUT_GET_INSTALLED_PACKAGES = "call (* android.content.pm.PackageManager.getInstalledPackages(..) )";

    // Intercepts calls to obtain iMEI and device ID
    private static final String POINT_CUT_GET_IMEI = "call (* android.telephony.TelephonyManager.getImei(..) )";
    private static final String POINT_CUT_GET_DEVICE_ID = "call(* android.telephony.TelephonyManager.getDeviceId(..) )";

    // Intercepts a call to the getLine1Number method
    private static final String POINT_CUT_GET_LINE_NUMBER = "call (* android.telephony.TelephonyManager.getLine1Number(..) )";

    // Intercepts calls to locate
    private static final String POINT_CUT_GET_LAST_KNOWN_LOCATION = "call (* android.location.LocationManager.getLastKnownLocation(..) )";
    private static final String POINT_CUT_REQUEST_LOCATION_UPDATES = "call (* android.location.LocationManager.requestLocationUpdates(..) )";
    private static final String POINT_CUT_REQUEST_LOCATION_SINGLE = "call (* android.location.LocationManager.requestSingleUpdate(..) )";
    
    / /...
    
    @Around(POINT_CUT_GET_INSTALLED_APPLICATION)
    public Object callGetInstalledApplications(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, new ArrayList<ApplicationInfo>());
    }

    @Around(POINT_CUT_GET_INSTALLED_PACKAGES)
    public Object callGetInstalledPackages(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, new ArrayList<PackageInfo>());
    }

    @Around(POINT_CUT_GET_IMEI)
    public Object callGetImei(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, "");
    }

    @Around(POINT_CUT_GET_DEVICE_ID)
    public Object callGetDeviceId(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, "");
    }

    @Around(POINT_CUT_GET_LINE_NUMBER)
    public Object callGetLine1Number(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, "");
    }

    @Around(POINT_CUT_GET_LAST_KNOWN_LOCATION)
    public Object callGetLastKnownLocation(ProceedingJoinPoint joinPoint) {
        return handleProceedingJoinPoint(joinPoint, null);
    }

    @Around(POINT_CUT_REQUEST_LOCATION_UPDATES)
    public void callRequestLocationUpdates(ProceedingJoinPoint joinPoint) {
        handleProceedingJoinPoint(joinPoint, null);
    }

    @Around(POINT_CUT_REQUEST_LOCATION_SINGLE)
    public void callRequestSingleUpdate(ProceedingJoinPoint joinPoint) {
        handleProceedingJoinPoint(joinPoint, null);
    }
    
    / /...
}
Copy the code

PrivacyAspect.java

Define a facet, PrivacyAspect, and a pointcut to examine the method being invoked. Substitution of use Around code for sensitive API calls, call handleProceedingJoinPoint processing, the first parameter is the join point ProceedingJoinPoint, the second parameter is the default return value (if the method returns a value, will return the result).

Then enter handleProceedingJoinPoint method:

private Object handleProceedingJoinPoint(ProceedingJoinPoint joinPoint, Object fakeResult) {
    if(! PrivacyController.isUserAllowed()) {// If the user does not agree
        StringBuilder sb = new StringBuilder();
        // Prints the method called and the location of the call
        sb.append("Performed without user consent").append(joinPoint.getSignature().toShortString())
                .append("[").append(joinPoint.getSourceLocation()).append("]");
        LogUtils.e(TAG,  sb.toString());
        // Return an empty default value
        return fakeResult;
    }

    try {
        // Execute the original method and return the original result
        return joinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    return fakeResult;
}
Copy the code

This method determines whether the user agrees or not. If not agreed, an empty return value is returned. Otherwise, pass and call the original method.

Intercepting API reflection calls

Some third-party SDKS call sensitive APIS through reflection, and encrypt the method name string to bypass the static check, so reflection calls also need to be intercepted.

@Aspect
public class PrivacyAspect {
    
    // Intercepts reflection calls
    private static final String POINT_CUT_METHOD_INVOKE = "call (* java.lang.reflect.Method.invoke(..) )";
    // Reflection method blacklist
    private static final List<String> REFLECT_METHOD_BLACKLIST = Arrays.asList(
            "getInstalledApplications"."getInstalledPackages"."getImei"."getDeviceId"."getLine1Number"."getLastKnownLocation"."loadClass"
    );
    
    @Around(POINT_CUT_METHOD_INVOKE)
    public Object callReflectInvoke(ProceedingJoinPoint joinPoint) {
        // Gets the name of the method called at this join point
        String methodName = ((Method) joinPoint.getTarget()).getName();
        if (REFLECT_METHOD_BLACKLIST.contains(methodName)) {
            // If the method is in the blacklist, check it
            return handleProceedingJoinPoint(joinPoint, null);
        }

        try {
            // Execute the original method and return the original result
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null; }}Copy the code

By intercepting a call to Method.invoke, determine whether the Method being reflected is a blacklisted Method.

Intercepts dynamically loaded calls
@Aspect
public class PrivacyAspect {

    // Intercepts the call to load the class
    private static final String POINT_CUT_DEX_FIND_CLASS = "call (* java.lang.ClassLoader.loadClass(..) )";
    
    @Around(POINT_CUT_DEX_FIND_CLASS)
    public Object callLoadClass(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        // Prints information about the join point
        StringBuilder sb = new StringBuilder();
        sb.append(joinPoint.getThis()).append("Medium dynamic loading");
        Object[] args = joinPoint.getArgs();
        if(args ! =null && args.length > 0) {
            sb.append("\" ").append(args[0]).append("\" ");
        }
        sb.append("Get").append(result);
        sb.append("").append(joinPoint.getSourceLocation());
        LogUtils.w(TAG, sb.toString());

        returnresult; }}Copy the code

The position at which the log output is called after intercepting loadClass.

Use the sample

public void interceptPrivacy(View view) {
    Log.d(TAG, "Customer agrees:" + PrivacyController.isUserAllowed());

    // Get the information about the mobile installed application
    List<ApplicationInfo> applicationInfos = DeviceUtils.getInstalledApplications(this);
    if(applicationInfos ! =null && applicationInfos.size() > 5) {
        applicationInfos = applicationInfos.subList(0.5);
    }
    Log.d(TAG, "getInstalledApplications: " + applicationInfos);

    // Get the information about the mobile installed application
    List<PackageInfo> packageInfos = DeviceUtils.getInstalledPackages(this);
    if(packageInfos ! =null && packageInfos.size() > 5) {
        packageInfos = packageInfos.subList(0.5);
    }
    Log.d(TAG, "getInstalledPackages: " + packageInfos);

    / / get the imei
    Log.d(TAG, "getImei: " + DeviceUtils.getImeiValue(this));
    // Get the phone number
    Log.d(TAG, "getLine1Number: " + DeviceUtils.getLine1Number(this));
    // Get location information
    Log.d(TAG, "getLastKnownLocation: " + DeviceUtils.getLastKnownLocation(this));

    try {
        // Load a class
        Log.d(TAG, "loadClass: " + getClassLoader().loadClass("com.cdh.aop.sample.op.BaseOp"));
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    try {
        // Use reflection to get information about the phone's installed application
        PackageManager pm = getPackageManager();
        Method method = PackageManager.class.getDeclaredMethod("getInstalledApplications".int.class);
        List<ApplicationInfo> list = (List<ApplicationInfo>) method.invoke(pm, 0);
        if(list ! =null && list.size() > 5) {
            list = list.subList(0.5);
        }
        Log.d(TAG, "reflect getInstalledApplications: " + list);
    } catch(Exception e) { e.printStackTrace(); }}Copy the code

View the logcat output after the run:

The end of the

After integrating with the AspectJX framework to package APK, you may encounter a ClassNotFoundException. Decomcompiling APK will find that many classes are not entered, even Application. Most of this is due to conflicts caused by the use of the AspectJ framework in dependent tripartite libraries, or errors in the syntax of self-written pointcuts, or problems with weaving code, such as method return values that do not match, or conflicting notifications defined for the same pointcut. If an error occurs, an error message is displayed in the build.

Without AOP thinking and the AspectJ framework to implement the above requirements, there would be a lot of tedious work. Through the application of a few simple scenarios, it can be found that a deep understanding of AOP ideas and the use of AspectJ will greatly improve the efficiency of architectural design and development.

See the sample complete source codeEfficiency-Toys