📜 ⬆️ ⬇️

The introduction of AspectJ in the Android application

I want to share with you my experience of studying the AOP paradigm and developing with its use in a large project.

image

Aspect-oriented programming, or AOP, is a paradigm that distinguishes end-to-end functionality and isolates it in the form of a so-called aspect, or aspect class. This implies the presence of semantic tools and mechanisms for the engine compartment injection of an aspect into the application code. Thus, it turns out that the aspect itself determines which parts of the application it needs to process, while the application does not know (before compilation, of course) that an alien code is impudently and shamelessly entered into its sections.
')
Suppose that we have a rather trivial task - to provide the application with support for some languages ​​(russian, english, italian, french, etc.). You will say that we have a linguistic and regional differentiation of all resources, and you will be right. Except for the case when the application uses not the built-in resources, but “pulls” them from the server. In general, this situation is often encountered and is solved trivially - we add BaseSctivity to the abstract class, which we inherit from the system class, a couple of lines to the handler, and everything works. And you can do without these pair of lines. And even without a base class. And if necessary, just copy one file to the application or add a dependency in the gradle, which will do everything itself.

So, the task is clear, we write.

package com.archinamon.example.xpoint; import android.support.v7.app.AppCompatActivity; import android.app.Application; public aspect LocaleMonitor { pointcut saveLocale(): execution(* MyApplication.onCreate()); pointcut checkLocale(AppCompatActivity activity): this(activity) && execution(* AppCompatActivity+.onCreate(..)); after(): saveLocale() { saveCurrentLocale(); } before(AppCompatActivity activity): checkLocale(activity) { if (isLocaleChanged()) { saveCurrentLocale(); restartApplication(activity); } } void saveCurrentLocale() {/* implementation */} void restartApplication(AppCompatActivity context) {/* implementation */} boolean isLocaleChanged() {/* implementation */} } 

By adding this class to our application, we will teach it to restart itself when changing the language in the system.

For a while I searched for ready-made solutions and tasted them. As a result, he wrote his own version. How it all works, why I had to reinvent the wheel and what came of it all - under the cut!

It should be noted that only the general framework of the aspect is described above, all the mechanics make you add a few extra lines of tips to take context data from slices and connection points . You will find the full aspect code in the demo application at the end of the article. The example illustrates the compactness and brevity of injections, due to which the aspect class permeates our application.

Philosophy


From this reservation it follows that the aspect class is a decorator over an object class. Approaching the task of implementing some functionality on aspects, it is important not to forget about the object-oriented mechanisms of language and ideology as such. Bad way - when an aspect becomes a class that is unreadable and stuffed with utility tools. Best way - to describe all the logic with a separate object module, as isolated as possible from the outside world, which is connected to the application through an aspect decorator.

Speaking about the aspect approach to development as a kind of decorating mechanism, it should be noted its main features, which were partially demonstrated in the example. The notion of procedures, functions, and methods is replaced by the term “advice” . Advice can be applied before ( before ), after ( after ) or instead of ( around ) that part of the code where we have been inserted through the slice . In turn, the notion of a slice ( pointcut ) hides under itself some description of the point (ek) in our program where the aspect class is connected. This description is a whole set of parameters - the name of the class and / or the signature of the method, the place where it was called or executed, etc. One slice can describe at least one join point ( joinPoint ), but the maximum is not limited and can permeate the entire application.

Testing


Another area where aspects can manifest itself in all its glory is testing and debugging. Writing a universal profiler of methods and classes is easier than it might seem.

MyProfilerImpl extends Profiler aspect
 package com.archinamon.example.xpoint; aspect MyProfilerImpl extends Profiler { private pointcut strict(): within(com.archinamon.example.*) && !within(*.xpoint.*); pointcut innerExecution(): strict() && execution(!public !static * *(..)); pointcut constructorCall(): strict() && call(*.new(..)); pointcut publicExecution(): strict() && execution(public !static * *(..)); pointcut staticsOnly(): strict() && execution(static * *(..)); private pointcut catchAny(): innerExecution() || constructorCall() || publicExecution() || staticsOnly(); before(): catchAny() { writeEnterTime(thisJoinPointStaticPart); } after(): catchAny() { writeExitTime(thisJoinPointStaticPart); } } abstract aspect Profiler issingleton() { abstract pointcut innerExecution(); abstract pointcut constructorCall(); abstract pointcut publicExecution(); abstract pointcut staticsOnly(); protected static final Map<String, Long> sTimeData = new ConcurrentHashMap<>(); protected void writeEnterTime(JoinPoint.StaticPart jp) {/* implementation */} protected void writeExitTime(JoinPoint.StaticPart jp) {/* implementation */} } 


