Jak zapobiec dwukrotnemu załadowaniu aktywności po naciśnięciu przycisku


96

Próbuję zapobiec dwukrotnemu załadowaniu działania, naciskając przycisk dwa razy natychmiast po pierwszym kliknięciu.

Mam działanie, które ładuje się na przykład po kliknięciu przycisku

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

Ponieważ ładowana czynność ma wywołania sieciowe, jej załadowanie zajmuje trochę czasu (MVC). W tym celu pokazuję widok ładowania, ale jeśli wcześniej dwukrotnie naciśnę przycisk, mogę zobaczyć, że czynność jest ładowana dwukrotnie.

Czy ktoś wie, jak temu zapobiec?


Możesz wyłączyć przycisk po otwarciu czynności ... a po zakończeniu czynności, włącz ją ponownie ... Możesz wykryć zakończenie drugiej czynności, wywołując funkcję onActivityResult
Maneesh.

Wyłącz przycisk po pierwszym kliknięciu i włącz go ponownie później tylko wtedy, gdy chcesz, aby przycisk został ponownie kliknięty.
JimmyB

Wyłączenie nie działa w prosty sposób, jeśli stwierdzenie jest bardzo obok jakiegoś długiego procesu lub rozpocząć działalność ... Do przycisk Wyłącz należy utworzyć oddzielny wątek ...
Awais Tariq

Jeśli dwukrotnie uderzysz w
Shailendra Madda

Odpowiedzi:


69

W detektorze zdarzeń przycisku wyłącz przycisk i pokaż inną czynność.

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

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

Zastąp, onResume()aby ponownie włączyć przycisk.

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

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

1
To jest właściwe podejście. Obsłuży nawet za Ciebie wybrane stany przycisku (jeśli je dostarczysz) i wszystkie „gadżety” Material Design, których można oczekiwać od prostego standardowego widżetu. Nie mogę uwierzyć, że ludzie używają do tego timerów. Potem zaczynasz widzieć dziwne biblioteki, które zajmują się takimi rzeczami…
Martin Marconcini

158

Dodaj to do swojej Activitydefinicji w AndroidManifest.xml...

android:launchMode = "singleTop"

Na przykład:

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

ok, myślę, że po rozpoczęciu nowej czynności trwa trochę dłużej. Dlatego ekran robi się czarny. Teraz, jeśli chcesz uniknąć tego czarnego ekranu, powinieneś pokazać okno dialogowe postępu na początku działania i przeprowadzić długie przetwarzanie w osobnym wątku (np. Wątek interfejsu użytkownika lub Po prostu użyj klasy asynchronicznej). Po zakończeniu przetwarzania ukryj to okno dialogowe. Jest to najlepsze rozwiązanie w mojej wiedzy i korzystałem z niego kilkakrotnie ... :)
Awais Tariq

Mam do pokazania okno dialogowe. Ale tak, mam jedną metodę wskazywania sieci w onCreate. Ale czy to jedyne rozwiązanie? Ponieważ w tym momencie chcę sobie poradzić bez zmiany wątku i wszystkiego. Więc znasz inne możliwe sposoby. A ja mam przycisk w mojej liście adaptera i zadeklarowałem na to sposób w xml, nie programowo
taas

2
co jeszcze jest możliwe ??? Tak czy inaczej, musisz zaimplementować wątkowanie, aby uzyskać gładko wyglądającą aplikację ... Spróbuj stary ..;) Po prostu umieść cały bieżący kod w metodzie i wywołaj tę metodę z osobnego wątku w tym samym miejscu, w którym napisałeś to wcześniej ... Prawie nie zwiększy pięciu do sześciu linii kodu ..
Awais Tariq

19
Zapobiega to istnieniu dwóch wystąpień działania, ale nie zapobiega dwukrotnemu, nieprawidłowemu uruchomieniu kodu. Przyjęta odpowiedź jest lepsza, pomimo mniejszej liczby głosów pozytywnych.
lilbyrdie

18
To jest złe, sprawia, że ​​czynność nigdy nie istnieje dwukrotnie, nawet w innych zadaniach. Poprawny byłby sposób android:launchMode = "singleTop", który osiąga efekt bez przerywania wielozadaniowości Androida. Dokumentacja stwierdza, że ​​większość aplikacji nie powinna używać tej singleInstanceopcji.
Nohus

37

Możesz użyć flag intencji w ten sposób.

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

Spowoduje to, że tylko jedno działanie będzie otwarte na górze stosu historii.


4
Wydaje się, że ta odpowiedź w połączeniu z najbardziej pozytywną odpowiedzią działa najlepiej. Użyj tej flagi w manifeście działania:, w android:launchMode = "singleTop"ten sposób zostanie rozwiązany bez konieczności dodawania flagi do każdej intencji.
Nohus

