我有一个简单的Activity,有两个按钮“On”和“Off”。我想用按钮“开”在循环中开始改变背景的颜色,并用“关闭”按钮停止。另外,我需要通过点击“关闭”按钮获得红色。我写了简单的程序,一切都很好,但我无法理解一件事。为什么最后一种颜色不总是红色?如果我在主线程中使用代码循环
Thread.sleep(100);
或
Thread.sleep(1000);
我总是有红色,但如果我设置
Thread.sleep(10);
我有随机的最后一种颜色。为什么?
谢谢!
我有这段代码:
公共类MyActivity扩展了Activity {
final Handler myHandler = new Handler();
private int randColor;
final Runnable updateColor = new Runnable() {
public void run() {
final Random random = new Random();
randColor = Color.rgb(random.nextInt (255), random.nextInt (255), random.nextInt (255));
mRelativeLayout.setBackgroundColor(randColor);
}
};
private ColorChanger myThread;
class ColorChanger extends Thread {
private volatile boolean mIsStopped = false;
@Override
public void run() {
super.run();
do
{
if (!Thread.interrupted()) {
myHandler.post(updateColor);
}
else
{
return;
}
try{
Thread.sleep(100);
}catch(InterruptedException e){
return;
}
}
while(true);
}
public void stopThis() {
this.interrupt();
}
}
private RelativeLayout mRelativeLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mRelativeLayout = (RelativeLayout)findViewById(R.id.relativeLayout);
}
public void onflagClick(View view) {
myThread = new ColorChanger();
myThread.start();
}
public void onflagoffClick(View view) throws InterruptedException {
myThread.interrupt();
if(myThread.isAlive())
{
try {
myThread.join();
} catch(InterruptedException e){
}
}
else
{
mRelativeLayout.setBackgroundColor(getResources().getColor(R.color.redColor));
}
mRelativeLayout.setBackgroundColor(getResources().getColor(R.color.redColor));
}
}
答案 0 :(得分:2)
我同意之前的答案,但提出了不同的解决方案。
首先让我说我建议你停止使用Runnables。通常,将Runnable发布到Handler的效率低于发送消息的效率,尽管此规则的例外非常少见。
现在,如果我们发送消息,我们该怎么办?我们基本上想做的是继续做我们正在做的事情,直到遇到条件。一个很好的方法是编写一个消息处理程序,它接收一个消息,完成我们的工作(设置颜色),检查我们是否应该继续,如果是这样,将来安排一个新的消息来做更多的工作。让我们看看我们如何做到这一点。
假设下面的代码在Activity中。
private static final int MSG_UPDATE_COLOR = 1;
private static final int DELAY = 10; //10 millis
private final Object mLock = new Object();
private boolean mContinue = true;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_COLOR:
synchronized (mLock) {
if (mContinue) {
setColor(Color.rgb(random.nextInt (255), random.nextInt (255), random.nextInt (255)));
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_COLOR, DELAY);
} else {
setColor(Color.RED);
}
}
break;
}
}
}
}
public void onflagClick(View view) {
mHandler.sendEmptyMessage(MSG_UPDATE_COLOR);
}
public void onflagoffClick(View view) throws InterruptedException {
synchronized (mLock) {
mContinue = false;
}
// cancel any pending update
mHandler.removeMessages(MSG_UPDATE_COLOR);
// schedule an immediate update
mHandler.sendEmptyMessage(MSG_UPDATE_COLOR);
}
好的,那么,这里发生了什么。我们已经创建了一个处理程序,它将执行所有颜色更新。当我们的开始活动发生时,我们会启动它。然后,Message在十毫秒内调度一条新消息(以及颜色更新)。当停止事件发生时,我们重置一个消息处理程序读取的标志,以确定是否应该安排新的更新。然后我们取消预定所有更新消息,因为它可能会在未来几毫秒内安排,而是发送一条即时消息进行最终的颜色更新。
对于奖励积分,我们消除了使用第二个线程来节省资源。仔细观察我已经使用了synchronized块,但这些实际上是不必要的,因为所有都发生在主线程上。我包括这些以防万一有人从后台线程更改mContinue
。此策略的另一个重点是所有颜色更新都发生在代码中的一个位置,因此更容易理解。
答案 1 :(得分:1)
当您发布到Handler
时,它会在将来的某个时间运行您的Runnable
。这不是直接的。它也可以在一个队列中工作,所以你发布到Handler
的次数越多,你就会堆叠最终将按顺序执行的命令。
您面临竞争条件,因为使用Thread.sleep(10)
时,该程序很可能会堆叠很多Runnables
来执行。无论您的线程是否正在运行,它们都将运行,因为它们已排队等待在主线程上运行。 Thread.sleep(100)
或Thread.sleep(1000)
只是因为您为系统提供了足够的时间来执行所有颜色命令而导致此问题。但是,如果您在恰当的时间按下关闭按钮,仍然可能出现此问题。
答案 2 :(得分:0)
正如DeeV告诉你的那样,Handler
将Runnables发送到Looper
,这基本上是一个Thread循环内部处理消息或每个循环中的runnables。您正在排队向主Looper
发送消息,然后您正在睡觉您的工作线程。您可能在工作线程的每个循环之间连续发送2个runnables,但主循环器只执行了最后一个,因此您无法看到每种颜色。
如果您想要一个简单的解决方案来使其发挥作用,您可以使用Object
或CountDownLatch
将您的主Looper
与您的工作人员Thread
同步。
例如:在你让工人Thread
睡觉之前,你可以做下一件事myLockObject.wait()
然后,您应该将post(Runnable)
更改为sendMessage(Message)
。在您handleMessage
Handler
myLockObject.notify()
handleMessage
Looper
Handler
,请注意Looper
将在您创建的Message
myHandler.obtainMessage()
内执行Thread
或者你可以指定你想表达的任何Looper
。要获得新的Object
,您应该使用Activity
。
这将使您的工作人员private myLockObject = new Object()
等待您的主{{1}}在等待X时间之前处理您的runnable,直到您发布下一个颜色。显然,您应该将新的{{1}}创建为{{1}}的字段,例如:
{{1}}