1, Before you start:
Centralized login architecture design: this login is not a login request, but a judgment of whether the current login status is available. If it is not, skip the login interface; otherwise, perform the corresponding skip logic. This business is a global business, and the global business is extracted to one aspect.
If it is OOP thought programming, use SharedPreferences to save isLogined login status. If isLogined == true, jump to my integral, my zone, etc. Otherwise, jump to LoginActivity.
2, Implementation mode 1: implementation of dynamic agent at runtime
It is implemented in the way of dynamic agent at run time. Check the code specifically CentralizedLoginArchitecture
Focus on mode 2:
3, Implementation mode 2: AspectJ
1. For example, there are the following requirements:
Similar requirements, such as user behavior statistical analysis, are also a global business. It can also be done by AOP aspect.
Get back to the point AspectJ AspectJ is a framework of aspect oriented programming, which extends the Java language and defines the syntax to implement AOP.
2. Explanation of terms:
PointCut, PointCut
Advice, advice
Joint Point, before after around
3. Integrate AspectJ
AspectJ can be integrated through plug-ins or directly through gradle.
1. Plug in integration
- New main module app
- New android library aspectj
2. gradle integration
- Project root build.gradle add dependency:
It is worth noting that if an error is reported during project synchronization: received close notify during handshake, you can try to use Alibaba cloud's warehouse as above// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' // Version boundary: as-3.0.1 + gradle 4.4-all (NDK environment of r17 needs to be configured) // Or: as-3.2.1 + gradle 4.6-all (normal use, no warning) classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } allprojects { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
- Modify build.gradle in the moudle of app: compile with Aspect specific compiler instead of traditional javac
// Version boundary: as-3.0.1 + gradle 4.4-all (NDK environment of r17 needs to be configured) // Or: as-3.2.1 + gradle 4.6-all (normal use, no warning) buildscript { // Compile with Aspect specific compiler instead of traditional javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } }
- The whole is as follows:
apply plugin: 'com.android.application' // Version boundary: as-3.0.1 + gradle 4.4-all (NDK environment of r17 needs to be configured) // Or: as-3.2.1 + gradle 4.6-all (normal use, no warning) buildscript { // Compile with Aspect specific compiler instead of traditional javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.aspectj" minSdkVersion 15 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' // Package with aspectj implementation 'org.aspectj:aspectjrt:1.8.13' } /** * The following groovy code doesn't need much attention, just as a support */ // Version boundary: as-3.0.1 + gradle 4.4-all (NDK environment of r17 needs to be configured) // Or: as-3.2.1 + gradle 4.6-all (normal use, no warning) import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main 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.8", "-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; } } } }
4. Code operation
Requirements: click log in; view my zone, my coupons, my points (check whether you are logged in before checking, if so, jump to the corresponding functional area, otherwise jump to the login page and log in again); in addition, conduct statistical analysis.
Question: how to achieve statistical analysis of user behavior?
A: third party statistics such as the friendship League. But if you want to implement it, you can only implement it through AOP.
Count the behavior of users clicking a function. (in the actual project, it is stored locally and transmitted to the server every N days).
1. User behavior statistics
-
Custom behavior annotation
//User click trace (Behavior Statistics) user defined notes refer to: open class IOC container @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ClickBehavior { String value(); }
-
Apply behavior annotation to code requiring behavior statistics.
// User behavior statistics @ClickBehavior(value = "Sign in") public void login(View view) { Log.e(TAG, "login: Verification passed, login succeeded"); }
-
Define AspectJ
When writing this class, you need to be careful not to write a wrong symbol, otherwise it is not easy to check.
AspectJ consists of PointCut and advice. Advice has three situations: Before After Around- Use the annotation @ Aspect to define a tangent class.
@Aspect public class ClickBehaviorAspect { }
- Find the entry point to deal with through @ Pointcut
You can view the wildcard character * (..) execution(* com.xxx..(...))// 1. Which annotations are used in the application, put them into the current pointcut for processing (find the pointcut to be processed) // Execution triggers the Aspect class with the method execution as the pointcut. Syntax: execution() // ** (..) wildcard, which can handle all methods of ClickBehavior class, or specify com.example.aspectj.MainActivity.login() method as the entry point @Pointcut("execution(@com.example.aspectj.annotation.ClickBehavior * *(..))") // public void methodPointCut() { }
- Process entry point
// 2. How to deal with the entry point, // Around how to deal with pointcuts @Around("methodPointCut()") public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable { // Follow three rules: 1. Return value Object 2. Parameter ProceedingJoinPoint 3. Throw an exception Throwable }
- Use the annotation @ Aspect to define a tangent class.
2. Centralized login
- Also define LoginCheck annotations
// Check if you are logged in @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginCheck { }
- Using LoginCheck annotations
//Statistics of user behavior after successful login @ClickBehavior(value = "My points") @LoginCheck() public void score(View view) { Log.e(TAG, "score: Jump my points"); startActivity(new Intent(MainActivity.this, OtherActivity.class)); }
- Define Aspect and handle pointcuts
@Aspect public class LoginCheckAspect { private static final String TAG = LoginCheckAspect.class.getSimpleName(); @Pointcut("execution(@com.example.aspectj.annotation.LoginCheck * *(..))") public void methodLoginCheck() { } @Around("methodLoginCheck()") public Object check(ProceedingJoinPoint joinPoint) throws Throwable { Context context = (Context) joinPoint.getThis(); boolean isLogin = false; //In the actual project, read from SharedPreference if (isLogin) { Log.e(TAG, "Login detected"); return joinPoint.proceed();//Login detected, continue with subsequent code } else { Log.e(TAG, "No sign in detected"); context.startActivity(new Intent(context, LoginActivity.class)); return null; //Without login, subsequent code does not need to be executed. } } }
See the code for details. AspectJ
4, Pits encountered in the use of AspectJ
- Critical version as 3.0.1 and gradle 4.4-all need to configure the r17 environment of ndk
- as3.2.1 and gradle 4.6-all are in normal use without obsolete warning
- as3.4.0 and gradle 5.1.1-all have outdated api warnings