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


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.