Как предотвратить загрузку активности дважды при нажатии на кнопку

Я пытаюсь предотвратить загрузку активности дважды, если дважды нажать кнопку сразу после первого щелчка.

У меня есть активность, которая загружается по нажатию кнопки, скажем

 myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
       //Load another activity
    }
});

Теперь, поскольку загружаемое действие имеет сетевые вызовы, загрузка занимает немного времени (MVC). Я показываю вид загрузки для этого, но если я дважды нажму кнопку до этого, я вижу, что действие загружается дважды.

Кто-нибудь знает, как это предотвратить?

23 ответа

Решение

В прослушивателе событий кнопки отключите кнопку и покажите другое действие.

    Button b = (Button) view;
    b.setEnabled(false);

    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);

Override onResume() чтобы снова включить кнопку.

@Override
    protected void onResume() {
        super.onResume();

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setEnabled(true);
    }

Добавьте это к вашему Activity определение в Androidmanifest.xml...

android:launchMode = "singleInstance"

Вы можете использовать флаги намерений, как это.

Intent intent = new Intent(Class.class);    
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivity(intent);

В верхней части стека истории будет открыто только одно действие.

Так как SO не позволяет мне комментировать другие ответы, я должен загрязнить эту ветку новым ответом.

Общие ответы на проблему "активность открывается дважды" и мой опыт использования этих решений (Android 7.1.1):

  1. Кнопка "Отключить", которая запускает действие: работает, но чувствует себя немного неуклюже. Если у вас есть несколько способов запустить действие в вашем приложении (например, кнопка на панели действий И, нажав на элемент в виде списка), вы должны отслеживать состояние включения / выключения нескольких элементов графического интерфейса. Плюс, например, не очень удобно отключать нажатые элементы в виде списка. Итак, не очень универсальный подход.
  2. launchMode = "singleInstance": не работает с startActivityForResult(), прерывает навигацию с помощью startActivity(), не рекомендуется для обычных приложений в документации манифеста Android.
  3. launchMode = "singleTask": не работает с startActivityForResult(), не рекомендуется для обычных приложений в документации манифеста Android.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: кнопка возврата назад.
  5. FLAG_ACTIVITY_SINGLE_TOP: не работает, действие все еще открывается дважды.
  6. FLAG_ACTIVITY_CLEAR_TOP: это единственный, который работает на меня.

РЕДАКТИРОВАТЬ: Это было для начала деятельности с startActivity(). При использовании startActivityForResult () мне нужно установить FLAG_ACTIVITY_SINGLE_TOP и FLAG_ACTIVITY_CLEAR_TOP.

Это работало только для меня, когда startActivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

Используйте singleInstance, чтобы избежать активности, чтобы вызвать дважды.

<activity
            android:name=".MainActivity"
            android:label="@string/activity"
            android:launchMode = "singleInstance" />

Допустим, @wannik прав, но если у нас более одной кнопки, вызывающей один и тот же слушатель действия, и я нажимаю две кнопки один раз почти одновременно, прежде чем приступить к следующему действию...

Так что хорошо, если у вас есть поле private boolean mIsClicked = false; и в слушателе:

if(!mIsClicked)
{
    mIsClicked = true;
    Intent i = new Intent(this, AnotherActitivty.class);
    startActivity(i);
}

А также onResume() нам нужно вернуть состояние:

@Override
protected void onResume() {
    super.onResume();

    mIsClicked = false;
}

В чем разница между ответом my и @ wannik?

Если вы установили значение "false" в прослушивателе этого вызова, другая кнопка, использующая того же прослушивателя, все равно будет включена. Таким образом, чтобы быть уверенным, что действие слушателя не вызывается дважды, вам нужно иметь что-то глобальное, что отключает все вызовы слушателя (не важно, новый это экземпляр или нет)

В чем разница между моим ответом и другими?

Они думают правильно, но они не думают о будущем возвращении к тому же случаю вызывающей деятельности:)

Для этой ситуации я пойду на один из двух подходов, singleTask в manifest.xml ИЛИ флаг в деятельности onResume() & onDestroy() методы соответственно.