The strict () slice cuts the crawl of junction points to avoid traversing the aspect classes themselves. Otherwise, the structure described is extremely simple and intuitive. I deliberately divide the selection of methods, constructors, and static methods into different cuts — this will allow you to flexibly customize the profiler for a specific application and a specific task. The issingleton () marker in the description of an abstract aspect class explicitly declares that each successor will be a singleton. In fact, the record is unnecessary, because all aspect classes are singleton by default. In our case, this marker is needed here to inform about this property of a third-party developer. In my practice, I prefer to label implicit functionality - thus increasing the understanding and readability of the module for others.

We proceed directly to the testing. What aspects are effective?


All this gives a great profit when writing unit and functional tests. But there is always an important "but." Aspect testing is more likely a real-time analysis. To implement the classic testing cycle, it is still necessary to describe some environment or context in which the test decorators will be executed. So, as a replacement, the usual frameworks of AOP will not work.

I summarize all of the above. Aspect approach is well suited as an addition to classical testing to cover analyzers and monitors of the work product in order to detect errors in an already running application. For example, with manual testing or as a beta version of the application.

PS Useful stuff
And on aspects you can cover classes and / or methods with exception handlers with one swipe of your fingers:
 abstract aspect NetworkProtector { abstract pointcut myClass(); Response around(): myClass() && execution(* executeRequest(..)) { try { return proceed(); } catch (NetworkException ex) { Response response = new Response(); response.addError(new Error(ex)); return response; } } } 
This is the easiest option, and it can be more difficult, describing all the stages in detail and in steps.

Bicycle v3.14: Why and How?


You can master AOP without Android. To use technology in my project, I started writing my own plug-in to the Gradle build system. But there are already ready-made solutions, the well-informed reader will say! And he will be right. And it will not be completely right, because all available only for a narrow range of conditions, but did not cover all the necessary and possible combinations. For example, no plugin could work correctly with flavors or create its own source-set for source code decomposition. And some allowed to write aspects only in the style of java-annotations. So I collected all the rakes on the way to implement your own plugin. Which, as a result, covered all the bottlenecks.

And I even got a hacked semantic plugin (hello Spring @ AOP from Ultimate version!) For personal convenience.

We are waiting for the official release in future versions of Android Studio
Issue Title: AspectJ support
Labels: Type-Enhancement
Subcomponent-Tools-Studio Subcomponent-Tools-gradle-ide
Priority-Small Target-1.6

I note a few obvious and not so things that I had to face during development.


What to take with you?


To the heap to aspects, we also take the syntax of Java 8 and StreamAPI from the same eight (this is necessary for functional work with arrays, collections and lists) - after all, the Java API is already included in the Android API, and alas, it does not boast of innovations.

The build.gradle project file is transformed.
 buildscript { repositories { mavenCentral() maven { url 'https://raw.github.com/Archinamon/GradleAspectJ-Android/master/' } maven { url 'https://raw.github.com/Archinamon/RetroStream/master/' } } dependencies { //retrolambda classpath 'me.tatarka:gradle-retrolambda:3.2.3' //aspectj classpath 'com.archinamon:AspectJ-gradle:1.0.16' } } // Required because retrolambda is on maven central repositories { mavenCentral() } apply plugin: 'com.android.application' //or apply plugin: 'com.android.library' apply plugin: 'me.tatarka.retrolambda' apply plugin: 'com.archinamon.aspectj' dependencies { compile 'com.archinamon:RetroStream:1.0.4' } 


That's all! I will leave the detailed setting behind the scenes, but all the details can be found in the source code on the github.

Links


Demonstration project .
Gradle plugin for Android Studio.

Source: https://habr.com/ru/post/269371/


All Articles