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

Posted

技术标签:

【中文标题】如何防止活动在按下按钮时加载两次【英文标题】:How to prevent the activity from loading twice on pressing the button 【发布时间】:2011-12-26 00:53:41 【问题描述】:

如果我在第一次点击后立即按两次按钮,我会尝试阻止 Activity 加载两次。

我有一个在点击按钮时加载的活动,比如说

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

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

有人知道如何防止这种情况吗?

【问题讨论】:

您可以在打开活动后禁用按钮...当活动完成时,重新启用它...您可以通过调用onActivityResult函数来检测第二个活动的完成 第一次单击时禁用该按钮,仅当您希望再次单击该按钮时才重新启用它。 如果下一个语句是针对某个较长的进程或活动启动,则禁用以简单的方式不起作用...要禁用按钮,您必须创建一个单独的线程... 如果两次访问相同的 API,请参阅此处:techstricks.com/avoid-multiple-requests-when-using-volley Avoid button multiple rapid clicks的可能重复 【参考方案1】:

将此添加到您在androidManifest.xml 中的Activity 定义...

android:launchMode = "singleTop"

例如:

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

【讨论】:

好吧,我猜你在开始新的活动后正在做一些长时间的处理。这就是为什么屏幕变成黑色的原因。现在,如果您想避免出现黑屏,您应该在活动开始时显示一个进度对话框,并在单独的线程中进行长时间处理(即 UI 线程或只需使用异步类)。处理完成后隐藏该对话框。这是我所知道的最好的解决方案,我已经用过好几次了......:) 我有对话框要显示。但是,是的,我有一种方法在 onCreate 中指向 web。但这是唯一的解决方案吗?因为在这一点上,我想在不改变线程和所有的情况下进行处理。那么你知道其他可能的方法吗?而且我的列表适配器中有按钮,并且我已经在 xml 中声明了它的方法,而不是以编程方式 还有什么可能???您必须以一种或其他方式实现线程才能获得外观流畅的应用程序......试试吧伙计......;)只需将所有当前代码放在一个方法中,然后从您编写的同一位置的单独线程调用该方法它更早...它几乎不会增加五到六行代码.. 这可以防止 Activity 的两个实例存在,但不会防止代码错误地运行两次。尽管赞成票较少,但接受的答案更好。 这是错误的,它使活动永远不会存在两次,即使在不同的任务中也是如此。正确的做法是android:launchMode = "singleTop",在不破坏Android多任务处理的情况下达到效果。该文档指出,大多数应用程序不应使用 singleInstance 选项。【参考方案2】:

在按钮的事件监听器中,禁用按钮并显示另一个活动。

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

【讨论】:

这是正确的方法。它甚至会为您处理 Button Selected States(如果您提供它们)以及您期望从一个简单的标准 Widget 获得的所有 Material Design “好东西”。我不敢相信人们会为此使用计时器。然后你开始看到奇怪的库来处理这些事情......【参考方案3】:

您可以像这样使用意图标志。

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

它只会在历史堆栈的顶部打开一个活动。

【讨论】:

这个答案与最受好评的答案相结合似乎效果最好。在 Activity 的清单中使用这个标志:android:launchMode = "singleTop",这样就可以解决,而无需将标志添加到每个 Intent。 这在您需要嵌套活动时没有用,因为您不能有两个相同类型的活动。 这在 startActivityForResult 的情况下不起作用【参考方案4】:

由于 SO 不允许我对其他答案发表评论,因此我必须用新答案污染此线程。

“活动打开两次”问题的常见答案以及我对这些解决方案的体验(Android 7.1.1):

    禁用启动活动的按钮:有效但感觉有点笨拙。如果您有多种方法可以在您的应用程序中启动活动(例如,操作栏中的按钮并通过单击列表视图中的项目),您必须跟踪多个 GUI 元素的启用/禁用状态。另外,例如,在列表视图中禁用单击的项目不是很方便。因此,这不是一种非常通用的方法。 launchMode="singleInstance":无法使用 startActivityForResult(),使用 startActivity() 中断导航,Android 清单文档不建议将其用于常规应用程序。 launchMode="singleTask":不适用于 startActivityForResult(),Android 清单文档不建议将其用于常规应用程序。 FLAG_ACTIVITY_REORDER_TO_FRONT:断开后退按钮。 FLAG_ACTIVITY_SINGLE_TOP:不工作,活动仍然打开两次。 FLAG_ACTIVITY_CLEAR_TOP:这是唯一一个为我工作的人。

编辑:这是用于使用 startActivity() 启动活动。使用 startActivityForResult() 时,我需要同时设置 FLAG_ACTIVITY_SINGLE_TOP 和 FLAG_ACTIVITY_CLEAR_TOP。

【讨论】:

