如何在Android中暂停/休眠线程或进程?

323
我希望在两行代码之间加入一个暂停,让我解释一下:
-> 用户点击一个按钮(实际上是一张卡片),我通过改变这个按钮的背景来显示它:
thisbutton.setBackgroundResource(R.drawable.icon);

->在1秒钟后,我需要通过更改按钮的背景颜色来返回到其先前的状态:

thisbutton.setBackgroundResource(R.drawable.defaultcard);

-> 我曾尝试使用以下代码在这两行代码之间暂停线程:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

然而,这并不起作用。也许我需要暂停的是进程而不是线程?我还尝试过以下方法(但它不起作用):
new Reminder(5);

使用这个:

public class Reminder {

Timer timer;

        public Reminder(int seconds) {
            timer = new Timer();
            timer.schedule(new RemindTask(), seconds*1000);
        }

        class RemindTask extends TimerTask {
            public void run() {
                System.out.format("Time's up!%n");
                timer.cancel(); //Terminate the timer thread
            }
        }  
    }

如何暂停/休眠线程或进程?


6
哦,只需使用经典的线程暂停块:while (true) {}。 - KristoferA
9
@KristoferA-Huagati.com,我不确定你是否在讽刺,或者确实有一些 Dalvik/Android 的魔法可以使这在 Android 上是可接受的。你能否请澄清一下?抱歉怀疑,但我问这个问题是因为虽然 (!conditionCheck()) {} 通常是被不鼓励的。 - Miserable Variable
1
然而,这并没有起作用。 我也尝试过(但它不起作用)。 这是一个经典的说有问题却不提供症状的例子。这些尝试以何种方式未能满足您的要求?线程是否未暂停?是否收到错误消息? - LarsH
12个回答

483

解决这个问题的一种方法是使用Handler.postDelayed()方法。一些谷歌的培训材料也建议使用相同的解决方案。

@Override
public void onClick(View v) {
    my_button.setBackgroundResource(R.drawable.icon);

    Handler handler = new Handler(); 
    handler.postDelayed(new Runnable() {
         @Override 
         public void run() { 
              my_button.setBackgroundResource(R.drawable.defaultcard); 
         } 
    }, 2000); 
}

然而,一些人指出上述解决方案会导致内存泄漏,因为它使用了非静态的内部类和匿名类,这些类隐式地持有对其外部类即Activity的引用。当Activity上下文被垃圾回收时,这就成了一个问题。

一种更复杂的解决方案是在Activity内部使用静态内部类来继承HandlerRunnable,因为静态内部类不会隐式地持有对其外部类的引用:

private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();

public static class MyRunnable implements Runnable {
    private final WeakReference<Activity> mActivity;

    public MyRunnable(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void run() {
        Activity activity = mActivity.get();
        if (activity != null) {
            Button btn = (Button) activity.findViewById(R.id.button);
            btn.setBackgroundResource(R.drawable.defaultcard);
        }
    }
}

private MyRunnable mRunnable = new MyRunnable(this);

public void onClick(View view) {
    my_button.setBackgroundResource(R.drawable.icon);

    // Execute the Runnable in 2 seconds
    mHandler.postDelayed(mRunnable, 2000);
}

请注意,Runnable使用WeakReference引用Activity,这在需要访问UI的静态类中是必要的。


3
可以实现,但这样引入延迟对整个代码来说相当不便,对吧? - Ehtesh Choudhury
4
我不确定你所说的“不方便”是什么意思。Handler的postDelayed方法旨在告诉Android,您希望在一定时间后执行一些代码。 - tronman
27
2年后,这段代码终于帮了我!感谢@tronman!! :) - Melvin Lai
2
你可以简单地将它复制到另一个(最终)变量中,如 final Button mynewbutton = mybutton;,然后在处理程序和可运行程序中使用 mynewbutton - Dzhuneyt
2
@MelvinLai 这个代码帮了我5年,谢谢! :) - gabrieloliveira
显示剩余6条评论

203
你可以尝试这个,它很简短
SystemClock.sleep(7000);

警告: 绝对不要在用户界面线程上执行此操作。

使用它来休眠例如后台线程。


完整的解决方案为: 此API可在版本1中使用。

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View button) {
                button.setBackgroundResource(R.drawable.avatar_dead);
                final long changeTime = 1000L;
                button.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setBackgroundResource(R.drawable.avatar_small);
                    }
                }, changeTime);
            }
        });

无需创建 tmp Handler。此解决方案比 @tronman 更好,因为我们不保留视图通过 Handler。 此外,我们没有处理在错误线程中创建的问题;)

文档

public static void sleep (long ms)

API 级别 1 中添加

在返回之前等待给定数量的毫秒(of uptimeMillis)。类似于 sleep(long),但不会抛出 InterruptedException;interrupt() 事件被推迟到下一个可中断操作。 直到经过至少指定的毫秒数后才会返回。

参数

ms:在返回之前要睡眠的时间,以 uptime 的毫秒为单位。

View 类中用于 postDelayed 的代码:

/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 * @param delayMillis The delay (in milliseconds) until the Runnable
 *        will be executed.
 *
 * @return true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the Runnable will be processed --
 *         if the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 *
 * @see #post
 * @see #removeCallbacks
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}

