如何防止活动在按下按钮时加载两次
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);
);
【讨论】:
以上是关于如何防止活动在按下按钮时加载两次的主要内容,如果未能解决你的问题,请参考以下文章