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


96

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

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

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

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

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


Вы можете отключить кнопку после открытия действия ... и когда действие закончится, снова включить его ... Вы можете определить окончание второго действия, вызвав функцию onActivityResult
— Maneesh

Отключите кнопку при первом нажатии и повторно активируйте ее позже только тогда, когда вы хотите, чтобы кнопка была нажата еще раз.
— JimmyB

отключение не работает простым способом, если следующий оператор предназначен для какого-то длительного процесса или запуска действия ... Чтобы отключить кнопку, вам необходимо создать отдельный поток ...
— Аваис Тарик

Если вы дважды используете один и тот же API, обратитесь сюда: techstricks.com/avoid-multiple-requests-when-using-volley
— Шайлендра Мадда

Ответы:


69

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

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

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

Переопределить, onResume()чтобы снова включить кнопку.

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

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

1
Это правильный подход. Он даже будет обрабатывать выбранные состояния кнопок (если вы их предоставите) и все «вкусности» материального дизайна, которые вы ожидаете от простого стандартного виджета. Не могу поверить, что люди используют для этого таймеры. Затем вы начинаете видеть странные библиотеки для работы с такими вещами…
— Мартин Маркончини

158

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

android:launchMode = "singleTop"

Например:

<activity
            android:name=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            android:launchMode = "singleTop"/>

хорошо, я думаю, после начала нового действия вы занимаетесь какой-то долгой обработкой ... Вот почему экран становится черным. Теперь, если вы хотите избежать этого черного экрана, вы должны показать диалоговое окно прогресса в начале действия и выполнить длительную обработку в отдельном потоке (например, поток пользовательского интерфейса или просто использовать класс async). Как только ваша обработка будет завершена, скройте этот диалог. Насколько мне известно, это лучшее решение, и я использовал его несколько раз ... :)
— Awais Tariq

Мне нужно показать диалог. Но да, у меня есть один метод, указывающий на веб в onCreate. Но разве это единственное решение? Потому что на этом этапе я хочу работать, не меняя поток и все такое. Так знаете ли вы другие возможные способы. И у меня есть кнопка в моем адаптере списка, и я объявил метод для нее в xml, а не программно
— tejas

2
что еще можно ??? Так или иначе, вы должны реализовать потоки, чтобы приложение выглядело гладко ... Попробуйте, чувак ..;) Просто поместите весь текущий код в метод и вызовите этот метод из отдельного потока в том же месте, где вы написали это было раньше ... Это вряд ли увеличит пять-шесть строк кода ..
— Awais Tariq

19
Это предотвращает существование двух экземпляров действия, но не предотвращает неправильный запуск кода дважды. Принятый ответ лучше, несмотря на меньшее количество голосов за.
— lilbyrdie

18
Это неправильно, из-за этого активность никогда не существует дважды, даже в разных задачах. Правильный способ - добиться android:launchMode = "singleTop"эффекта, не нарушая многозадачности Android. В документации указано, что большинство приложений не должны использовать эту singleInstanceопцию.
— Ноус

37

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

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

Это сделает открытыми только одно действие в верхней части стека истории.


4
Этот ответ в сочетании с ответом, получившим наибольшее количество голосов, кажется, работает лучше всего. Используйте этот флаг в манифесте Activity:, android:launchMode = "singleTop"таким образом он решается без добавления флага к каждому Intent.
— Ноус

1
это бесполезно, когда вам нужны вложенные действия, потому что у вас не может быть двух действий одного типа.
— Behnam Хейдари

5
Это не работает в случае startActivityForResult
— raj

27

Поскольку 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.


FLAG_ACTIVITY_CLEAR_TOP: Это единственный работающий у меня на Android 7.1.1
— Mingjiang Shi

1
Я использую «FLAG_ACTIVITY_REORDER_TO_FRONT», и он работает нормально, и кнопка «Назад» также работает нормально. Что именно вы имели в виду, говоря "ломает кнопку возврата"? Не могли бы вы прояснить это?
— Мирмухсин Содиков

Я обнаружил, что флаг "REORDER" содержит ошибку ... и он не переупорядочивается в KitKat. Однако я проверил его в Lollipop and Pie, он работает нормально.
— Мирмухсин Содиков

9

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

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

1
@raj вы пробовали добавить это android:launchMode = "singleInstance"в файл манифеста вашего тега активности?
— Шайлендра Мадда

5

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

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

4

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

Так что хорошо, если у вас есть поле 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;
}

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

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

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

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


сервопер, спасибо за ваше исследование. Этот вопрос уже решен, однако ваш ответ также выглядит многообещающим для той ситуации, которую вы рассказали. Позвольте мне попробовать и прийти с результатом :)
— tejas

1
У меня эта проблема есть в одной из моих игр. У меня есть шары "уровня выбора", у которых есть тот же слушатель, и представления просто разные по тегам. Итак, если я быстро выберу два шарика, начнутся два действия. Я знаю это, потому что новое действие запускает звук ... и в этом случае звук воспроизводится дважды ... но вы можете проверить это, щелкнув назад, что приведет вас к предыдущему действию
— Sir NIkolay Cesar The First

1
Этого недостаточно. Вам также необходимо использовать a, synchronized(mIsClicked) {...}чтобы быть на 100% безопасным.
— Monstieur

@Monstieur, вам не нужен синхронизированный блок, потому что это все основной
— поток

@MartinMarconcini Просто потому, что это безопасно в деятельности Android, не делает его хорошим кодом. Если бы это был отдельный класс, его нужно было бы задокументировать как не поточно-ориентированное.
— Monstieur

4

В этой ситуации я выберу один из двух подходов: singleTaskв manifest.xml ИЛИ флаг в методах onResume()& Activity 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);
}

3

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

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

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


3

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

 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()
}`

2

Другое очень-очень простое решение, если вы не хотите использовать, 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...
        }
    });

2

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

private long mLastClickTime = 0;

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

 @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.
         }
      }
 }

1

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

общедоступное логическое значение oneTimeLoadActivity = false;

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

1

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

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

0

Если вы используете 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;
  }
}

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


-1
myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      myButton.setOnClickListener(null);
    }
});

Это, вероятно, не сработает, так как вам придется объявить его окончательным.
— King

-1

Используйте 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}
    } 

perform click; wait; flg = false;когда мы вернемся
— Xeno Lupus

-1

Вы можете просто переопределить 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);
    }
}

-4

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

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);
            }

        });
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.