Як запобігти двійному завантаженню активності при натисканні кнопки


96

Я намагаюся запобігти завантаженню активності двічі, якщо натиснути кнопку двічі миттєво після першого клацання.

Скажімо, у мене є діяльність, яка завантажується натисканням кнопки

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

Тепер, оскільки діяльність, яку потрібно завантажити, має мережеві дзвінки, завантаження займає трохи часу (MVC). Я показую для цього режим завантаження, але якщо перед цим двічі натиснути кнопку, я бачу, як діяльність завантажується двічі.

Хтось знає, як цього запобігти?


Ви можете вимкнути кнопку після відкриття активності ... і коли дія завершиться ,, знову ввімкніть її ... u можна виявити завершення другої діяльності, викликавши функцію
onActivityResult

Вимкніть кнопку при першому натисканні та ввімкніть її пізніше лише тоді, коли ви хочете, щоб кнопку натиснули ще раз.
JimmyB

відключення не працює просто, якщо наступне твердження стосується якогось тривалого процесу чи активності ... Щоб вимкнути кнопку, потрібно створити окремий потік ...
Awais Tariq

Якщо ви двічі натискаєте один і той же 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"/>

добре, я думаю, ви робите довгу обробку після початку нової діяльності .. Ось чому екран стає чорним. Тепер, якщо ви хочете уникнути цього чорного екрану, вам слід показати діалогове вікно прогресу на початку дії та виконати тривалу обробку в окремому потоці (тобто UI Thread або Просто використовувати асинхронний клас). Після завершення обробки сховіть це діалогове вікно. Це найкраще рішення, наскільки я знаю, і я використовував його кілька разів ... :)
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"таким чином це вирішується без необхідності додавати прапор до кожного наміру.
Нохус,

1
це не корисно, коли вам потрібні вкладені дії, оскільки ви не можете мати двох однотипних дій.
Бехнам

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 відповіддю?

Якщо ви встановите для false для false у прослуховувачі його виклику, інша кнопка, що використовує той самий прослуховувач, все одно буде ввімкнена. Отже, щоб бути впевненим, що дія слухача не викликається двічі, потрібно мати щось глобальне, що відключає всі виклики слухача (неважливо, якщо це новий екземпляр чи ні)

У чому різниця між моєю відповіддю та іншими?

Вони думають правильно, але не думають про майбутнє повернення до тієї самої інстанції дзвінка :)


сервопер, дякую за ваше дослідження. Це питання вже вирішено, однак ваша відповідь також виглядає багатообіцяючою щодо ситуації, яку ви розповіли. Дозвольте мені спробувати прийти з результатом :)
tejas

1
У мене це питання в одній із моїх ігор. У мене є аеростати "вибраного рівня", у яких однаковий слухач, і погляди просто відрізняються за тегами. Отже, якщо я швидко виберу два кульки, це почне дві дії. Я знаю, що, оскільки нова активність починає звучати .. і в цьому випадку звук відтворюється двічі ... але ви можете перевірити це, натиснувши назад, що призведе вас до попередньої активності
сер Микола Микола Сезар Перший

1
Цього недостатньо. Вам також потрібно використовувати a synchronized(mIsClicked) {...}, щоб бути в 100% безпеці.
Monstieur

@Monstieur, вам не потрібен синхронізований блок, тому що це все Основна нитка ...
Мартін Маркончіні

@MartinMarconcini Те, що це безпечно в діяльності Android, не робить його хорошим кодом. Якби це був автономний клас, його потрібно було б задокументувати як не безпечний для потоків.
Монсьє,

4

У цій ситуації я скористаюся одним із двох наближених, singleTaskу manifest.xml АБО прапором у Activity onResume()&onDestroy() методів.

Для першого рішення: я віддаю перевагу використовувати singleTaskдля активності в маніфесті, а не singleInstance, згідно з використанням, singleInstanceя з'ясував, що в деяких випадках діяльність створює новий окремий екземпляр, що призводить до того, що у запущених програмах є два окремі вікна програм у bcakground та крім додаткових виділень пам'яті, що призведе до дуже поганого користувацького досвіду, коли користувач відкриє перегляд програм, щоб вибрати якусь програму для відновлення. Отже, кращий спосіб - визначити активність у маніфесті.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 перевірте, якщо різниця поточного часу та часу останнього клацання менша за i секунду, тоді нічого не робити (повернути), інакше перейти до події кліку

 @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

Просто збережіть один прапорець у кнопці методу 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);
    }
});

Це, мабуть, не спрацювало б, оскільки вам довелося б оголосити це остаточним.
Король

-1

Використовуйте flagзмінну, встановіть її to true, перевірте, чи справді вона просто returnвиконує виклик активності.

Ви також можете використовувати setClickable (false) для виконання виклику активності

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 

perform click; wait; flg = false;бо коли ми повернемось
Ксено Лупус

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