Cómo evitar que la actividad se cargue dos veces al presionar el botón

93

Estoy tratando de evitar que la actividad se cargue dos veces si presiono el botón dos veces instantáneamente después del primer clic.

Tengo una actividad que se carga al hacer clic en un botón, digamos

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

Ahora que la actividad que se va a cargar tiene llamadas de red, se tarda un poco en cargar (MVC). Muestro una vista de carga para esto, pero si presiono el botón dos veces antes de eso, puedo ver que la actividad se carga dos veces.

¿Alguien sabe cómo prevenir esto?

tejas
fuente
Puede deshabilitar el botón después de abrir la actividad ... y cuando la actividad finalice, vuelva a habilitarlo ... puede detectar el final de la segunda actividad llamando a la función onActivityResult
Maneesh
Desactive el botón cuando se haga clic en él por primera vez y vuelva a activarlo más tarde solo cuando desee que se vuelva a hacer clic en el botón.
JimmyB
deshabilitar no funciona de una manera simple si la siguiente declaración es para un proceso largo o inicio de actividad ... Para deshabilitar el botón, debe crear un hilo separado ...
Awais Tariq
Si golpea la misma API dos veces, consulte aquí: techstricks.com/avoid-multiple-requests-when-using-volley
Shylendra Madda
Posible duplicado del botón Evitar múltiples clics rápidos
Arnab Kar

Respuestas:

69

En el detector de eventos del botón, desactive el botón y muestre otra actividad.

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

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

Anular onResume()para volver a habilitar el botón.

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

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setEnabled(true);
    }
Wannik
fuente
1
Este es el enfoque correcto. Incluso manejará los estados seleccionados de botón por usted (si los proporciona) y todos los "beneficios" de diseño de materiales que esperaría de un widget estándar simple. No puedo creer que la gente use temporizadores para esto. Entonces empiezas a ver bibliotecas extrañas para manejar cosas como estas…
Martin Marconcini
157

Agregue esto a su Activitydefinición en AndroidManifest.xml...

android:launchMode = "singleTop"

Por ejemplo:

<activity
            android:name=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            android:launchMode = "singleTop"/>
Awais Tariq
fuente
ok, supongo que estás haciendo un largo procesamiento después de comenzar una nueva actividad. Por eso la pantalla se vuelve negra. Ahora, si desea evitar esa pantalla negra, debe mostrar un cuadro de diálogo de progreso al comienzo de la actividad y realizar el procesamiento largo en un hilo separado (es decir, UI Thread o simplemente use la clase async). Una vez que haya terminado su procesamiento, oculte ese diálogo. Es la mejor solución que yo sepa y la he usado varias veces ... :)
Awais Tariq
Tengo el diálogo para mostrar. Pero sí, tengo un método que apunta a la web en onCreate. Pero, ¿es esta la única solución? Porque en este punto, quiero manejar sin cambiar el hilo y todo. Entonces, ¿conoces otras formas posibles? Y tengo el botón en mi adaptador de lista y he declarado el método en el xml, no programáticamente
tejas
2
que mas es posible ??? De una forma u otra, debe tener que implementar el subproceso para obtener una aplicación de apariencia suave ... Pruébelo amigo ...;) Simplemente coloque todo el código actual en un método y llame a ese método desde un hilo separado en el mismo lugar donde ha escrito antes ... Difícilmente aumentará de cinco a seis líneas de código ..
Awais Tariq
19
Esto evita que existan dos instancias de la actividad, pero no evita que el código se ejecute dos veces de forma incorrecta. La respuesta aceptada es mejor, a pesar de tener menos votos a favor.
lilbyrdie
18
Esto está mal, hace que la actividad nunca exista dos veces, incluso en diferentes tareas. La forma correcta sería android:launchMode = "singleTop", que logra el efecto sin romper la multitarea de Android. La documentación indica que la mayoría de las aplicaciones no deberían usar la singleInstanceopción.
Nohus
37

Puede usar los indicadores de intención de esta manera.

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

Hará que solo una actividad esté abierta en la parte superior de la pila del historial.

Carlos EduardoL
fuente
4
Esta respuesta, combinada con la respuesta más votada, parece funcionar mejor. Use esta bandera en el manifiesto de la Actividad:, de android:launchMode = "singleTop"esta manera se resuelve sin tener que agregar la bandera a cada Intent.
Nohus
1
esto no es útil cuando necesita actividades anidadas, porque no puede tener dos actividades del mismo tipo.
Behnam Heydari
4
Esto no funciona en el caso de startActivityForResult
raj
27

