如何防止活动加载两次按下按钮

我试图阻止活动从载入两次,如果我按下按钮两次立即后,第一次点击。

我有一个活动,加载一个按钮的点击,说

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

现在,因为要加载的活动具有网络调用,所以加载(MVC)需要一点时间。我确实显示了一个加载视图,但是如果我在此之前按两次按钮,我可以看到活动被加载两次。

有人知道怎么避免这种情况吗?

94618 次浏览

Add this to your Activity definition in AndroidManifest.xml...

android:launchMode = "singleTop"

For example:

<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode = "singleTop"/>
myButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
myButton.setOnClickListener(null);
}
});

Use a flag variable set it to true, Check if its true just return else perform Activity Call.

You can also use setClickable(false) one executing the Activity Call

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

Just maintain one flag in button onClick method as:

public boolean oneTimeLoadActivity = false;

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

I think you're going about solving the problem the wrong way. Generally it is a bad idea for an activity to be making long-running web requests in any of its startup lifecycle methods (onCreate(), onResume(), etc). Really these methods should simply be used to instantiate and initialise objects your activity will use and should therefore be relatively quick.

If you need to be performing a web request then do this in a background thread from your newly launched activity (and show the loading dialog in the new activity). Once the background request thread completes it can update the activity and hide the dialog.

This then means your new activity should get launched immediately and prevent the double click from being possible.

In the button's event listener, disable the button and show another activity.

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


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

Override onResume() to re-enable the button.

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


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

You can try this also

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


});

Hope this helps:

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

Let's say @wannik is right but if we have more than 1 button calling same action listener and i click two buttons once almost same time before starting next activity...

So it is good if you have field private boolean mIsClicked = false; and in the listener:

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

And onResume() we need to return the state:

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


mIsClicked = false;
}

What's the deifference between my and @wannik's answer?

If you set enabled to false in the listener of it's calling view other button using same listener will still be enabled. So to be sure that listener's action is not called twice you need to have something global that disables all callings of the listener(nevermind if it's new instance or no)

What is the difference between my answer and others?

They are thinking in right way but they are not thinking for future return to the same instance of the calling activity :)

You can use the intent flags like this.

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

It will make only one activity be open at the top of the history stack.

You could just override startActivityForResult and use instance variable:

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

If you're using onActivityResult, you could use a variable to save state.

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

Works on back button pressed too because request code is returned on event.

Use singleInstance to avoid activity to invoke twice.

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

Other very very simple solution if you no want use onActivityResult() is disable the button for 2 seconds (or time you want), is not ideal, but can solve partly the problem is some cases and the code is 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...
}
});

For this situation, I'll go for one of two approached, singleTask in manifest.xml OR a flag in the Activity's onResume() & onDestroy() methods respectively.

For the first solution: I prefer to use singleTask for the activity in the manifest rather than singleInstance, as per using singleInstance I figured out that in some occasions the activity creating a new separate instance for itself which result to have a two separate applications window in the running apps in bcakground and besides extra memory allocations that would result a very bad User Experience when the user opens the apps view to choose some app to resume. So, the better way is to have the activity defined at the manifest.xml like the following:

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

you can check activity launch modes here.


For the second solution, you have to just define a static variable or a preference variable, for example:

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


}

and from the other side when you want to launch this activity, just check:

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

Since SO doesn't allow me to comment on other answers, I have to pollute this thread with a new answer.

Common answers for the "activity opens twice" problem and my experiences with these solutions (Android 7.1.1):

  1. Disable button that starts the activity: Works but feels a little clumsy. If you have multiple ways to start the activity in your app (e.g. a button in the action bar AND by clicking on an item in a list view), you have to keep track of the enabled/disabled state of multiple GUI elements. Plus it's not very convenient to disable clicked items in a list view, for example. So, not a very universal approach.
  2. launchMode="singleInstance": Not working with startActivityForResult(), breaks back navigation with startActivity(), not recommended for regular applications by Android manifest documentation.
  3. launchMode="singleTask": Not working with startActivityForResult(), not recommended for regular applications by Android manifest documentation.
  4. FLAG_ACTIVITY_REORDER_TO_FRONT: Breaks back button.
  5. FLAG_ACTIVITY_SINGLE_TOP: Not working, activity is still opened twice.
  6. FLAG_ACTIVITY_CLEAR_TOP: This is the only one working for me.

EDIT: This was for starting activities with startActivity(). When using startActivityForResult() I need to set both FLAG_ACTIVITY_SINGLE_TOP and FLAG_ACTIVITY_CLEAR_TOP.

It was only working for me when startActivity(intent)

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

// variable to track event time

private long mLastClickTime = 0;

2.In onClick check that if current time and last click time difference are less than i second then dont't do anything(return) else go for click event

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

add Launch Mode as single task in manifest to avoid activity opening twice on clicking

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

We can use button debounce as well.

public abstract class DebounceOnClickListener implements View.OnClickListener {


private final long minimumInterval;
private final Map<View, Long> lastClickMap;


   

public abstract void debounceOnClick(View view);


  

public DebounceOnClickListener(long minIntervalMilliSec) {
this.minimumInterval = minIntervalMilliSec;
this.lastClickMap = new WeakHashMap<View, Long>();
}


@Override
public void onClick(View clickedView) {
Long previousClickTimestamp = lastClickMap.get(clickedView);
long currentTimestamp = SystemClock.uptimeMillis();


lastClickMap.put(clickedView, currentTimestamp);
if (previousClickTimestamp == null ||
(currentTimestamp - previousClickTimestamp.longValue() > minimumInterval)) {
debounceOnClick(clickedView);
}
}
}

In kotlin you can use this extension function to disable a view's click , this will enable the view after the time

fun View.disableClick(time: Long = 1000) {
isEnabled = false
postDelayed({
isEnabled = true
}, time)
}


you can call the function like below

  binding.btGetStarted.setOnClickListener {
//disabling button click
binding.btGetStarted.disableClick()
//activity redirection
ActivityUtils.startActivity(this,RegisterActivity::class.java, isFinish = false)
}