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