【问题标题】: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

标签: android button android-activity onclick


【解决方案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):

  1. 禁用启动活动的按钮:有效但感觉有点笨拙。如果您有多种方法可以在您的应用程序中启动活动(例如,操作栏中的按钮并通过单击列表视图中的项目),您必须跟踪多个 GUI 元素的启用/禁用状态。另外,例如,在列表视图中禁用单击的项目不是很方便。因此,这不是一种非常通用的方法。
  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 上为我工作的人
  • 我正在使用“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);
                                    }
                        
                                });
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2016-05-20
                          • 2011-06-14
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-09-02
                          相关资源
                          最近更新 更多