Como SO no me permite comentar sobre otras respuestas, tengo que contaminar este hilo con una nueva respuesta.

Respuestas comunes para el problema "la actividad se abre dos veces" y mis experiencias con estas soluciones (Android 7.1.1):

  1. Desactivar el botón que inicia la actividad: funciona pero se siente un poco torpe. Si tiene varias formas de iniciar la actividad en su aplicación (por ejemplo, un botón en la barra de acción Y haciendo clic en un elemento en una vista de lista), debe realizar un seguimiento del estado habilitado / deshabilitado de múltiples elementos GUI. Además, no es muy conveniente desactivar los elementos en los que se hizo clic en una vista de lista, por ejemplo. Entonces, no es un enfoque muy universal.
  2. launchMode = "singleInstance": no funciona con startActivityForResult (), interrumpe la navegación con startActivity (), no recomendado para aplicaciones regulares por la documentación del manifiesto de Android.
  3. launchMode = "singleTask": no funciona con startActivityForResult (), no se recomienda para aplicaciones normales en la documentación del manifiesto de Android.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: Botón para retroceder.
  5. FLAG_ACTIVITY_SINGLE_TOP: No funciona, la actividad todavía se abre dos veces.
  6. FLAG_ACTIVITY_CLEAR_TOP: Este es el único que funciona para mí.

EDITAR: Esto fue para iniciar actividades con startActivity (). Cuando uso startActivityForResult (), necesito configurar FLAG_ACTIVITY_SINGLE_TOP y FLAG_ACTIVITY_CLEAR_TOP.

Andy Roid
fuente
FLAG_ACTIVITY_CLEAR_TOP: Este es el único que funciona para mí en Android 7.1.1
Mingjiang Shi
1
Estoy usando "FLAG_ACTIVITY_REORDER_TO_FRONT" y está funcionando bien y el botón Atrás también actúa normalmente. ¿A qué te refieres exactamente con "Botón de retroceso"? ¿Podrías aclarar eso?
Mirmuhsin Sodiqov
Encontré que la bandera "REORDER" tenía un error ... y no se estaba reordenando en KitKat. Sin embargo, lo verifiqué en Lollipop y Pie, está funcionando bien.
Mirmuhsin Sodiqov
7

Solo funcionaba para mí cuando startActivity(intent)

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Shylendra Madda
fuente
1
@raj, ¿ha intentado agregar esto android:launchMode = "singleInstance"en el archivo de manifiesto de su etiqueta de actividad?
Shylendra Madda
5

Use singleInstance para evitar que la actividad se invoque dos veces.

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

Digamos que @wannik tiene razón, pero si tenemos más de 1 botón llamando al mismo oyente de acción y hago clic en dos botones una vez casi al mismo tiempo antes de comenzar la siguiente actividad ...

Entonces es bueno si tiene un campo private boolean mIsClicked = false;y en el oyente:

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

Y onResume()necesitamos devolver el estado:

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

    mIsClicked = false;
}

¿Cuál es la diferencia entre mi respuesta y la de @ wannik?

Si establece habilitado en falso en el oyente de su llamada, ver otro botón que usa el mismo oyente aún estará habilitado. Entonces, para asegurarse de que la acción del oyente no se llame dos veces, debe tener algo global que deshabilite todas las llamadas del oyente (no importa si es una nueva instancia o no)

¿Cuál es la diferencia entre mi respuesta y otras?

Están pensando de la manera correcta, pero no están pensando en regresar en el futuro a la misma instancia de la actividad de llamada :)

Sir NIkolay Cesar El Primero
fuente
servoper, gracias por su investigación. Esta pregunta ya ha sido resuelta, sin embargo, su respuesta también parece prometedora para la situación que contó. Permítanme tratar de llegar con el resultado :)
Tejas
1
Tengo este problema en uno de mis juegos. Tengo globos de "nivel de selección" que tienen el mismo oyente y las vistas son diferentes por etiquetas. Entonces, si elijo rápidamente dos globos, comienzan dos actividades. Lo sé porque la nueva actividad comienza con el sonido ... y en este caso el sonido se reproduce dos veces ... pero puede verificarlo haciendo clic en Atrás, lo que lo llevará a la actividad anterior
Sir NIkolay Cesar The First
1
Esto no es suficiente. También debe usar un synchronized(mIsClicked) {...}para estar 100% seguro.
Monstieur
@Monstieur, no necesitas un bloque sincronizado porque todo esto es hilo principal…
Martin Marconcini
@MartinMarconcini El hecho de que sea seguro en una actividad de Android no lo convierte en un buen código. Si fuera una clase independiente, tendría que estar documentada como no segura para subprocesos.
Monstieur
4