1
nie jest to przydatne, gdy potrzebne są działania zagnieżdżone, ponieważ nie można mieć dwóch działań tego samego typu.
Behnam Heydari

5
To nie działa w przypadku startActivityForResult
raj

27

Ponieważ SO nie pozwala mi komentować innych odpowiedzi, muszę zaśmiecić ten wątek nową odpowiedzią.

Typowe odpowiedzi na problem „aktywność otwiera się dwukrotnie” i moje doświadczenia z tymi rozwiązaniami (Android 7.1.1):

  1. Wyłącz przycisk uruchamiający czynność: działa, ale sprawia wrażenie trochę niezdarnego. Jeśli masz wiele sposobów rozpoczęcia działania w swojej aplikacji (np. Przycisk na pasku akcji ORAZ przez kliknięcie elementu w widoku listy), musisz śledzić stan włączenia / wyłączenia wielu elementów GUI. Poza tym na przykład wyłączenie klikniętych elementów w widoku listy nie jest zbyt wygodne. Nie jest to więc bardzo uniwersalne podejście.
  2. launchMode = "singleInstance": Nie działa z startActivityForResult (), przerywa nawigację wstecz z startActivity (), niezalecane dla zwykłych aplikacji przez dokumentację manifestu Androida.
  3. launchMode = "singleTask": Nie działa z startActivityForResult (), niezalecane dla zwykłych aplikacji według dokumentacji manifestu Androida.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: Przycisk cofania.
  5. FLAG_ACTIVITY_SINGLE_TOP: Nie działa, aktywność jest nadal otwierana dwukrotnie.
  6. FLAG_ACTIVITY_CLEAR_TOP: Tylko to dla mnie pracuje.

EDYCJA: służyło do rozpoczynania działań za pomocą startActivity (). Używając startActivityForResult (), muszę ustawić zarówno FLAG_ACTIVITY_SINGLE_TOP, jak i FLAG_ACTIVITY_CLEAR_TOP.


FLAG_ACTIVITY_CLEAR_TOP: To jest jedyny, który działa dla mnie na Androidzie 7.1.1
Mingjiang Shi

1
Używam „FLAG_ACTIVITY_REORDER_TO_FRONT” i działa dobrze, a przycisk Wstecz również działa normalnie. Co dokładnie miałeś na myśli, mówiąc „przycisk cofania”? Czy mógłbyś to wyjaśnić?
Mirmukhsin Sodikov

Okazało się, że flaga „REORDER” zawiera błąd ... i nie zmieniała kolejności w KitKat. Jednak sprawdziłem to w Lollipop and Pie, działa dobrze.
Mirmukhsin Sodikov

9

To działało dla mnie tylko wtedy startActivity(intent)

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

1
@raj Czy próbowałeś dodać to android:launchMode = "singleInstance"w pliku manifestu swojego tagu aktywności?
Shailendra Madda,

5

Użyj singleInstance, aby uniknąć dwukrotnego wywołania.

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

4

Powiedzmy, że @wannik ma rację, ale jeśli mamy więcej niż 1 przycisk wywołujący tę samą akcję nasłuchiwania i klikam dwa przyciski raz prawie w tym samym czasie przed rozpoczęciem następnej czynności ...

Więc dobrze, jeśli masz pole private boolean mIsClicked = false;i słuchacz:

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

I onResume()musimy zwrócić stan:

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

    mIsClicked = false;
}

Jaka jest różnica między odpowiedzią my i @ wannik?

Jeśli ustawisz Enabled na false w odbiorniku jego wywołania, inny przycisk używający tego samego odbiornika będzie nadal włączony. Aby mieć pewność, że akcja słuchacza nie jest wywoływana dwukrotnie, musisz mieć coś globalnego, które wyłącza wszystkie wywołania słuchacza (nieważne, czy jest to nowa instancja, czy nie)

Jaka jest różnica między moją odpowiedzią a innymi?

Myślą we właściwy sposób, ale nie myślą o przyszłym powrocie do tej samej instancji czynności wzywającej :)


serwo, dziękuję za twoje badania. To pytanie zostało już rozwiązane, ale twoja odpowiedź również wygląda obiecująco w opisanej sytuacji. Spróbuję i dojdę z wynikiem :)
tejas

1
Mam ten problem w jednej z moich gier. Mam balony „Wybierz poziom”, które mają ten sam odbiornik, a widoki różnią się tylko tagami. Jeśli więc szybko wybiorę dwa balony, zaczną się dwie czynności. Wiem to, ponieważ nowa czynność zaczyna dźwięk ... iw tym przypadku dźwięk jest odtwarzany dwa razy ... ale możesz to sprawdzić klikając wstecz, co przeniesie Cię do poprzedniej czynności
Sir NIkolay Cesar The First