Для первого решения: я предпочитаю использовать singleTask за активность в манифесте, а не singleInstanceсогласно использованию singleInstance Я выяснил, что в некоторых случаях действие создает новый отдельный экземпляр для себя, в результате чего в запущенных приложениях в bcakground появляется окно с двумя отдельными приложениями, и помимо дополнительного выделения памяти, что может привести к очень плохому пользовательскому интерфейсу, когда пользователь открывает приложения посмотреть, чтобы выбрать приложение, чтобы возобновить. Таким образом, лучший способ - определить действие в файле manifest.xml следующим образом:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"</activity>

Вы можете проверить режимы запуска активности здесь.


Для второго решения вам нужно просто определить статическую переменную или переменную предпочтения, например:

public class MainActivity extends Activity{
    public static boolean isRunning = false;

    @Override
    public void onResume() {
        super.onResume();
        // now the activity is running
        isRunning = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // now the activity will be available again
        isRunning = false;
    }

}

а с другой стороны, когда вы хотите запустить это действие, просто проверьте:

private void launchMainActivity(){
    if(MainActivity.isRunning)
        return;
    Intent intent = new Intent(ThisActivity.this, MainActivity.class);
    startActivity(intent);
}

// переменная для отслеживания времени события

private long mLastClickTime = 0;

2. В onClick проверьте, что если текущее время и разница во времени последнего клика меньше, чем я секунда, то ничего не предпринимайте (возврат), иначе перейдите к событию клика

 @Override
public void onClick(View v) {
    // Preventing multiple clicks, using threshold of 1 second
    if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
        return;
          }
    mLastClickTime = SystemClock.elapsedRealtime();
            // Handle button clicks
            if (v == R.id.imageView2) {
        // Do ur stuff.
         }
            else if (v == R.id.imageView2) {
        // Do ur stuff.
         }
      }
 }

Я думаю, что вы собираетесь решить проблему неправильно. Как правило, плохой идеей является выполнение длительных веб-запросов в любом из методов запуска жизненного цикла (onCreate(), onResume(), так далее). Действительно, эти методы должны просто использоваться для создания и инициализации объектов, которые будет использовать ваша деятельность, и поэтому должны быть относительно быстрыми.

Если вам нужно выполнить веб-запрос, сделайте это в фоновом потоке из недавно запущенного действия (и покажите диалоговое окно загрузки в новом действии). После завершения потока фонового запроса он может обновить действие и скрыть диалоговое окно.

Это означает, что ваша новая активность должна быть запущена немедленно и предотвратить двойной щелчок.

Надеюсь это поможет:

 protected static final int DELAY_TIME = 100;

// to prevent double click issue, disable button after click and enable it after 100ms
protected Handler mClickHandler = new Handler() {

    public void handleMessage(Message msg) {

        findViewById(msg.what).setClickable(true);
        super.handleMessage(msg);
    }
};

