The original link

A few days ago, I found a bunch of entries in the App Settings page. Clicking on some items will launch two pages. For example, take the wechat discovery page for example:



Here, clicking on each entry will open a new Activity, not just the moments. However, if you click quickly, for example, clicking on a nearby person, there will be two pages of nearby people. You can really believe this.

So how do we solve this problem?

  1. Set all activities to singleTop
  2. Make an onClick event on the button for nearby people to prevent double clicking

Both methods are fine, but there are problems with both.

Set all activities to singleTop

If the current Activity is already at the top of the stack, you don’t start a new Activity, you just call its onNewIntent. Can we rule out the possibility of opening the same page when the Activity is already at the top of the stack? Can’t! The business is ever-changing.

So, is singleTask ok? Sorry, singleTask says that if there is an Activity on the page stack, it will reuse it and kill all activities on top of it to put itself at the top of the stack. It is also unlikely that all activities will be set to singleTask mode.

Therefore, the disadvantages of the first method are obvious:

  • We should not set all activities to “singleTop” in order to open more pages.
  • Also, this is very limited because it misses the nature of the problem, which is caused by onClick being executed twice and having two activities as a result.
  • We’re putting the cart before the horse by tolerating the result rather than dealing with the source of the phenomenon.

Make an exception to the button’s onClick event to prevent double clicking

Well, this time you seem to have found the cause of the problem. If so, you write:

    btNeayby.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            long nowTime = System.currentTimeMillis();
            if (nowTime - mLastClickTime > TIME_INTERVAL) {
                enterActiviy()
            } 
        }
    });
}Copy the code

Some variables are not given here, I believe you can also understand the logic, for one click can prevent the effect of repeated clicks, so, what about other places? Everywhere else you have to write this logic and define the time when the last click was made

So, is there a way, without having to define these variables, without having to write the wrapping logic, the answer is yes

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

There is no definition of the mLastClickTime variable. However, there must be many places in the project that need to click events. You must use RxView for every place

Android APT (compile-time code generation), I’m sure you know what I’m talking about. If you’re not familiar with this gray tech, check out this article on Android APT (compile-time Code Generation) best practices

Let me help you out.

  1. Define an Annotation, for example, called SingleClick
  2. With APT, the grey technology, the relevant code is generated at compile time based on this Annotation.

Those of you who know ButterKnife should know:

 @OnClick(R.id.bt_submit)
    public void submit() { title.setText(" hello world") ; }Copy the code

This note, what does he actually do?

  1. The generated code binds R.i.dbt_submit to a variable via findViewBy(), for examplemSubmitCome up.
  2. tomSubmitSet the onClick event.
  3. In the handling of the onClick event, the handler is forwarded to Submit, which is handled only by the onClick annotation method
  @Override
    public void onClick(View v) {
        Method method = null;
        try {
            method = receiver.getClass().getMethod(clickMethodName);
            if(method ! = null) { method.invoke(receiver); } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, " "Not found". + clickMethodName + " Method & quot;) ; } try {if (method == null) {
                    method = receiver.getClass().getMethod(clickMethodName, View.class);
                    if(method ! = null) { method.invoke(receiver, v); } } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, " "View type argument not found"; + clickMethodName + " Method & quot;) ; }}Copy the code

However, ButterKnife’s OnClick annotation does not prevent double clicking.

Could you please add ButterKnife, of course it is possible

And when I add them, can I write them like this?

 @SingleClick(R.id.bt_submit)
 public void submit() { title.setText(" hello world") ; }Copy the code

Then submit is forwarded, depending on the logic of your APT.

And now that we’re at this point, is that it? Of course not. We:

We’re not satisfied yet, because what if I just don’t like using APT frameworks? I just don’t like writing view.setonlicklistenser (…)

We finally have the ultimate killer, AOP

Maybe, those of you who know AOP will understand in seconds, yes, faceted programming, why don’t we intercept onClick to do some articles?