1
To nie wystarczy. Aby synchronized(mIsClicked) {...}być w 100% bezpiecznym, musisz również użyć .
Monstieur

@Monstieur, nie potrzebujesz zsynchronizowanego bloku, ponieważ to wszystko jest głównym wątkiem…
Martin Marconcini

@MartinMarconcini Tylko dlatego, że jest to bezpieczne w działaniu na Androida, nie czyni go dobrym kodem. Gdyby była to klasa samodzielna, musiałaby zostać udokumentowana jako niebezpieczna dla wątków.
Monstieur

4

W tej sytuacji skorzystam z jednego z dwóch podejść singleTaskw pliku manifest.xml LUB flagi w onResume()&onDestroy() metod odpowiednio.

W przypadku pierwszego rozwiązania: wolę używać singleTaskdo działania w manifeście niż singleInstance, zgodnie z użyciem, singleInstancedoszedłem do wniosku, że w niektórych przypadkach aktywność tworzy dla siebie nową oddzielną instancję, co skutkuje posiadaniem dwóch oddzielnych okien aplikacji w uruchomionych aplikacjach w bcakground i poza dodatkowymi alokacjami pamięci, które spowodowałyby bardzo złe wrażenia użytkownika, gdy użytkownik otwiera widok aplikacji, aby wybrać aplikację do wznowienia. Dlatego lepszym sposobem jest zdefiniowanie aktywności w pliku manifest.xml w następujący sposób:

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

można sprawdzić tryby uruchamiania działalność tutaj .


W przypadku drugiego rozwiązania wystarczy zdefiniować zmienną statyczną lub zmienną preferencji, na przykład:

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

}

a z drugiej strony, jeśli chcesz uruchomić tę aktywność, po prostu sprawdź:

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

3

Myślę, że zamierzasz rozwiązać problem w niewłaściwy sposób. Na ogół jest to zły pomysł na działalność do podejmowania długotrwałych żądań internetowych w żadnej ze swoich metod cyklu życia (startup onCreate(), onResume()itp). Naprawdę, te metody powinny być po prostu używane do tworzenia instancji i inicjalizacji obiektów, których będzie używać twoja aktywność, a zatem powinny być stosunkowo szybkie.

Jeśli musisz wykonać żądanie sieciowe, zrób to w wątku w tle z nowo uruchomionej aktywności (i pokaż okno dialogowe ładowania w nowym działaniu). Po zakończeniu wątku żądania w tle może zaktualizować działanie i ukryć okno dialogowe.

Oznacza to, że Twoja nowa aktywność powinna zostać natychmiast uruchomiona i uniemożliwić dwukrotne kliknięcie.


3

Mam nadzieję że to pomoże:

 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

Innym bardzo prostym rozwiązaniem, jeśli nie chcesz używać, onActivityResult()jest wyłączenie przycisku na 2 sekundy (lub na czas, który chcesz), nie jest idealne, ale może częściowo rozwiązać problem w niektórych przypadkach, a kod jest prosty:

   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

// zmienna do śledzenia czasu zdarzenia

private long mLastClickTime = 0;

2. w onClick sprawdź, czy jeśli aktualny czas i różnica czasu ostatniego kliknięcia są mniejsze niż jedna sekunda, nie rób nic (zwróć), w przeciwnym razie przejdź do zdarzenia kliknięcia

 @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

Po prostu zachowaj jedną flagę w metodzie onClick jako:

public boolean oneTimeLoadActivity = false;

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

1

dodaj Tryb uruchamiania jako pojedyncze zadanie w manifeście, aby uniknąć dwukrotnego otwarcia działania po kliknięciu

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

0

Jeśli używasz onActivityResult, możesz użyć zmiennej, aby zapisać stan.

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

Działa również po naciśnięciu przycisku Wstecz, ponieważ kod żądania jest zwracany przy zdarzeniu.


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

To prawdopodobnie by nie zadziałało, ponieważ musiałbyś zadeklarować to jako ostateczne.
King

-1

Użyj flagzmiennej ustaw ją to true, sprawdź, czy jest prawdziwa, po prostu returnwykonaj wywołanie aktywności.

Możesz również użyć setClickable (false) one wykonując wywołanie działania

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

perform click; wait; flg = false;kiedy wrócimy
Xeno Lupus

-1

Możesz po prostu nadpisać startActivityForResult i użyć zmiennej instancji:

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

Możesz też spróbować tego

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

        });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.