@Override
public void onClick(View v) {
    int id = v.getId();
    v.setClickable(false);
    mClickHandler.sendEmptyMessageDelayed(id, DELAY_TIME);
    // startActivity()
}`

Добавить режим запуска как отдельную задачу в манифест, чтобы активность не открывалась дважды при нажатии

<activity
        android:name=".MainActivity"
        android:label="@string/activity"
        android:launchMode = "singleTask" />

Другое очень очень простое решение, если вы не хотите использовать onActivityResult() Это отключить кнопку на 2 секунды (или нужное время), не идеально, но может частично решить проблему в некоторых случаях, и код прост:

   final Button btn = ...
   btn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            //start activity here...
            btn.setEnabled(false);   //disable button

            //post a message to run in UI Thread after a delay in milliseconds
            btn.postDelayed(new Runnable() {
                public void run() {
                    btn.setEnabled(true);    //enable button again
                }
            },1000);    //1 second in this case...
        }
    });

Просто сохраните один флаг в методе onClick кнопки как:

public boolean oneTimeLoadActivity = false;

    myButton.setOnClickListener(new View.OnClickListener() {
          public void onClick(View view) {
               if(!oneTimeLoadActivity){
                    //start your new activity.
                   oneTimeLoadActivity = true;
                    }
        }
    });

Если вы используете onActivityResult, вы можете использовать переменную для сохранения состояния.

private Boolean activityOpenInProgress = false;

myButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View view) {
    if( activityOpenInProgress )
      return;

    activityOpenInProgress = true;
   //Load another activity with startActivityForResult with required request code
  }
});

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if( requestCode == thatYouSentToOpenActivity ){
    activityOpenInProgress = false;
  }
}

Работает и при нажатой кнопке назад, потому что код запроса возвращается по событию.

Добавление:

android:launchMode="singleTop"

Внутри тега активности в AndroidManifest.xml проблема решена.

В kotlin вы можете использовать эту функцию расширения, чтобы отключить щелчок представления, это включит представление по истечении времени

      fun View.disableClick(time: Long = 1000) {
    isEnabled = false
    postDelayed({
        isEnabled = true
    }, time)
}   

вы можете вызвать функцию, как показано ниже

        binding.btGetStarted.setOnClickListener {
     //disabling button click
      binding.btGetStarted.disableClick()
     //activity redirection
     ActivityUtils.startActivity(this,RegisterActivity::class.java, isFinish = false)
        }

Мы также можем использовать функцию устранения дребезга кнопок.

      public abstract class DebounceOnClickListener implements View.OnClickListener {

    private final long minimumInterval;
    private final Map<View, Long> lastClickMap;

   
    public abstract void debounceOnClick(View view);

  
    public DebounceOnClickListener(long minIntervalMilliSec) {
        this.minimumInterval = minIntervalMilliSec;
        this.lastClickMap = new WeakHashMap<View, Long>();
    }

    @Override
    public void onClick(View clickedView) {
        Long previousClickTimestamp = lastClickMap.get(clickedView);
        long currentTimestamp = SystemClock.uptimeMillis();

        lastClickMap.put(clickedView, currentTimestamp);
        if (previousClickTimestamp == null ||
                (currentTimestamp - previousClickTimestamp.longValue() > minimumInterval)) {
            debounceOnClick(clickedView);
        }
    }
}

Более общее решение, которое будет применено только один раз для всех приложений, состоит в добавлении следующих двух классов, как показано ниже

public class SingleClickButton extends AppCompatButton {
            public SingleClickButton(Context context) {
                super(context);
            }
            public SingleClickButton(Context context, AttributeSet attrs) {
                super(context, attrs);
            }
            public SingleClickButton(Context context, AttributeSet attrs, int defStyleAttr) {
                super(context, attrs, defStyleAttr);
            }
            @Override
            public void setOnClickListener(@Nullable OnClickListener l) {
                if(l!=null){
                    super.setOnClickListener(new DebouncedClickListener() {
                        @Override
                        public void onClicked(View v) {
                            l.onClick(v);
                        }
                    });
                }else{
                    super.setOnClickListener(l);
                }
            }
        }


public abstract class DebouncedClickListener  implements View.OnClickListener {
    private static final long MAX_TIME_INTERVAL = 750L;
    private long lastClickedTime;

    public abstract void onClicked(View v);

    @Override public void onClick(View v) {
        Logger.d("Click Test trying to click");
        long now = SystemClock.elapsedRealtime();
        long diff = now - lastClickedTime;
        if (diff > MAX_TIME_INTERVAL) {
            Logger.d("Click Test allowed to click");
            onClicked(v);
        }else{
            Logger.d("Click Test not allowed to click time ");
        }
        lastClickedTime = now;
    }

}

поэтому всякий раз, когда вы хотите кнопку одним щелчком мыши, просто добавьте пользовательский вид в XML, как показано ниже

<yourpackagge.SingleClickButton .... />

Вы можете попробовать это также

Button game = (Button) findViewById(R.id.games);
        game.setOnClickListener(new View.OnClickListener() 
        {
            public void onClick(View view) 
            {
                Intent myIntent = new Intent(view.getContext(), Games.class);
                startActivityForResult(myIntent, 0);
            }

        });

Вы можете просто переопределить startActivityForResult и использовать переменную экземпляра:

boolean couldStartActivity = false;

@Override
protected void onResume() {
    super.onResume();

    couldStartActivity = true;
}

@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if (couldStartActivity) {
        couldStartActivity = false;
        intent.putExtra(RequestCodeKey, requestCode);
        super.startActivityForResult(intent, requestCode, options);
    }
}

Использовать flag переменная установить его to trueПроверьте, правда ли это просто return еще выполнить Activity Call.

Вы также можете использовать setClickable(false) для выполнения Activity Call

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 
myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      myButton.setOnClickListener(null);
    }
});
Другие вопросы по тегам