Para esta situación, elegiré uno de los dos abordados, singleTasken manifest.xml O una bandera en los métodos onResume()& de la Actividad onDestroy()respectivamente.

Para la primera solución: prefiero usar singleTaskpara la actividad en el manifiesto en lugar de singleInstance, según el uso singleInstance, descubrí que en algunas ocasiones la actividad crea una nueva instancia separada para sí misma, lo que da como resultado una ventana de dos aplicaciones separadas en las aplicaciones en ejecución en bcakground y además de asignaciones de memoria extra que resultarían en una experiencia de usuario muy mala cuando el usuario abre la vista de aplicaciones para elegir alguna aplicación para reanudar. Entonces, la mejor manera es tener la actividad definida en el manifest.xml como se muestra a continuación:

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

puedes consultar los modos de inicio de actividad aquí .


Para la segunda solución, solo tiene que definir una variable estática o una variable de preferencia, por ejemplo:

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

}

y desde el otro lado, cuando desee iniciar esta actividad, simplemente marque:

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

Creo que estás resolviendo el problema de manera incorrecta. En general es una mala idea para una actividad que se va haciendo larga ejecución peticiones web en cualquiera de sus métodos de puesta en marcha del ciclo de vida ( onCreate(), onResume(), etc.). Realmente, estos métodos deberían usarse simplemente para crear instancias e inicializar objetos que su actividad usará y, por lo tanto, deberían ser relativamente rápidos.

Si necesita realizar una solicitud web, hágalo en un hilo de fondo de su actividad recién iniciada (y muestre el diálogo de carga en la nueva actividad). Una vez que se completa el hilo de solicitud en segundo plano, puede actualizar la actividad y ocultar el diálogo.

Esto significa que su nueva actividad debe iniciarse de inmediato y evitar que el doble clic sea posible.

tomtheguvnor
fuente
3

Espero que esto ayude:

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

Otra solución muy muy simple si no quieres usar onActivityResult()es deshabilitar el botón por 2 segundos (o el tiempo que quieras), no es lo ideal, pero puede solucionar en parte el problema en algunos casos y el código es simple:

   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...
        }
    });
Gilian
fuente
2

// variable para rastrear el tiempo del evento

private long mLastClickTime = 0;

2.En onClick, compruebe que si la hora actual y la diferencia de tiempo del último clic son menos de un segundo, no haga nada (regresar), de lo contrario, vaya al evento de clic

 @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.
         }
      }
 }
44kksharma
fuente
1

Simplemente mantenga una bandera en el botón onClick método como:

public boolean oneTimeLoadActivity = false;

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

Si está usando onActivityResult, podría usar una variable para guardar el estado.

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

También funciona con el botón Atrás presionado porque el código de solicitud se devuelve en el evento.

Umang
fuente
0

agregue el modo de inicio como una sola tarea en el manifiesto para evitar que la actividad se abra dos veces al hacer clic

<activity
        android:name=".MainActivity"
        android:label="@string/activity"
        android:launchMode = "singleTask" />
Desarrollador de Android JIL
fuente
-1
myButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      myButton.setOnClickListener(null);
    }
});
Conejo Trueno
fuente
Eso probablemente no funcionaría ya que tendría que declararlo como final.
Rey
-1

Use una flagvariable to true, configúrelo, verifique si es cierto, solo returnrealice una llamada de actividad.

También puede usar setClickable (false) one ejecutando la llamada de actividad

flg=false
 public void onClick(View view) { 
       if(flg==true)
         return;
       else
       { flg=true;
        // perform click}
    } 
MKJParekh
fuente
perform click; wait; flg = false;para cuando volvamos
Xeno Lupus
-1

Puede simplemente anular startActivityForResult y usar la variable de instancia:

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);
    }
}
Aleksei Minaev
fuente
-4

Puedes probar esto también

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

        });
Karthik
fuente