Github – Jarryleo /SingleClick: Android Anti-reclick library

However, I see something I don’t like. Why define a SingleClick annotation when I already use AOP? Why don’t I just block all onclicks?

If so, my plan is as follows:

@Aspect public class OnClickAspect { @Pointcut(" execution(* onClick(..) )") public voidonClickPointcut() { } @Around(" onClickPointcut()") Public void onClick(ProceedingJoinPoint joinPoint) throws Throwable {// Retrieve method parameters, OnClick (View viw) = null;for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
                break; }}if (view == null) {
            joinPoint.proceed();
            return; MethodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod();if (method.isAnnotationPresent(MutilClick.class)){
            joinPoint.proceed();
            return;
        }
        if(! XClickUtil. IsFastDoubleClick (joinPoint. GetTarget (), the view, 500)) {/ / not quick hits, the implementation of the original method joinPoint. Proceed (); }}}Copy the code

Of course, IN the process of doing this, I also found four pits:

  1. What if some clicks require more than one click?
  2. What happens if a forward is made in the onClick event?
  3. What if super.onclick (v) appears?
  4. How to deal with NPE when release package is made?

The first problem above exists objectively. For example, we click a button repeatedly for several times to pop up our backdoor. Therefore, I have added a MutilClick note to avoid this kind of situation, which is very rare, maybe one or two.

However, for

Forwarding was done in the onClick event

view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { listener.onClick(v); }});Copy the code

OnClick (v) = onClick(v) = onClick(v) = onClick(v) In practice this is equivalent to:

A.click(view1)

B.click(view1)Copy the code

Therefore, you can determine whether the body of the call is consistent, as described below.

super.onClick(v)

 @Override
    public void onClick(View view) {
        super.onClick(view);
        switch (view.getId()) {Copy the code

This is embarrassing, because the body of the call becomes one, which is essentially equal to

A.click(view1)

A.click(view1)Copy the code

Everything is the same, the only difference is the number of ms, wait, can a person’s hand speed be several ms? Obviously not, so we seem to have found a way, so to sum up, our anti-repeat click tool class might look like this:

package com.tencent.igame.common.utils; import android.text.TextUtils; import android.util.Log; import android.view.View; Public class XClickUtil {/** * private static String mLastTargetName; /** * private static long mLastClickTime; /** * private static int mLastClickViewId; /** * is it quick click ** @param v click the control * @param intervalMillis time interval (ms) * @return true: that's right.false*/ public static Boolean isFastDoubleClick(Object target, View v, long intervalMillis) {int viewId = v.getid (); long time = System.currentTimeMillis(); long timeInterval = Math.abs(time - mLastClickTime); //10, indicating that the hand speed is not possible to break the msif (timeInterval>10 && timeInterval < intervalMillis && viewId == mLastClickViewId && TextUtils.equals(getTargetHash(target), mLastTargetName)) {
            Log.e("XClickUtil", "重复点击 target = [" + getTargetHash(target) + "], v = [" + v.getId() + "], currentTimeMillis = [" + time + "]");
            return true;
        } else{// fixme here can actually add automatic buried point Log. XClickUtil" , " Single click target = [" + getTargetHash(Target) + "] , v = [" + v.getId() + "] , currentTimeMillis = [" + time + "] ") ; mLastTargetName = getTargetHash(target); mLastClickTime = time; mLastClickViewId = viewId;return false;
        }
    }

    private static String getTargetHash(Object object) {
        return object.getClass().getName() + "@" + object.hashCode();
    }
}Copy the code

And the last hole, just hit release package directly to NPE

This situation is definitely caused by confusion, generally add confusion configuration is OK

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- annotation of AOP -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ---adaptclassstrings -keepattributes InnerClasses, EnclosingMethod, Signature, *Annotation* -keepnames @org.aspectj.lang.annotation.Aspect class * { ajc* < methods>; }Copy the code