Like attention, no more lost, your support means a lot to me!

🔥 Hi, I’m Chouchou. GitHub · Android-Notebook has been included in this article. Welcome to grow up with Chouchou Peng. (Contact information at GitHub)

preface

  • inAndroidIn development, it is a common requirement to limit button quick clicking (button anti-shaking);
  • In this article, I will introduce a useAspectJBased on the annotation processor & runtime annotation reflection. Please be sure to like and follow if you can help, it really means a lot to me.

series

  • The Android | article bring you a comprehensive understanding of AspectJ framework”
  • The Android | use AspectJ limit button quickly click”

Extension of the article

  • On reflection, please read: the Java | reflection: access type information at run time (including the Kotlin)”
  • On the annotation, please read: the Java | this is a comprehensive use of annotations strategy (including the Kotlin)”
  • On the annotation processor (APT), please read: the Java | annotation processor (APT) principle of analytical & practice”

directory


1. Define requirements

Before we begin, let’s define the requirements as follows:

  • Limit quick click requirements schematic:


2. Conventional treatment methods

At present, there are two common processing methods to limit quick click, as follows:

2.1 Encapsulating the proxy class

Encapsulate a proxy class to process click events. The proxy class determines whether to intercept click events by judging the click interval. The code is as follows:

Public abstract class implements View.onClickListener {private long mLastClickTime; private long interval = 1000L; public FastClickListener() { } public FastClickListener(long interval) { this.interval = interval; } @Override public void onClick(View v) { long currentTime = System.currentTimeMillis(); If (currentTime-mlastClickTime > interval) {// Enough time has passed to allow onClick(); mLastClickTime = nowTime; } } protected abstract void onClick(); }Copy the code

Use this proxy class where you need to limit quick clicks as follows:

Tv.setonclicklistener (new FastClickListener() {@override protected void onClick() {// Handle the clicklistener}});Copy the code

2.2 RxAndroid filter expression

The throttleFirst filter expression in RxJava can also limit quick clicks, as follows:

RxView.clicks(view) .throttleFirst(1, TimeUnit.SECONDS) .subscribe(new Consumer<Object>() { @Override public void accept(Object o) throws Exception { // Handle click logic}});Copy the code

2.3 summary

Both the proxy class and RxAndroid filter expressions have two drawbacks:

  • 1. Intrude on core business logic and need to replace code where clicks need to be restricted;
  • 2. The workload of modification is heavy, and the code needs to be modified in every place where the limit click is added.

We needed a solution that circumvented both of these shortcomings — AspectJ. AspectJ is a popular Java AOP (aspect oriented programming) programming extension framework, if still not understand, please be sure to view the article: the Android | article bring you a comprehensive understanding of AspectJ framework”


3. Detailed steps

In the following sections, we will use the AspectJ framework to isolate and maintain the logic that restricts quick clicks as a core concern from the business logic. The specific steps are as follows:

Step 1: AddAspectJRely on

    1. Hujiang-dependentAspectJXGradle plug-in– in the projectbuild.gradleAdd plugin dependencies to:
/ / project level build. Gradle dependencies {classpath 'com. Android. View the build: gradle: 3.5.3' classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: mid-atlantic moved'}Copy the code

If the plugin is too slow to download, you can directly rely on the plugin JAR file, download the plugin to the project root directory (such as /plugins), and add the plugin dependencies to the project build.gradle:

/ / project level build. Gradle dependencies {classpath 'com. Android. View the build: gradle: 3.5.3' classpath fileTree (dir: "plugins", include:['*.jar']) }Copy the code
    1. Application of plug-inApp Modulethebuild.gradleApplication plug-in:
// App Module build.gradle apply plugin: 'Android-aspectJx'...Copy the code
    1. Rely on the AspectJ framework– in the containAspectJThe code ofModulethebuild.gradleAdd dependencies to files:
// Build. Gradle dependencies {... API 'org. Aspectj: aspectjrt: 1.8.9'... }Copy the code

Step 2: Implement a utility class that determines quick clicks

  • Let’s make a judgmentViewQuick click tool class;
  • The implementation principle is to useViewthetagProperty stores the last click time, each click to determine whether the current time has passed enough time to store the time;
  • To avoid callingView#setTag(int key,Object tag)When the incomingkeyWith other places incomingkeyThe id defined in the resource file can effectively ensure global uniqueness, as follows:
// ids.xml
<resources>
    <item type="id" name="view_click_time" />
</resources>
Copy the code
Public class FastClickCheckUtil {/** * Check whether it is a quick click ** @param view click view * @param interval quick click threshold * @return true: */ public static Boolean isFastClick(@nonnull View View, long interval) {int key = r.i.view_click_time; Long currentClickTime = system.currentTimemillis (); if(null == view.getTag(key)){ // 1. SetTag (key, currentClickTime); return false; } long lastClickTime = (long) view.getTag(key); If (currentClickTime-lastClickTime < interval){// If (currentClickTime-lastClickTime < interval){return true; }else{view.setTag(key, currentClickTime); return false; }}}Copy the code

Step 3: DefinitionAspectsection

Using the @aspect annotation to define an Aspect, classes modified with this annotation are recognized by the AspectJ compiler as Aspect classes:

@aspect public class FastClickCheckerAspect {// later fill in}Copy the code

Step 4: DefinitionPointCutThe breakthrough point

Use the @Pointcut annotation to define a Pointcut. At compile time the AspectJ compiler will search for all matching JoinPoints, weaving them into:

@aspect public FastClickAspect {// Define a pointcut: The OnClickListener# onClick () method of the @pointcut (" execution (void android. View. The View. An OnClickListener. OnClick (..) )") public void viewonClick () {}Copy the code

Step 5: DefinitionAdviceTo enhance

There are many ways to enhance it. Here we define a wrap Around enhancement using the @around annotation, which wraps the PointCut and adds crosscutting logic before and after the PointCut as follows:

@aspect public FastClickAspect {// Define pointcut: The OnClickListener# onClick () method of the @pointcut (" execution (void android. View. The View. An OnClickListener. OnClick (..) )") public void viewonClick () {} Public void aroundViewOnClick(ProceedingJoinPoint joinPoint) Throws Throwable {View target = (View) joinPoint.getargs ()[0]; Quick click if (! FastClickCheckUtil.isFastClick(target, 2000)) { joinPoint.proceed(); }}}Copy the code

Step 6: Implement view.onClickListener

In this step, we set the OnClickListener for the View. We have not added any code to restrict quick clicks.

/ / the source code:  public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.text).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i("AspectJ","click"); }}); }}Copy the code

Compile the code, and then decompile the woven.class file executed by the AspectJ compiler. . Don’t know how to find the compiled class files, please check the article: the Android | article bring you a comprehensive understanding of AspectJ framework”

public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(2131361820); findViewById(2131165349).setOnClickListener(new View.OnClickListener() { private static final JoinPoint.StaticPart ajc$tjp_0; // View.OnClickListener#onClick() public void onClick(View v) { View view = v; JoinPoint JoinPoint = factory.makejp (ajC $tjp_0, this, this, view); onClick_aroundBody1$advice(this, view, joinPoint, FastClickAspect.aspectOf(), (ProceedingJoinPoint)joinPoint); } static { ajc$preClinit(); } private static void ajc$preClinit() { Factory factory = new Factory("MainActivity.java", null.class); ajc$tjp_0 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "onClick", "com.have.a.good.time.aspectj.MainActivity$1", "android.view.View", "v", "", "void"), 25); } // The original code in view.onClickListener# onClick(), Private static final void onClick_aroundBody0(null AJC $this, View v, JoinPoint param1JoinPoint) { Log.i("AspectJ", "click"); AroundViewOnClick (); aroundViewOnClick(); aroundViewOnClick(); Advice Private static final void onClick_aroundBody1$Advice (null ajC $this, View V, JoinPoint ThisPoint, FastClickAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint) { View target = (View)joinPoint.getArgs()[0]; if (! FastClickCheckUtil. IsFastClick (target, 2000)) {/ / a quick click, click execute logical ProceedingJoinPoint ProceedingJoinPoint = joinPoint; onClick_aroundBody0(ajc$this, v, (JoinPoint)proceedingJoinPoint); null; }}}); }}Copy the code

summary

Here, we have covered the details of using the AspectJ framework to limit button speed clicking, summarized as follows:

  • use@ Aspect annotationsDescribe asectionClasses decorated with this annotation will be modified byThe AspectJ compilerIdentified as section class;
  • useThe @pointcut annotationTo define aThe breakthrough pointCompile time,The AspectJ compilerAll matches will be searchedJoinPoint, perform weaving;
  • use@ Around annotationsTo define aTo enhanceThe enhancement will be woven into the matchJoinPoint

Evolution of 4.

Now, let’s go back to the requirements defined at the beginning of this article, and there are four of them. The first two of these are already possible using the current scenario, and now we focus on the second two, which allow customization of the time interval and cover as many click scenarios as possible.

  • Demand regression diagram:

4.1 Customizing the Interval

In actual projects, buttons in different scenes often need to limit different clicking intervals, so we need a simple way to customize the time intervals in different scenes, or for some places that do not need to limit quick clicking, there is a way to skip the judgment of quick clicking, the specific method is as follows:

  • Custom annotation
*/ @retention (retentionPolicy.runtime) @target (elementType.method) public @interface FastClick { long interval() default FastClickAspect.FAST_CLICK_INTERVAL_GLOBAL; }Copy the code
  • Modify the section classAdvice
@Aspect public class SingleClickAspect { public static final long FAST_CLICK_INTERVAL_GLOBAL = 1000L; @Pointcut("execution(void android.view.View.OnClickListener.onClick(..) )") public void methodViewOnClick() {} @Around("methodViewOnClick()") public void aroundViewOnClick(ProceedingJoinPoint JoinPoint) throws Throwable {// Get joinPoint's signature MethodSignature = (MethodSignature) joinPoint.getSignature(); / / remove the JoinPoint Method Method. = methodSignature getMethod (); Long interval = FAST_CLICK_INTERVAL_GLOBAL; if (method.isAnnotationPresent(FastClick.class)) { // 2. If the method uses the @fastclick modifier, extract the custom interval FastClick singleClick = method.getannotation (fastclick.class); interval = singleClick.interval(); View target = (View) joinPoint.getargs ()[0]; // 3. Determine whether to quickly click if (! FastClickCheckUtil.isFastClick(target, interval)) { joinPoint.proceed(); }}}Copy the code
  • Using annotations
findViewById(R.id.text).setOnClickListener(new View.OnClickListener() { @FastClick(interval = 5000L) @Override public void onClick(View v) { Log.i("AspectJ","click"); }});Copy the code

4.2 Complete scene coverage

ButterKnife @OnClick android:onClick OK RecyclerView / ListView Java Lambda NO Kotlin Lambda OK DataBinding OK

Editting…


Recommended reading

  • Cryptography | is Base64 encryption algorithm?
  • Interview questions | back algorithm framework to solve problems
  • The interview questions | list questions summary algorithm
  • Java | show you understand the ServiceLoader principle and design idea
  • Computer network | graphic DNS & HTTPDNS principle
  • Say from Android Android | : text to TextView process
  • Android | interview will ask Handler, are you sure you don’t look at it?
  • Android | show you explore LayoutInflater layout analysis principle
  • Android | View & fragments & Window getContext () must return to the Activity?

Thank you! Your “like” is the biggest encouragement for me! Welcome to attentionPeng XuruiThe lot!