In the project development, after the reconstruction of App client, it was found that the friendly statistics code and user behavior log code used for the statistics of user behavior were scattered in each business module. For example, in a certain module, in order to realize the statistics of user behavior I and II, therefore, according to the OOP object-oriented programming thought, It is necessary to write the code of UmENG statistics into the corresponding module in the form of strong dependence, which will cause confusion in the business logic of the project and is not conducive to providing SDK externally. Therefore, through the research, we found that in the Android project, we can use AOP to face the thought of section programming to extract all the UmENG statistical code in the project from each business module and put it into a unified module, so that we can avoid the SDK provided by the user does not need to contain the UMeng SDK and related code.
AOP
Aspect-oriented programming (AOP) is a technique that can dynamically and uniformly add functionality to programs without modifying source code through precompilation and runtime dynamic proxy implementation. AOP is the continuation of OOP, is a hot spot in the software development, is a derived paradigm of functional programming, the code into the designated method of class, designated location on the programming idea. Using AOP, each part of the business logic can be isolated, thus reducing the degree of coupling between each part of the business logic, improving the reusability of the program, and improving the efficiency of development.
AOP and OOP, while very similar in their literal terms, are two design ideas for different domains. OOP (object-oriented programming) in view of the business process of entity and its attributes and behaviors in the abstract encapsulation, in order to obtain more clear and efficient logical unit division, while the AOP is to extract the aspect in the business process, it is facing a step or phase in the process and to obtain low coupling between the parts in the process of logical isolation effect. These two kinds of design ideas have essential difference in goal. For a simple example, encapsulating a business entity such as “Employee” is an OOP/OOD task. We can create an “Employee” class for it and encapsulate the attributes and behaviors related to “Employee”. It would be impossible to encapsulate “Employee” with AOP design ideas. Partitioning the “permission check” action fragment is AOP’s target domain, and encapsulating an action through OOD/OOP is a bit of a nonentity.
The main uses of AOP programming are: logging, behavior statistics, security control, transaction processing, exception handling, system unified authentication, authority management and so on. You can use AOP techniques to separate this code from the business logic code, and by separating these behaviors, you can isolate them into methods that do not guide the business logic, thus changing these behaviors without affecting the business logic code.
Common usage scenarios for AOP programming:
- logging
- persistence
- Behavior monitoring
- Data validation
- The cache
- .
Code injection timing
Code injection takes advantage of Java’s reflection and annotation mechanisms, which can be divided into runtime, load-time, and compile-time depending on the timing of annotation.
- Runtime: Your code is clear about the need for enhancements, such as the need to use dynamic proxies (which is arguably not true code injection).
- Load time: Changes are performed when the target class is loaded by Dalvik or ART. This is an injection of Java bytecode files or Android dex files.
- Compile time: The compiled classes are modified by adding additional steps to the compile process before packaging the distribution.
Common AOP programming libraries
In Java, common open source libraries for faceted programming are: AspectJ: An extension tool for faceted programming that seamlessly integrates with the Java language (available for Android). Javassist for Android: A well-known Java library for manipulating bytecodes ported to the Android platform. DexMaker: A Java language-based API for generating code at compile time or runtime for the Dalvik VM. ASMDEX: A bytecode manipulation library (ASM), but it handles Android executables (DEX bytecode).
Aspectj
AOP is a concept, a specification, and does not itself set a specific language implementation, which actually provides a very broad range of extended capabilities. AspectJ is a long-standing implementation of AOP that can be used in conjunction with Java, as well as ASMDex, but is best known for AspectJ.
At the heart of AspectJ’s use is its compiler. One thing it does is insert AspectJ code into the target program at compile time. The runtime is just like anywhere else, so the key to using it is to use its compiler to compile the code AJC. Ajc builds the relationship between the target program and AspectJ code, and inserts AspectJ code into the PointCut at compile time for AOP purposes.
To understand AspectJ, you need to understand several new concepts introduced by AspectJ.
AspectJ introduces a new concept to Java: Join Point, which includes several new constructs: Pointcuts, advice, Inter-Type declarations, and Aspects.
- Cross-cutting concerns: Even though most classes perform a single, specific function in object-oriented programming, they sometimes need to share common helper functions.
- Advice: code that needs to be injected into the. Class bytecode file. There are usually three: before, after, and around, respectively, before, after, and in place of target code execution. In addition to code injection, you can make other changes, such as adding member variables and interfaces to a class.
- Join point: the point in a program at which code insertion is performed, such as at method invocation or method execution.
- Pointcut: An expression that tells the code injection tool where to inject specific code (that is, which Joint points need to apply specific Advice).
- Aspect: Aspect ties pointcut and advice together. For example, we implement the aspect of adding a print log function to our program by defining a pointcut and giving an accurate advice.
Process executed: A join point is a point specified in the program flow. Pointcuts collect a particular set of join points and the values within those points. A notification is the code that executes when a join point arrives, which is a dynamic part of AspectJ. A join point is a breakpoint set at a particular statement. It collects information about the program stack at the breakpoint, and the notification is the program code to be added before and after the breakpoint. AspectJ also has many different kinds of inter-type declarations, which allow programmers to modify the static structure of a program, names, class members, and relationships between classes. Aspects in AspectJ are modular units of crosscutting concerns. They behave much like classes in the Java language, but aspects also encapsulate pointcuts, notifications, and intertype declarations.
Normally, we would split a simple sample application into two modules, the first containing our Android App code and the second an Android Library project that uses AspectJ to weave in code (code injection). Its engineering structure is shown as follows:
Android integration AspectJ
There are two main ways to integrate AspectJ: 1. Plugin: Gradle-Android-Aspectj-plugin is available on Github. This approach is easy to configure, but tested incompatible with the Databinding framework.
Gradle configuration is a bit tricky to configure, but there are some scripts in the build file that are difficult to understand, but can be used in AS. Article source: https://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
Here’s how to integrate AspectJ into an Android project. 1. First, create a new AS project, and then create a Module (Android Library).
2. Add AspectJ dependencies to gintonic, write build scripts, add tasks, make IDE use AJC as compiler to compile code, and then add this Module to main project Module.
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com. Android. Tools. Build: gradle: 2.1.0'
classpath 'org. Aspectj: aspectjtools:, version 1.8.1'
}
}
apply plugin: 'com.android.library'
repositories {
mavenCentral()
}
dependencies {
compile 'org. Aspectj: aspectjrt:, version 1.8.1'
}
android {
compileSdkVersion 21
buildToolsVersion '21.1.2'
lintOptions {
abortOnError false} } 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", plugin.project.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; }}}}Copy the code
Build. Gradle (Module:app), add AspectJ dependencies, write build scripts, and add tasks. The purpose is to establish communication between the two, and make the IDE use AJC to compile code.
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org. Aspectj: aspectjtools:, version 1.8.1'
}
}
repositories {
mavenCentral()
}
android {
compileSdkVersion 21
buildToolsVersion '21.1.2'
defaultConfig {
applicationId 'com.example.myaspectjapplication'
minSdkVersion 15
targetSdkVersion 21
}
lintOptions {
abortOnError true
}
}
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if(! variant.buildType.isDebuggable()) { log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
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", 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;
}
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com. Android. Support. Test. Espresso: espresso - core: 2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
//compile 'com. Android. Support: appcompat - v7:25.3.1'
//compile 'com. Android. Support. The constraint, the constraint - layout: 1.0.2'
testCompile 'junit: junit: 4.12'
compile project(':gintonic')
compile 'org. Aspectj: aspectjrt:, version 1.8.1'
}
Copy the code
App groovy to build statements in the main module and other library engineering the only difference is to obtain “- the bootclasspath” approach, the main module is configured in the project. The android. Bootclasspath. Join (File. PathSeparator), GetAndroidBuilder ().getBootclasspath (true).join(file.pathseparator).
Note that the above groovy configuration statements only apply to gradle versions higher than 3.3, due to the different gradle versions of the Api for obtaining information such as the path to the class at compile time.
4. Create a new class named “TraceAspect” in Module(gintonic) for testing.
@Aspect
public class TraceAspect {
//ydc start
private static final String TAG = "ydc";
@Before("execution(* android.app.Activity.on**(..) )")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodBefore: " + key+"\n"+joinPoint.getThis()); }}Copy the code
Then we create a new test page LinearLayoutTestActivity class, which looks like this:
public class LinearLayoutTestActivity extends Activity {
private LinearLayout myLinearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_linear_layout_test); myLinearLayout = (LinearLayout) findViewById(R.id.linearLayoutOne); myLinearLayout.invalidate(); }}Copy the code
And then we run the project, and what’s amazing is that the onCreate(Bundle savedInstanceState) method in the LinearLayoutTestActivity is monitored by TraceAspect, Not only intercepts the LinearLayoutTestActivity class information and methods and method parameters. By decompiling APK, you can take a look at the associated code.
As you can see, some AspectJ code was inserted and the onActivityMethodBefore(JoinPoint JoinPoint) method from TraceAspect was called before the onCreate execution.
Reference: AOP programming AspectJ practice implementation data buried points
AspectJ implements non-invasive buried points on the Android side
Meituan mobile performance monitoring