FLAG_ACTIVITY_CLEAR_TOP:这是唯一一个在 Android 7.1.1 上为我工作的人 我正在使用“FLAG_ACTIVITY_REORDER_TO_FRONT”,它工作得很好,后退按钮也能正常工作。你说的“Breaks back button”到底是什么意思?你能澄清一下吗? 我发现“REORDER”标志有一个错误......而且它不是在 KitKat 中重新排序。但是,我在 Lollipop and Pie 中检查了它,它工作正常。【参考方案5】:

只有当startActivity(intent) 时它才对我有用

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

【讨论】:

@raj 你试过在你的活动标签的清单文件中添加这个android:launchMode = "singleInstance"吗?【参考方案6】:

使用 singleInstance 避免活动调用两次。

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

【讨论】:

【参考方案7】:

假设@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,则使用相同侦听器的其他按钮仍将启用。所以为了确保监听器的动作不会被调用两次,你需要有一个全局的东西来禁用监听器的所有调用(不管它是否是新实例)

我的回答和其他人有什么区别?

他们正在以正确的方式思考,但他们没有考虑将来返回到调用活动的同一实例:)

【讨论】:

服务器,感谢您的研究。这个问题已经解决了,你的答案对于你所说的情况来说看起来还是很有希望的。让我试试看结果:) 我的一个游戏中有这个问题。我有具有相同侦听器的“选择级别”气球,并且视图只是标签不同。因此,如果我快速选择两个气球,它会启动两个活动。我知道这是因为新活动开始发出声音......在这种情况下声音播放了两次......但您可以通过单击返回来检查它,这将带您进入上一个活动 这还不够。您还需要使用 synchronized(mIsClicked) ... 以确保 100% 安全。 @Monstieur 你不需要同步块,因为这都是主线程...... @MartinMarconcini 仅仅因为它在 Android 活动中是安全的,并不能使它成为好的代码。如果它是一个独立的类,它必须被记录为不是线程安全的。【参考方案8】:

对于这种情况,我会选择两种方法中的一种,singleTask in manifest.xml 或 Activity 的 onResume()onDestroy() 方法中的标志。

对于第一个解决方案:我更喜欢在清单中使用singleTask 而不是singleInstance,因为使用singleInstance 我发现在某些情况下活动创建一个新的独立实例,这导致在 bcakground 中正在运行的应用程序中有两个独立的应用程序窗口,此外还有额外的内存分配,当用户打开应用程序视图以选择要恢复的应用程序时,这会导致非常糟糕的用户体验。 因此,更好的方法是在 manifest.xml 中定义活动,如下所示:

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

您可以查看活动启动模式here。


对于第二个解决方案,您只需定义一个静态变量或一个偏好变量,例如:

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

【讨论】:

【参考方案9】:

我认为您正在以错误的方式解决问题。一般来说,一个活动在其任何启动生命周期方法(onCreate()onResume() 等)中发出长时间运行的 Web 请求都是一个坏主意。实际上,这些方法应该简单地用于实例化和初始化您的活动将使用的对象,因此应该相对较快。

如果您需要执行网络请求,请在新启动的活动的后台线程中执行此操作(并在新活动中显示加载对话框)。后台请求线程完成后,它可以更新活动并隐藏对话框。

这意味着您的新活动应该立即启动并防止双击成为可能。

【讨论】:

【参考方案10】:

希望这会有所帮助:

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

【讨论】:

【参考方案11】:

// 跟踪事件时间的变量

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.
         
      
 

【讨论】:

【参考方案12】:

如果您不想使用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...
        
    );

【讨论】:

【参考方案13】:

只需在按钮的onClick方法中维护一个标志为:

public boolean oneTimeLoadActivity = false;

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

【讨论】:

【参考方案14】:

将启动模式添加为清单中的单个任务,以避免在单击时打开两次活动

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

【讨论】:

【参考方案15】:

如果你使用 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;
  

也可以在按下后退按钮时使用,因为请求代码会在事件中返回。

【讨论】:

【参考方案16】:

我们也可以使用按钮去抖动。

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

【讨论】:

【参考方案17】:
myButton.setOnClickListener(new View.OnClickListener() 
      public void onClick(View view) 
      myButton.setOnClickListener(null);
    
);

【讨论】:

这可能行不通,因为您必须将其声明为 final。【参考方案18】:

使用flag 变量设置它to true, 检查它是否真的只是return 否则执行活动调用。

你也可以使用 setClickable(false) 来执行 Activity 调用

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

【讨论】:

perform click; wait; flg = false; 我们什么时候回来【参考方案19】:

你可以重写 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);
    

【讨论】:

【参考方案20】:

你也可以试试这个

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

        );

【讨论】:

以上是关于如何防止活动在按下按钮时加载两次的主要内容,如果未能解决你的问题,请参考以下文章

如何防止按钮双击

按下浏览器重新加载按钮时防止变量丢失[重复]

按下后退按钮时防止反应路由器重新加载 API 调用。反应

Android - 如何防止按下音量或相机键时手机屏幕打开?

xctest - 如何测试是不是在按下按钮时加载新视图

通过单击按钮调用 onPaint()