22
让安卓用户界面冻结?不要这样做。 - shkschneider
3
Ctrl + Shift + O 是 Eclipse 中的自动导入快捷键。 - Gelldur
14
Gelldur,你没有理解@RichieHH评论的重点。与您发布帖子之前被接受的答案相比,您建议的方法并不能帮助OP解决问题。原因在于:代码无论是像OP展示的那样,还是像被接受的答案中展示的那样,都在一个UI事件处理程序中运行。说“天啊,当然要在后台线程中执行”是无关紧要的,除非您展示如何将其放入后台线程中。而此时,您会发现您的答案比已经被接受的答案更加复杂。我提到了三年前已经有一个更好的解决方案被采纳了吗? :P - ToolmakerSteve
2
忘了提到这个建议特别不符合问题的目的,因为OP明确想要在延迟后执行UI操作。因此,你会有一个解决方案,在其中创建了一个后台线程(已经比解决问题所需的更重),睡眠,然后你必须(以某种方式)回到UI线程。Handler + postRunnable可以在一步完成所有这些操作。而且没有创建第二个线程的系统开销。 - ToolmakerSteve
2
@ToolmakerSteve现在开心了吗:D?主要问题是:“如何在Android中暂停/休眠线程或进程?”所以我的答案很简单:)。如果有人谷歌“如何休眠线程”,这个问题就会显示出来。他/她可能正在寻找我的答案:P。但好吧,我添加了完整的回复;) - Gelldur
显示剩余6条评论

32

我使用这个:

Thread closeActivity = new Thread(new Runnable() {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
      // Do some stuff
    } catch (Exception e) {
      e.getLocalizedMessage();
    }
  }
});

5
e.getLocalizedMessage() 是用来获取异常信息的方法。 - Philipp Reichart
当我需要一个简单、通用和快速的异常消息时,我使用 e.getLocalizedMessage()。 - Byt3
1
但我敢打赌你不会在 OP 所要求的情况下执行此操作,也就是在UI点击方法内部进行操作,这将进一步更新UI。被接受的“Handler/postDelayed”解决方案具有两个优点:(1)避免了第二个线程的系统开销,(2)在UI线程上运行,因此可以进行UI更改而不会引发异常。 - ToolmakerSteve
1
不直接涉及问题的主要目标,但你不应该捕获 Exception。而应该捕获 InterruptedException。通过捕获 Exception,你将捕获所有可能出错的情况,从而隐藏了本应检测到的问题。 - Herve Thu

21

我使用CountDownTime

new CountDownTimer(5000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        // do something after 1s
    }

    @Override
    public void onFinish() {
        // do something end times 5s
    }

}.start(); 

这很有用。 - uzaysan

16
您可能不希望以那种方式实现。在您的按钮点击事件处理程序中显式放置 sleep() 将会锁定整个用户界面一秒钟。另一种选择是使用某种单次触发的计时器。创建一个计时器任务来将背景颜色更改回默认颜色,并在计时器上进行调度。
另一个可能性是使用处理程序。这里有一个教程,介绍了有人从使用计时器转向使用处理程序的情况。
顺便说一下,您不能暂停进程。Java(或Android)进程至少有1个线程,您只能使线程休眠。

9
这是我在一天结束时所做的事情 - 现在已经很好了。
@Override
    public void onClick(View v) {
        my_button.setBackgroundResource(R.drawable.icon);
        // SLEEP 2 SECONDS HERE ...
        final Handler handler = new Handler(); 
        Timer t = new Timer(); 
        t.schedule(new TimerTask() { 
                public void run() { 
                        handler.post(new Runnable() { 
                                public void run() { 
                                 my_button.setBackgroundResource(R.drawable.defaultcard); 
                                } 
                        }); 
                } 
        }, 2000); 
    }

3
为什么不使用postDelayed?不需要计时器。 - RichieHH

9
除了Yankowsky先生的回答外,您还可以使用postDelayed()。这在任何View上都可用(例如,您的卡片),并需要一个Runnable和一个延迟时间。它会在延迟后执行Runnable

5

或者你可以使用:

android.os.SystemClock.sleep(checkEvery)

这种方法的优点在于不需要使用包装的 try ... catch


5
这是我的例子。
创建一个 Java Utils。
    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.Intent;

    public class Utils {

        public static void showDummyWaitingDialog(final Context context, final Intent startingIntent) {
            // ...
            final ProgressDialog progressDialog = ProgressDialog.show(context, "Please wait...", "Loading data ...", true);

            new Thread() {
                public void run() {
                    try{
                        // Do some work here
                        sleep(5000);
                    } catch (Exception e) {
                    }
                    // start next intent
                    new Thread() {
                        public void run() {
                        // Dismiss the Dialog 
                        progressDialog.dismiss();
                        // start selected activity
                        if ( startingIntent != null) context.startActivity(startingIntent);
                        }
                    }.start();
                }
            }.start();  

        }

    }    

3
问题很久以前就已经得到了解决,但最近又增加了一个答案,与问题无关,因为它没有涉及到想要执行 UI 工作的需求。在这里,你无法进行 UI 工作,因为你正在运行一个新线程,而不是 UI 线程。 - ToolmakerSteve
1
在用户体验方面,向用户显示阻塞对话框以加载数据是不好的。 - 2Dee
如果我们不需要 UI 更改,我认为这种方法比在主线程上运行处理程序更好。但是定时器类或在不同的线程上显式运行处理程序呢? - JCarlosR

2
如果您使用 Kotlin 和 协程(coroutines),您可以简单地执行以下操作。
GlobalScope.launch {
   delay(3000) // In ms
   //Code after sleep
}

如果您需要更新用户界面
GlobalScope.launch {
  delay(3000)
  GlobalScope.launch(Dispatchers.Main) {
    //Action on UI thread
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接